diff --git a/include/ghostty.h b/include/ghostty.h index c422c3584..082711836 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -757,6 +757,7 @@ typedef enum { GHOSTTY_ACTION_OPEN_URL, GHOSTTY_ACTION_SHOW_CHILD_EXITED, GHOSTTY_ACTION_PROGRESS_REPORT, + GHOSTTY_ACTION_SHOW_ON_SCREEN_KEYBOARD, } ghostty_action_tag_e; typedef union { diff --git a/src/Surface.zig b/src/Surface.zig index 1ab0fc59e..866505717 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -4804,6 +4804,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool {}, ), + .show_on_screen_keyboard => return try self.rt_app.performAction( + .{ .surface = self }, + .show_on_screen_keyboard, + {}, + ), + .select_all => { const sel = self.io.terminal.screen.selectAll(); if (sel) |s| { diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 0f2b68087..d2d444c3a 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -291,6 +291,9 @@ pub const Action = union(Key) { /// Show a native GUI notification about the progress of some TUI operation. progress_report: terminal.osc.Command.ProgressReport, + /// Show the on-screen keyboard. + show_on_screen_keyboard, + /// Sync with: ghostty_action_tag_e pub const Key = enum(c_int) { quit, @@ -345,6 +348,7 @@ pub const Action = union(Key) { open_url, show_child_exited, progress_report, + show_on_screen_keyboard, }; /// Sync with: ghostty_action_u diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig index becfac14a..a47d80ed1 100644 --- a/src/apprt/gtk-ng/class/application.zig +++ b/src/apprt/gtk-ng/class/application.zig @@ -618,6 +618,7 @@ pub const Application = extern struct { .toggle_window_decorations => return Action.toggleWindowDecorations(target), .toggle_command_palette => return Action.toggleCommandPalette(target), .toggle_split_zoom => return Action.toggleSplitZoom(target), + .show_on_screen_keyboard => return Action.showOnScreenKeyboard(target), // Unimplemented but todo on gtk-ng branch .inspector, @@ -2146,6 +2147,21 @@ const Action = struct { } } + pub fn showOnScreenKeyboard(target: apprt.Target) bool { + switch (target) { + .app => { + log.warn("show_on_screen_keyboard to app is unexpected", .{}); + return false; + }, + // NOTE: Even though `activateOsk` takes a gdk.Event, it's currently + // unused by all implementations of `activateOsk` as of GTK 4.18. + // The commit that introduced the method (ce6aa73c) clarifies that + // the event *may* be used by other IM backends, but for Linux desktop + // environments this doesn't matter. + .surface => |v| return v.rt_surface.surface.showOnScreenKeyboard(null), + } + } + fn getQuickTerminalWindow() ?*Window { // Find a quick terminal window. const list = gtk.Window.listToplevels(); diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index c12afc054..7e86354d3 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -573,6 +573,11 @@ pub const Surface = extern struct { return self.as(gtk.Widget).activateAction("win.toggle-command-palette", null) != 0; } + pub fn showOnScreenKeyboard(self: *Self, event: ?*gdk.Event) bool { + const priv = self.private(); + return priv.im_context.as(gtk.IMContext).activateOsk(event) != 0; + } + /// Set the current progress report state. pub fn setProgressReport( self: *Self, @@ -1946,18 +1951,29 @@ pub const Surface = extern struct { const event = gesture.as(gtk.EventController).getCurrentEvent() orelse return; const priv = self.private(); - if (priv.core_surface) |surface| { - const gtk_mods = event.getModifierState(); - const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); - const mods = gtk_key.translateMods(gtk_mods); - _ = surface.mouseButtonCallback( - .release, - button, - mods, - ) catch |err| { - log.warn("error in key callback err={}", .{err}); - return; - }; + const surface = priv.core_surface orelse return; + const gtk_mods = event.getModifierState(); + const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton()); + + const mods = gtk_key.translateMods(gtk_mods); + const consumed = surface.mouseButtonCallback( + .release, + button, + mods, + ) catch |err| { + log.warn("error in key callback err={}", .{err}); + return; + }; + + // Trigger the on-screen keyboard if we have no selection, + // and that the mouse event hasn't been intercepted by the callback. + // + // It's better to do this here rather than within the core callback + // since we have direct access to the underlying gdk.Event here. + if (!consumed and button == .left and !surface.hasSelection()) { + if (!self.showOnScreenKeyboard(event)) { + log.warn("failed to activate the on-screen keyboard", .{}); + } } } diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index faa4781f6..0f75a2d97 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -539,6 +539,7 @@ pub fn performAction( .check_for_updates, .undo, .redo, + .show_on_screen_keyboard, => { log.warn("unimplemented action={}", .{action}); return false; diff --git a/src/input/Binding.zig b/src/input/Binding.zig index b20319810..2ed6c2636 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -524,6 +524,14 @@ pub const Action = union(enum) { /// Has no effect on macOS. show_gtk_inspector, + /// Show the on-screen keyboard if one is present. + /// + /// Only implemented on Linux (GTK). On GNOME, the "Screen Keyboard" + /// accessibility feature must be turned on, which can be found under + /// Settings > Accessibility > Typing. Other platforms are as of now + /// untested. + show_on_screen_keyboard, + /// Open the configuration file in the default OS editor. /// /// If your default OS editor isn't configured then this will fail. @@ -1051,6 +1059,7 @@ pub const Action = union(enum) { .toggle_window_float_on_top, .toggle_secure_input, .toggle_command_palette, + .show_on_screen_keyboard, .reset_window_size, .crash, => .surface, diff --git a/src/input/command.zig b/src/input/command.zig index 84e9afc79..615ffb713 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -369,6 +369,12 @@ fn actionCommands(action: Action.Key) []const Command { .description = "Show the GTK inspector.", }}, + .show_on_screen_keyboard => comptime &.{.{ + .action = .show_on_screen_keyboard, + .title = "Show On-Screen Keyboard", + .description = "Show the on-screen keyboard if present.", + }}, + .open_config => comptime &.{.{ .action = .open_config, .title = "Open Config",