apprt/gtk: subscribe to AdwStyleManager::dark for ColorScheme (#6007)

This commit is contained in:
Leah Amelia Chen
2025-02-26 20:38:50 +01:00
committed by GitHub

View File

@ -14,6 +14,7 @@ const gtk = @import("gtk");
const gio = @import("gio"); const gio = @import("gio");
const glib = @import("glib"); const glib = @import("glib");
const gobject = @import("gobject"); const gobject = @import("gobject");
const adw = @import("adw");
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
@ -1256,9 +1257,22 @@ pub fn run(self: *App) !void {
self.transient_cgroup_base = path; self.transient_cgroup_base = path;
} else log.debug("cgroup isolation disabled config={}", .{self.config.@"linux-cgroup"}); } else log.debug("cgroup isolation disabled config={}", .{self.config.@"linux-cgroup"});
// Setup our D-Bus connection for listening to settings changes, // Setup color scheme notifications
// and asynchronously request the initial color scheme const adw_app: *adw.Application = @ptrCast(@alignCast(self.app));
self.initDbus(); const style_manager: *adw.StyleManager = adw_app.getStyleManager();
_ = gobject.Object.signals.notify.connect(
style_manager,
*App,
adwNotifyDark,
self,
.{
.detail = "dark",
},
);
// Make an initial request to set up the color scheme
const light = style_manager.getDark() == 0;
self.colorSchemeEvent(if (light) .light else .dark);
// Setup our actions // Setup our actions
self.initActions(); self.initActions();
@ -1292,57 +1306,6 @@ pub fn run(self: *App) !void {
} }
} }
/// Subscribe to various DBus signals and get information from the FreeDesktop
/// portals by calling various DBus methods
fn initDbus(self: *App) void {
// FIXME: when self.app is converted to zig-gobject
const app: *gio.Application = @ptrCast(@alignCast(self.app));
const dbus = app.getDbusConnection() orelse {
log.warn("unable to get dbus connection, not setting up events", .{});
return;
};
// Listen for light/dark color scheme changes.
_ = dbus.signalSubscribe(
null,
"org.freedesktop.portal.Settings",
"SettingChanged",
"/org/freedesktop/portal/desktop",
"org.freedesktop.appearance",
.{ .match_arg0_namespace = true },
gtkNotifyColorScheme,
self,
null,
);
// Request the initial color scheme asynchronously.
{
const parameters = glib.Variant.new(
"(ss)",
"org.freedesktop.appearance",
"color-scheme",
);
defer glib.free(parameters);
const reply_type = glib.VariantType.new("(v)");
defer glib.free(reply_type);
dbus.call(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"ReadOne",
parameters,
reply_type,
.{},
-1,
null,
dbusColorSchemeCallbackReadOne,
self,
);
}
}
// This timeout function is started when no surfaces are open. It can be // This timeout function is started when no surfaces are open. It can be
// cancelled if a new surface is opened before the timer expires. // cancelled if a new surface is opened before the timer expires.
pub fn gtkQuitTimerExpired(ud: ?*anyopaque) callconv(.C) c.gboolean { pub fn gtkQuitTimerExpired(ud: ?*anyopaque) callconv(.C) c.gboolean {
@ -1578,163 +1541,15 @@ fn gtkWindowIsActive(
core_app.focusEvent(false); core_app.focusEvent(false);
} }
fn dbusColorSchemeCallbackReadOne( fn adwNotifyDark(
source_object: ?*gobject.Object, style_manager: *adw.StyleManager,
result: *gio.AsyncResult, _: *gobject.ParamSpec,
ud: ?*anyopaque, self: *App,
) callconv(.C) void { ) callconv(.C) void {
const self: *App = @ptrCast(@alignCast(ud.?)); const color_scheme: apprt.ColorScheme = if (style_manager.getDark() == 0)
const dbus = gobject.ext.cast(gio.DBusConnection, source_object.?).?; .light
if (dbusColorSchemeFinish(dbus, result)) |scheme| {
self.colorSchemeEvent(scheme);
return;
}
// if we got null, we should retry with the older `Read` method
const parameters = glib.Variant.new(
"(ss)",
"org.freedesktop.appearance",
"color-scheme",
);
defer glib.free(parameters);
const reply_type = glib.VariantType.new("(v)");
defer glib.free(reply_type);
// Request the initial color scheme asynchronously.
dbus.call(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"Read",
parameters,
reply_type,
.{},
-1,
null,
dbusColorSchemeCallbackRead,
self,
);
}
fn dbusColorSchemeCallbackRead(
source_object: ?*gobject.Object,
result: *gio.AsyncResult,
ud: ?*anyopaque,
) callconv(.C) void {
const self: *App = @ptrCast(@alignCast(ud.?));
const dbus = gobject.ext.cast(gio.DBusConnection, source_object.?).?;
if (dbusColorSchemeFinish(dbus, result)) |scheme| {
self.colorSchemeEvent(scheme);
return;
}
self.colorSchemeEvent(.light);
}
fn dbusColorSchemeFinish(dbus: *gio.DBusConnection, result: *gio.AsyncResult) ?apprt.ColorScheme {
var err: ?*glib.Error = null;
defer if (err) |e| e.free();
const value = dbus.callFinish(result, &err) orelse {
const e = err orelse {
log.warn("dbus call failed but no error?", .{});
return .light;
};
// If `ReadOne` is not yet implemented, fall back to deprecated `Read` method
// Error code: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method ReadOne
if (e.f_code == 19) return null;
log.warn("unable to get current color scheme: {s}", .{
if (e.f_message) |msg|
msg
else
"(unknown error)",
});
return .light;
};
defer value.unref();
const value_type = glib.VariantType.new("(v)");
defer glib.free(value_type);
if (value.isOfType(value_type) == 0) {
return .light;
}
var tmp: ?*glib.Variant = null;
value.get("(v)", &tmp);
const inner = tmp orelse return .light;
defer inner.unref();
const inner_type = glib.VariantType.new("u");
defer glib.free(inner_type);
if (inner.isOfType(inner_type) == 0) return .light;
return switch (inner.getUint32()) {
1 => .dark,
else => .light,
};
}
/// This will be called by D-Bus when the style changes between light & dark.
fn gtkNotifyColorScheme(
_: *gio.DBusConnection,
_: ?[*:0]const u8,
_: [*:0]const u8,
_: [*:0]const u8,
_: [*:0]const u8,
parameters: *glib.Variant,
user_data: ?*anyopaque,
) callconv(.C) void {
const self: *App = @ptrCast(@alignCast(user_data orelse {
log.err("style change notification: userdata is null", .{});
return;
}));
{
const variant_type = glib.VariantType.new("(ssv)");
defer glib.free(variant_type);
if (parameters.isOfType(variant_type) == 0) {
log.err("unexpected parameter type: {s}", .{parameters.getTypeString()});
return;
}
}
var namespace: [*c]u8 = undefined;
var setting: [*c]u8 = undefined;
var value: *glib.Variant = undefined;
parameters.get("(ssv)", &namespace, &setting, &value);
defer {
glib.free(namespace);
glib.free(setting);
value.unref();
}
// ignore any setting changes that we aren't interested in
if (std.mem.orderZ(u8, "org.freedesktop.appearance", namespace) != .eq) return;
if (std.mem.orderZ(u8, "color-scheme", setting) != .eq) return;
{
const variant_type = glib.VariantType.new("u");
defer glib.free(variant_type);
if (value.isOfType(variant_type) == 0) {
log.err("unexpected value type: {s}", .{value.getTypeString()});
return;
}
}
const color_scheme: apprt.ColorScheme = if (value.getUint32() == 1)
.dark
else else
.light; .dark;
self.colorSchemeEvent(color_scheme); self.colorSchemeEvent(color_scheme);
} }