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;
|
_ = 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 {
|
pub fn core(self: *Self) *CoreSurface {
|
||||||
// This asserts the non-optional because libghostty should only
|
// This asserts the non-optional because libghostty should only
|
||||||
// be calling this for initialized surfaces.
|
// be calling this for initialized surfaces.
|
||||||
|
@ -15,6 +15,7 @@ const cgroup = @import("../cgroup.zig");
|
|||||||
const CoreApp = @import("../../../App.zig");
|
const CoreApp = @import("../../../App.zig");
|
||||||
const configpkg = @import("../../../config.zig");
|
const configpkg = @import("../../../config.zig");
|
||||||
const internal_os = @import("../../../os/main.zig");
|
const internal_os = @import("../../../os/main.zig");
|
||||||
|
const terminal = @import("../../../terminal/main.zig");
|
||||||
const xev = @import("../../../global.zig").xev;
|
const xev = @import("../../../global.zig").xev;
|
||||||
const CoreConfig = configpkg.Config;
|
const CoreConfig = configpkg.Config;
|
||||||
const CoreSurface = @import("../../../Surface.zig");
|
const CoreSurface = @import("../../../Surface.zig");
|
||||||
@ -404,6 +405,9 @@ pub const Application = extern struct {
|
|||||||
value.config,
|
value.config,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
.mouse_shape => Action.mouseShape(target, value),
|
||||||
|
.mouse_visibility => Action.mouseVisibility(target, value),
|
||||||
|
|
||||||
.new_window => try Action.newWindow(
|
.new_window => try Action.newWindow(
|
||||||
self,
|
self,
|
||||||
switch (target) {
|
switch (target) {
|
||||||
@ -439,8 +443,6 @@ pub const Application = extern struct {
|
|||||||
.present_terminal,
|
.present_terminal,
|
||||||
.initial_size,
|
.initial_size,
|
||||||
.size_limit,
|
.size_limit,
|
||||||
.mouse_visibility,
|
|
||||||
.mouse_shape,
|
|
||||||
.mouse_over_link,
|
.mouse_over_link,
|
||||||
.toggle_tab_overview,
|
.toggle_tab_overview,
|
||||||
.toggle_split_zoom,
|
.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(
|
pub fn newWindow(
|
||||||
self: *Application,
|
self: *Application,
|
||||||
parent: ?*CoreSurface,
|
parent: ?*CoreSurface,
|
||||||
|
@ -10,6 +10,7 @@ const apprt = @import("../../../apprt.zig");
|
|||||||
const input = @import("../../../input.zig");
|
const input = @import("../../../input.zig");
|
||||||
const internal_os = @import("../../../os/main.zig");
|
const internal_os = @import("../../../os/main.zig");
|
||||||
const renderer = @import("../../../renderer.zig");
|
const renderer = @import("../../../renderer.zig");
|
||||||
|
const terminal = @import("../../../terminal/main.zig");
|
||||||
const CoreSurface = @import("../../../Surface.zig");
|
const CoreSurface = @import("../../../Surface.zig");
|
||||||
const gresource = @import("../build/gresource.zig");
|
const gresource = @import("../build/gresource.zig");
|
||||||
const adw_version = @import("../adw_version.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 {
|
pub const signals = struct {
|
||||||
@ -81,6 +122,12 @@ pub const Surface = extern struct {
|
|||||||
/// The configuration that this surface is using.
|
/// The configuration that this surface is using.
|
||||||
config: ?*Config = null,
|
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
|
/// The GLAarea that renders the actual surface. This is a binding
|
||||||
/// to the template so it doesn't have to be unrefed manually.
|
/// to the template so it doesn't have to be unrefed manually.
|
||||||
gl_area: *gtk.GLArea = undefined,
|
gl_area: *gtk.GLArea = undefined,
|
||||||
@ -516,6 +563,8 @@ pub const Surface = extern struct {
|
|||||||
priv.rt_surface = .{ .surface = self };
|
priv.rt_surface = .{ .surface = self };
|
||||||
priv.precision_scroll = false;
|
priv.precision_scroll = false;
|
||||||
priv.cursor_pos = .{ .x = 0, .y = 0 };
|
priv.cursor_pos = .{ .x = 0, .y = 0 };
|
||||||
|
priv.mouse_shape = .text;
|
||||||
|
priv.mouse_hidden = false;
|
||||||
priv.size = .{
|
priv.size = .{
|
||||||
// Funky numbers on purpose so they stand out if for some reason
|
// Funky numbers on purpose so they stand out if for some reason
|
||||||
// our size doesn't get properly set.
|
// our size doesn't get properly set.
|
||||||
@ -687,6 +736,7 @@ pub const Surface = extern struct {
|
|||||||
gl_area.setHasStencilBuffer(0);
|
gl_area.setHasStencilBuffer(0);
|
||||||
gl_area.setHasDepthBuffer(0);
|
gl_area.setHasDepthBuffer(0);
|
||||||
gl_area.setUseEs(0);
|
gl_area.setUseEs(0);
|
||||||
|
gl_area.as(gtk.Widget).setCursorFromName("text");
|
||||||
_ = gtk.Widget.signals.realize.connect(
|
_ = gtk.Widget.signals.realize.connect(
|
||||||
gl_area,
|
gl_area,
|
||||||
*Self,
|
*Self,
|
||||||
@ -715,6 +765,22 @@ pub const Surface = extern struct {
|
|||||||
self,
|
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 {
|
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
|
// Signal Handlers
|
||||||
|
|
||||||
@ -1371,6 +1510,8 @@ pub const Surface = extern struct {
|
|||||||
// Properties
|
// Properties
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.config.impl,
|
properties.config.impl,
|
||||||
|
properties.@"mouse-shape".impl,
|
||||||
|
properties.@"mouse-hidden".impl,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const build_config = @import("../build_config.zig");
|
||||||
|
|
||||||
/// The possible cursor shapes. Not all app runtimes support these shapes.
|
/// The possible cursor shapes. Not all app runtimes support these shapes.
|
||||||
/// The shapes are always based on the W3C supported cursor styles so we
|
/// 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 {
|
pub fn fromString(v: []const u8) ?MouseShape {
|
||||||
return string_map.get(v);
|
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(.{
|
const string_map = std.StaticStringMap(MouseShape).initComptime(.{
|
||||||
|
Reference in New Issue
Block a user