mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
apprt/gtk-ng: hook up title, pwd surface properties (#8005)
Continuing to plumb along the APIs necessary basic surface functionality before moving onto windowing functionality. This adds title/pwd as properties to Surface, adds abstractions necessary to manage that memory correctly, and also adds a tiny change to renderers to make everything slightly more usable under Valgrind.
This commit is contained in:
@ -36,8 +36,7 @@ pub fn close(self: *Self, process_active: bool) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getTitle(self: *Self) ?[:0]const u8 {
|
pub fn getTitle(self: *Self) ?[:0]const u8 {
|
||||||
_ = self;
|
return self.surface.getTitle();
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getContentScale(self: *const Self) !apprt.ContentScale {
|
pub fn getContentScale(self: *const Self) !apprt.ContentScale {
|
||||||
|
@ -46,6 +46,52 @@ pub fn Common(
|
|||||||
}
|
}
|
||||||
}).private else {};
|
}).private else {};
|
||||||
|
|
||||||
|
/// A helper that can be used to create a property that reads and
|
||||||
|
/// writes a private `?[:0]const u8` field type.
|
||||||
|
///
|
||||||
|
/// Reading the property will result in a copy of the string
|
||||||
|
/// and callers are responsible for freeing it.
|
||||||
|
///
|
||||||
|
/// Writing the property will free the previous value and copy
|
||||||
|
/// the new value into the private field.
|
||||||
|
///
|
||||||
|
/// The object class (Self) must still free the private field
|
||||||
|
/// in finalize!
|
||||||
|
pub fn privateStringFieldAccessor(
|
||||||
|
comptime name: []const u8,
|
||||||
|
) gobject.ext.Accessor(
|
||||||
|
Self,
|
||||||
|
@FieldType(Private.?, name),
|
||||||
|
) {
|
||||||
|
const S = struct {
|
||||||
|
fn getter(self: *Self) ?[:0]const u8 {
|
||||||
|
return @field(private(self), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setter(self: *Self, value: ?[:0]const u8) void {
|
||||||
|
const priv = private(self);
|
||||||
|
if (@field(priv, name)) |v| {
|
||||||
|
glib.free(@constCast(@ptrCast(v)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to copy this because it was already
|
||||||
|
// copied by the typedAccessor.
|
||||||
|
@field(priv, name) = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.getter = S.getter,
|
||||||
|
.getter_transfer = .none,
|
||||||
|
.setter = S.setter,
|
||||||
|
.setter_transfer = .full,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Common class functions.
|
/// Common class functions.
|
||||||
pub const Class = struct {
|
pub const Class = struct {
|
||||||
pub fn as(class: *Self.Class, comptime T: type) *T {
|
pub fn as(class: *Self.Class, comptime T: type) *T {
|
||||||
|
@ -416,11 +416,15 @@ pub const Application = extern struct {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
.pwd => Action.pwd(target, value),
|
||||||
|
|
||||||
.quit_timer => try Action.quitTimer(self, value),
|
.quit_timer => try Action.quitTimer(self, value),
|
||||||
|
|
||||||
.render => Action.render(self, target),
|
.render => Action.render(self, target),
|
||||||
|
|
||||||
// Unimplemented
|
.set_title => Action.setTitle(target, value),
|
||||||
|
|
||||||
|
// Unimplemented but todo on gtk-ng branch
|
||||||
.quit,
|
.quit,
|
||||||
.close_window,
|
.close_window,
|
||||||
.toggle_maximize,
|
.toggle_maximize,
|
||||||
@ -438,8 +442,6 @@ pub const Application = extern struct {
|
|||||||
.inspector,
|
.inspector,
|
||||||
.show_gtk_inspector,
|
.show_gtk_inspector,
|
||||||
.desktop_notification,
|
.desktop_notification,
|
||||||
.set_title,
|
|
||||||
.pwd,
|
|
||||||
.present_terminal,
|
.present_terminal,
|
||||||
.initial_size,
|
.initial_size,
|
||||||
.size_limit,
|
.size_limit,
|
||||||
@ -449,7 +451,6 @@ pub const Application = extern struct {
|
|||||||
.toggle_window_decorations,
|
.toggle_window_decorations,
|
||||||
.prompt_title,
|
.prompt_title,
|
||||||
.toggle_quick_terminal,
|
.toggle_quick_terminal,
|
||||||
.secure_input,
|
|
||||||
.ring_bell,
|
.ring_bell,
|
||||||
.toggle_command_palette,
|
.toggle_command_palette,
|
||||||
.open_url,
|
.open_url,
|
||||||
@ -471,6 +472,13 @@ pub const Application = extern struct {
|
|||||||
log.warn("unimplemented action={}", .{action});
|
log.warn("unimplemented action={}", .{action});
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Unimplemented
|
||||||
|
.secure_input,
|
||||||
|
=> {
|
||||||
|
log.warn("unimplemented action={}", .{action});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume it was handled. The unhandled case must be explicit
|
// Assume it was handled. The unhandled case must be explicit
|
||||||
@ -937,6 +945,24 @@ const Action = struct {
|
|||||||
gtk.Window.present(win.as(gtk.Window));
|
gtk.Window.present(win.as(gtk.Window));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pwd(
|
||||||
|
target: apprt.Target,
|
||||||
|
value: apprt.action.Pwd,
|
||||||
|
) void {
|
||||||
|
switch (target) {
|
||||||
|
.app => log.warn("pwd to app is unexpected", .{}),
|
||||||
|
.surface => |surface| {
|
||||||
|
var v = gobject.ext.Value.newFrom(value.pwd);
|
||||||
|
defer v.unset();
|
||||||
|
gobject.Object.setProperty(
|
||||||
|
surface.rt_surface.gobj().as(gobject.Object),
|
||||||
|
"pwd",
|
||||||
|
&v,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn quitTimer(
|
pub fn quitTimer(
|
||||||
self: *Application,
|
self: *Application,
|
||||||
mode: apprt.action.QuitTimer,
|
mode: apprt.action.QuitTimer,
|
||||||
@ -958,6 +984,24 @@ const Action = struct {
|
|||||||
.surface => |v| v.rt_surface.surface.redraw(),
|
.surface => |v| v.rt_surface.surface.redraw(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setTitle(
|
||||||
|
target: apprt.Target,
|
||||||
|
value: apprt.action.SetTitle,
|
||||||
|
) void {
|
||||||
|
switch (target) {
|
||||||
|
.app => log.warn("set_title to app is unexpected", .{}),
|
||||||
|
.surface => |surface| {
|
||||||
|
var v = gobject.ext.Value.newFrom(value.title);
|
||||||
|
defer v.unset();
|
||||||
|
gobject.Object.setProperty(
|
||||||
|
surface.rt_surface.gobj().as(gobject.Object),
|
||||||
|
"title",
|
||||||
|
&v,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This sets various GTK-related environment variables as necessary
|
/// This sets various GTK-related environment variables as necessary
|
||||||
|
@ -93,6 +93,40 @@ pub const Surface = extern struct {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const pwd = struct {
|
||||||
|
pub const name = "pwd";
|
||||||
|
pub const get = impl.get;
|
||||||
|
pub const set = impl.set;
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.nick = "Working Directory",
|
||||||
|
.blurb = "The current working directory as reported by core.",
|
||||||
|
.default = null,
|
||||||
|
.accessor = C.privateStringFieldAccessor("pwd"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const title = struct {
|
||||||
|
pub const name = "title";
|
||||||
|
pub const get = impl.get;
|
||||||
|
pub const set = impl.set;
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.nick = "Title",
|
||||||
|
.blurb = "The title of the surface.",
|
||||||
|
.default = null,
|
||||||
|
.accessor = C.privateStringFieldAccessor("title"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const signals = struct {
|
pub const signals = struct {
|
||||||
@ -128,6 +162,14 @@ pub const Surface = extern struct {
|
|||||||
/// Whether the mouse should be hidden or not as requested externally.
|
/// Whether the mouse should be hidden or not as requested externally.
|
||||||
mouse_hidden: bool = false,
|
mouse_hidden: bool = false,
|
||||||
|
|
||||||
|
/// The current working directory. This has to be reported externally,
|
||||||
|
/// usually by shell integration which then talks to libghostty
|
||||||
|
/// which triggers this property.
|
||||||
|
pwd: ?[:0]const u8 = null,
|
||||||
|
|
||||||
|
/// The title of this surface, if any has been set.
|
||||||
|
title: ?[:0]const u8 = null,
|
||||||
|
|
||||||
/// 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,
|
||||||
@ -821,6 +863,15 @@ pub const Surface = extern struct {
|
|||||||
priv.core_surface = null;
|
priv.core_surface = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (priv.pwd) |v| {
|
||||||
|
glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.pwd = null;
|
||||||
|
}
|
||||||
|
if (priv.title) |v| {
|
||||||
|
glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.title = null;
|
||||||
|
}
|
||||||
|
|
||||||
gobject.Object.virtual_methods.finalize.call(
|
gobject.Object.virtual_methods.finalize.call(
|
||||||
Class.parent,
|
Class.parent,
|
||||||
self.as(Parent),
|
self.as(Parent),
|
||||||
@ -830,6 +881,11 @@ pub const Surface = extern struct {
|
|||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
|
/// Returns the title property without a copy.
|
||||||
|
pub fn getTitle(self: *Self) ?[:0]const u8 {
|
||||||
|
return self.private().title;
|
||||||
|
}
|
||||||
|
|
||||||
fn propMouseHidden(
|
fn propMouseHidden(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
@ -1512,6 +1568,8 @@ pub const Surface = extern struct {
|
|||||||
properties.config.impl,
|
properties.config.impl,
|
||||||
properties.@"mouse-shape".impl,
|
properties.@"mouse-shape".impl,
|
||||||
properties.@"mouse-hidden".impl,
|
properties.@"mouse-hidden".impl,
|
||||||
|
properties.pwd.impl,
|
||||||
|
properties.title.impl,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
|
@ -248,7 +248,7 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
self.cursor_h.run(
|
self.cursor_h.run(
|
||||||
&self.loop,
|
&self.loop,
|
||||||
&self.cursor_c,
|
&self.cursor_c,
|
||||||
CURSOR_BLINK_INTERVAL,
|
cursorBlinkInterval(),
|
||||||
Thread,
|
Thread,
|
||||||
self,
|
self,
|
||||||
cursorTimerCallback,
|
cursorTimerCallback,
|
||||||
@ -408,7 +408,7 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
self.cursor_h.run(
|
self.cursor_h.run(
|
||||||
&self.loop,
|
&self.loop,
|
||||||
&self.cursor_c,
|
&self.cursor_c,
|
||||||
CURSOR_BLINK_INTERVAL,
|
cursorBlinkInterval(),
|
||||||
Thread,
|
Thread,
|
||||||
self,
|
self,
|
||||||
cursorTimerCallback,
|
cursorTimerCallback,
|
||||||
@ -424,7 +424,7 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
&self.loop,
|
&self.loop,
|
||||||
&self.cursor_c,
|
&self.cursor_c,
|
||||||
&self.cursor_c_cancel,
|
&self.cursor_c_cancel,
|
||||||
CURSOR_BLINK_INTERVAL,
|
cursorBlinkInterval(),
|
||||||
Thread,
|
Thread,
|
||||||
self,
|
self,
|
||||||
cursorTimerCallback,
|
cursorTimerCallback,
|
||||||
@ -641,7 +641,14 @@ fn cursorTimerCallback(
|
|||||||
t.flags.cursor_blink_visible = !t.flags.cursor_blink_visible;
|
t.flags.cursor_blink_visible = !t.flags.cursor_blink_visible;
|
||||||
t.wakeup.notify() catch {};
|
t.wakeup.notify() catch {};
|
||||||
|
|
||||||
t.cursor_h.run(&t.loop, &t.cursor_c, CURSOR_BLINK_INTERVAL, Thread, t, cursorTimerCallback);
|
t.cursor_h.run(
|
||||||
|
&t.loop,
|
||||||
|
&t.cursor_c,
|
||||||
|
cursorBlinkInterval(),
|
||||||
|
Thread,
|
||||||
|
t,
|
||||||
|
cursorTimerCallback,
|
||||||
|
);
|
||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -687,3 +694,19 @@ fn stopCallback(
|
|||||||
self_.?.loop.stop();
|
self_.?.loop.stop();
|
||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the interval for the blinking cursor in milliseconds.
|
||||||
|
fn cursorBlinkInterval() u64 {
|
||||||
|
if (std.valgrind.runningOnValgrind() > 0) {
|
||||||
|
// If we're running under Valgrind, the cursor blink adds enough
|
||||||
|
// churn that it makes some stalls annoying unless you're on a
|
||||||
|
// super powerful computer, so we delay it.
|
||||||
|
//
|
||||||
|
// This is a hack, we should change some of our cursor timer
|
||||||
|
// logic to be more efficient:
|
||||||
|
// https://github.com/ghostty-org/ghostty/issues/8003
|
||||||
|
return CURSOR_BLINK_INTERVAL * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CURSOR_BLINK_INTERVAL;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user