mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-23 12:16:11 +03:00
apprt/gtk-ng: mouse shape,visibility (#7999)
Relatively simple port. A few cool things: 1. We use properties on `GhosttySurface` to set this now and standard property listeners 2. We make `terminal.MouseShape` a GObject enum if we have gobject available. 3. The property based approach means we don't have to manage `*gdk.Cursor` memory anywhere anymore. And, we're still Valgrind clean.
This commit is contained in:
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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(.{
|
||||
|
Reference in New Issue
Block a user