Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GTK: Fix clicking on desktop notifications #2163

Merged
merged 3 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,8 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.renderer_health => |health| self.updateRendererHealth(health),

.report_color_scheme => try self.reportColorScheme(),

.present_surface => try self.presentSurface(),
}
}

Expand Down Expand Up @@ -4158,6 +4160,14 @@ fn crashThreadState(self: *Surface) crash.sentry.ThreadState {
};
}

/// Tell the surface to present itself to the user. This may involve raising the
/// window and switching tabs.
fn presentSurface(self: *Surface) !void {
if (@hasDecl(apprt.Surface, "presentSurface")) {
self.rt_surface.presentSurface();
} else log.warn("runtime doesn't support presentSurface", .{});
}

pub const face_ttf = @embedFile("font/res/JetBrainsMono-Regular.ttf");
pub const face_bold_ttf = @embedFile("font/res/JetBrainsMono-Bold.ttf");
pub const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");
Expand Down
57 changes: 49 additions & 8 deletions src/apprt/gtk/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,8 @@ fn updateConfigErrors(self: *App) !void {

fn syncActionAccelerators(self: *App) !void {
try self.syncActionAccelerator("app.quit", .{ .quit = {} });
try self.syncActionAccelerator("app.open_config", .{ .open_config = {} });
try self.syncActionAccelerator("app.reload_config", .{ .reload_config = {} });
try self.syncActionAccelerator("app.open-config", .{ .open_config = {} });
try self.syncActionAccelerator("app.reload-config", .{ .reload_config = {} });
try self.syncActionAccelerator("win.toggle_inspector", .{ .inspector = .toggle });
try self.syncActionAccelerator("win.close", .{ .close_surface = {} });
try self.syncActionAccelerator("win.new_window", .{ .new_window = {} });
Expand Down Expand Up @@ -825,17 +825,58 @@ fn gtkActionQuit(
};
}

/// Action sent by the window manager asking us to present a specific surface to
/// the user. Usually because the user clicked on a desktop notification.
fn gtkActionPresentSurface(
_: *c.GSimpleAction,
parameter: *c.GVariant,
ud: ?*anyopaque,
) callconv(.C) void {
const self: *App = @ptrCast(@alignCast(ud orelse return));

// Make sure that we've receiived a u64 from the system.
if (c.g_variant_is_of_type(parameter, c.G_VARIANT_TYPE("t")) == 0) {
return;
}

// Convert that u64 to pointer to a core surface.
const surface: *CoreSurface = @ptrFromInt(c.g_variant_get_uint64(parameter));

// Send a message through the core app mailbox rather than presenting the
// surface directly so that it can validate that the surface pointer is
// valid. We could get an invalid pointer if a desktop notification outlives
// a Ghostty instance and a new one starts up, or there are multiple Ghostty
// instances running.
_ = self.core_app.mailbox.push(
.{
.surface_message = .{
.surface = surface,
.message = .{ .present_surface = {} },
},
},
.{ .forever = {} },
);
}

/// This is called to setup the action map that this application supports.
/// This should be called only once on startup.
fn initActions(self: *App) void {
// The set of actions. Each action has (in order):
// [0] The action name
// [1] The callback function
// [2] The GVariantType of the parameter
//
// For action names:
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
const actions = .{
.{ "quit", &gtkActionQuit },
.{ "open_config", &gtkActionOpenConfig },
.{ "reload_config", &gtkActionReloadConfig },
.{ "quit", &gtkActionQuit, null },
.{ "open-config", &gtkActionOpenConfig, null },
.{ "reload-config", &gtkActionReloadConfig, null },
.{ "present-surface", &gtkActionPresentSurface, c.G_VARIANT_TYPE("t") },
};

inline for (actions) |entry| {
const action = c.g_simple_action_new(entry[0], null);
const action = c.g_simple_action_new(entry[0], entry[2]);
defer c.g_object_unref(action);
_ = c.g_signal_connect_data(
action,
Expand Down Expand Up @@ -871,8 +912,8 @@ fn initMenu(self: *App) void {
defer c.g_object_unref(section);
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector");
c.g_menu_append(section, "Open Configuration", "app.open_config");
c.g_menu_append(section, "Reload Configuration", "app.reload_config");
c.g_menu_append(section, "Open Configuration", "app.open-config");
c.g_menu_append(section, "Reload Configuration", "app.reload-config");
c.g_menu_append(section, "About Ghostty", "win.about");
}

Expand Down
28 changes: 23 additions & 5 deletions src/apprt/gtk/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1144,19 +1144,26 @@ pub fn showDesktopNotification(
else => title,
};

const notif = c.g_notification_new(t.ptr);
defer c.g_object_unref(notif);
c.g_notification_set_body(notif, body.ptr);
const notification = c.g_notification_new(t.ptr);
defer c.g_object_unref(notification);
c.g_notification_set_body(notification, body.ptr);

const icon = c.g_themed_icon_new("com.mitchellh.ghostty");
defer c.g_object_unref(icon);
c.g_notification_set_icon(notif, icon);
c.g_notification_set_icon(notification, icon);

const pointer = c.g_variant_new_uint64(@intFromPtr(&self.core_surface));
c.g_notification_set_default_action_and_target_value(
notification,
"app.present-surface",
pointer,
);

const g_app: *c.GApplication = @ptrCast(self.app.app);

// We set the notification ID to the body content. If the content is the
// same, this notification may replace a previous notification
c.g_application_send_notification(g_app, body.ptr, notif);
c.g_application_send_notification(g_app, body.ptr, notification);
}

fn showContextMenu(self: *Surface, x: f32, y: f32) void {
Expand Down Expand Up @@ -1967,3 +1974,14 @@ fn translateMods(state: c.GdkModifierType) input.Mods {
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
return mods;
}

pub fn presentSurface(self: *Surface) void {
if (self.container.window()) |window| {
if (self.container.tab()) |tab| {
if (window.notebook.getTabPosition(tab)) |position|
window.notebook.gotoNthTab(position);
}
c.gtk_window_present(window.window);
}
self.grabFocus();
}
4 changes: 4 additions & 0 deletions src/apprt/surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ pub const Message = union(enum) {
/// Report the color scheme
report_color_scheme: void,

/// Tell the surface to present itself to the user. This may require raising
/// a window and switching tabs.
present_surface: void,

pub const ReportTitleStyle = enum {
csi_21_t,

Expand Down
Loading