diff --git a/src/apprt/gtk-ng/Surface.zig b/src/apprt/gtk-ng/Surface.zig index dd5d15b3a..ab9b6de52 100644 --- a/src/apprt/gtk-ng/Surface.zig +++ b/src/apprt/gtk-ng/Surface.zig @@ -14,6 +14,12 @@ pub fn deinit(self: *Self) void { _ = self; } +/// Returns the GObject surface for this apprt surface. This is a function +/// so we can add some extra logic if we ever have to here. +pub fn gobj(self: *Self) *Surface { + return self.surface; +} + pub fn core(self: *Self) *CoreSurface { // This asserts the non-optional because libghostty should only // be calling this for initialized surfaces. diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig index d65c0df7c..a170afc74 100644 --- a/src/apprt/gtk-ng/class/application.zig +++ b/src/apprt/gtk-ng/class/application.zig @@ -15,6 +15,7 @@ const cgroup = @import("../cgroup.zig"); const CoreApp = @import("../../../App.zig"); const configpkg = @import("../../../config.zig"); const internal_os = @import("../../../os/main.zig"); +const terminal = @import("../../../terminal/main.zig"); const xev = @import("../../../global.zig").xev; const CoreConfig = configpkg.Config; const CoreSurface = @import("../../../Surface.zig"); @@ -404,6 +405,9 @@ pub const Application = extern struct { value.config, ), + .mouse_shape => Action.mouseShape(target, value), + .mouse_visibility => Action.mouseVisibility(target, value), + .new_window => try Action.newWindow( self, switch (target) { @@ -439,8 +443,6 @@ pub const Application = extern struct { .present_terminal, .initial_size, .size_limit, - .mouse_visibility, - .mouse_shape, .mouse_over_link, .toggle_tab_overview, .toggle_split_zoom, @@ -886,6 +888,45 @@ const Action = struct { } } + pub fn mouseShape( + target: apprt.Target, + shape: terminal.MouseShape, + ) void { + switch (target) { + .app => log.warn("mouse shape to app is unexpected", .{}), + .surface => |surface| { + var value = gobject.ext.Value.newFrom(shape); + defer value.unset(); + gobject.Object.setProperty( + surface.rt_surface.gobj().as(gobject.Object), + "mouse-shape", + &value, + ); + }, + } + } + + pub fn mouseVisibility( + target: apprt.Target, + visibility: apprt.action.MouseVisibility, + ) void { + switch (target) { + .app => log.warn("mouse visibility to app is unexpected", .{}), + .surface => |surface| { + var value = gobject.ext.Value.newFrom(switch (visibility) { + .visible => false, + .hidden => true, + }); + defer value.unset(); + gobject.Object.setProperty( + surface.rt_surface.gobj().as(gobject.Object), + "mouse-hidden", + &value, + ); + }, + } + } + pub fn newWindow( self: *Application, parent: ?*CoreSurface, diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index c3a2aeef8..0100f61c4 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -10,6 +10,7 @@ const apprt = @import("../../../apprt.zig"); const input = @import("../../../input.zig"); const internal_os = @import("../../../os/main.zig"); const renderer = @import("../../../renderer.zig"); +const terminal = @import("../../../terminal/main.zig"); const CoreSurface = @import("../../../Surface.zig"); const gresource = @import("../build/gresource.zig"); const adw_version = @import("../adw_version.zig"); @@ -52,6 +53,46 @@ pub const Surface = extern struct { }, ); }; + + pub const @"mouse-hidden" = struct { + pub const name = "mouse-hidden"; + const impl = gobject.ext.defineProperty( + name, + Self, + bool, + .{ + .nick = "Mouse Hidden", + .blurb = "Whether the mouse cursor should be hidden.", + .default = false, + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "mouse_hidden", + ), + }, + ); + }; + + pub const @"mouse-shape" = struct { + pub const name = "mouse-shape"; + const impl = gobject.ext.defineProperty( + name, + Self, + terminal.MouseShape, + .{ + .nick = "Mouse Shape", + .blurb = "The current mouse shape to show for the surface.", + .default = .text, + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "mouse_shape", + ), + }, + ); + }; }; pub const signals = struct { @@ -81,6 +122,12 @@ pub const Surface = extern struct { /// The configuration that this surface is using. config: ?*Config = null, + /// The mouse shape to show for the surface. + mouse_shape: terminal.MouseShape = .default, + + /// Whether the mouse should be hidden or not as requested externally. + mouse_hidden: bool = false, + /// The GLAarea that renders the actual surface. This is a binding /// to the template so it doesn't have to be unrefed manually. gl_area: *gtk.GLArea = undefined, @@ -516,6 +563,8 @@ pub const Surface = extern struct { priv.rt_surface = .{ .surface = self }; priv.precision_scroll = false; priv.cursor_pos = .{ .x = 0, .y = 0 }; + priv.mouse_shape = .text; + priv.mouse_hidden = false; priv.size = .{ // Funky numbers on purpose so they stand out if for some reason // our size doesn't get properly set. @@ -687,6 +736,7 @@ pub const Surface = extern struct { gl_area.setHasStencilBuffer(0); gl_area.setHasDepthBuffer(0); gl_area.setUseEs(0); + gl_area.as(gtk.Widget).setCursorFromName("text"); _ = gtk.Widget.signals.realize.connect( gl_area, *Self, @@ -715,6 +765,22 @@ pub const Surface = extern struct { self, .{}, ); + + // Some property signals + _ = gobject.Object.signals.notify.connect( + self, + ?*anyopaque, + &propMouseHidden, + null, + .{ .detail = "mouse-hidden" }, + ); + _ = gobject.Object.signals.notify.connect( + self, + ?*anyopaque, + &propMouseShape, + null, + .{ .detail = "mouse-shape" }, + ); } fn dispose(self: *Self) callconv(.C) void { @@ -761,6 +827,79 @@ pub const Surface = extern struct { ); } + //--------------------------------------------------------------- + // Properties + + fn propMouseHidden( + self: *Self, + _: *gobject.ParamSpec, + _: ?*anyopaque, + ) callconv(.c) void { + const priv = self.private(); + + // If we're hidden we set it to "none" + if (priv.mouse_hidden) { + priv.gl_area.as(gtk.Widget).setCursorFromName("none"); + return; + } + + // If we're not hidden we just trigger the mouse shape + // prop notification to handle setting the proper mouse shape. + self.propMouseShape(undefined, null); + } + + fn propMouseShape( + self: *Self, + _: *gobject.ParamSpec, + _: ?*anyopaque, + ) callconv(.c) void { + const priv = self.private(); + + // If our mouse should be hidden currently then we don't + // do anything. + if (priv.mouse_hidden) return; + + const name: [:0]const u8 = switch (priv.mouse_shape) { + .default => "default", + .help => "help", + .pointer => "pointer", + .context_menu => "context-menu", + .progress => "progress", + .wait => "wait", + .cell => "cell", + .crosshair => "crosshair", + .text => "text", + .vertical_text => "vertical-text", + .alias => "alias", + .copy => "copy", + .no_drop => "no-drop", + .move => "move", + .not_allowed => "not-allowed", + .grab => "grab", + .grabbing => "grabbing", + .all_scroll => "all-scroll", + .col_resize => "col-resize", + .row_resize => "row-resize", + .n_resize => "n-resize", + .e_resize => "e-resize", + .s_resize => "s-resize", + .w_resize => "w-resize", + .ne_resize => "ne-resize", + .nw_resize => "nw-resize", + .se_resize => "se-resize", + .sw_resize => "sw-resize", + .ew_resize => "ew-resize", + .ns_resize => "ns-resize", + .nesw_resize => "nesw-resize", + .nwse_resize => "nwse-resize", + .zoom_in => "zoom-in", + .zoom_out => "zoom-out", + }; + + // Set our new cursor. + priv.gl_area.as(gtk.Widget).setCursorFromName(name.ptr); + } + //--------------------------------------------------------------- // Signal Handlers @@ -1371,6 +1510,8 @@ pub const Surface = extern struct { // Properties gobject.ext.registerProperties(class, &.{ properties.config.impl, + properties.@"mouse-shape".impl, + properties.@"mouse-hidden".impl, }); // Signals diff --git a/src/terminal/mouse_shape.zig b/src/terminal/mouse_shape.zig index aedba2455..e71d4fb3b 100644 --- a/src/terminal/mouse_shape.zig +++ b/src/terminal/mouse_shape.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const build_config = @import("../build_config.zig"); /// The possible cursor shapes. Not all app runtimes support these shapes. /// The shapes are always based on the W3C supported cursor styles so we @@ -45,6 +46,16 @@ pub const MouseShape = enum(c_int) { pub fn fromString(v: []const u8) ?MouseShape { return string_map.get(v); } + + /// Make this a valid gobject if we're in a GTK environment. + pub const getGObjectType = switch (build_config.app_runtime) { + .gtk, .@"gtk-ng" => @import("gobject").ext.defineEnum( + MouseShape, + .{ .name = "GhosttyMouseShape" }, + ), + + .none => void, + }; }; const string_map = std.StaticStringMap(MouseShape).initComptime(.{