git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit689f2e04ba32a5c89d5cda7a036705f09c7ef857
parent3031556951
authorLeon Henrik Plickat <[email protected]>
date2021-02-01 14:16
output: add output_title default option

Outputs now have a default option, "output_title". If this changes, the
outputs title is set to the option value. This title is currently only
relevant when run nested in a wayland/X11 session.

Co-authored-by: Isaac Freund <[email protected]>

 deps/zig-wlroots   |  2 +-
 doc/riverctl.1.scd |  8 +++++++-
 river/Option.zig   |  7 +++++++
 river/Output.zig   | 36 ++++++++++++++++++++++++++++++++++++
 river/Server.zig   |  2 +-
 5 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/deps/zig-wlroots b/deps/zig-wlroots
index 4db6b92..5bc7fcf 160000
--- a/deps/zig-wlroots
+++ b/deps/zig-wlroots
@@ -1 +1 @@
-Subproject commit 4db6b92146f22054b9a9363d0999cf3fb359a4fb
+Subproject commit 5bc7fcf94e1d488a8e2316ff7ebdd0ca193d0309
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index 05eafdf..3424ea4 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -280,7 +280,7 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
 	and is made available through the _XCURSOR_THEME_ and _XCURSOR_SIZE_
 	environment variables.
 
-# OPTIONS
+## OPTIONS
 
 River has various options that are saved in a typed key-value store. It also
 allows users to store arbitrary custom options in the store. Options are
@@ -299,6 +299,12 @@ name of the output as obtained from the xdg-output protocol.
 *set-option* [-output _output_name_] _name_ _value_
 	Set the value of the specified option to _value_.
 
+River declares certain default options for all outputs.
+
+*output_title* (string)
+	Changing this option changes the title of the wayland and X11 backend
+	outputs.
+
 # EXAMPLES
 
 Bind bemenu-run to Super+P in normal mode:
diff --git a/river/Option.zig b/river/Option.zig
index a6366bc..db9a731 100644
--- a/river/Option.zig
+++ b/river/Option.zig
@@ -45,6 +45,9 @@ output: ?*Output,
 key: [*:0]const u8,
 value: Value = .unset,
 
+/// Emitted whenever the value of the option changes.
+update: wl.Signal(*Self) = undefined,
+
 handles: wl.list.Head(zriver.OptionHandleV1, null) = undefined,
 
 pub fn create(options_manager: *OptionsManager, output: ?*Output, key: [*:0]const u8) !*Self {
@@ -57,6 +60,7 @@ pub fn create(options_manager: *OptionsManager, output: ?*Output, key: [*:0]cons
         .key = try util.gpa.dupeZ(u8, mem.span(key)),
     };
     self.handles.init();
+    self.update.init();
 
     options_manager.options.append(self);
 
@@ -101,6 +105,9 @@ pub fn set(self: *Self, value: Value) !void {
 
     var it = self.handles.iterator(.forward);
     while (it.next()) |handle| self.sendValue(handle);
+
+    // Call listeners, if any.
+    self.update.emit(self);
 }
 
 fn sendValue(self: Self, handle: *zriver.OptionHandleV1) void {
diff --git a/river/Output.zig b/river/Output.zig
index 1b599ef..bbedcc6 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -18,6 +18,8 @@
 const Self = @This();
 
 const std = @import("std");
+const mem = std.mem;
+const fmt = std.fmt;
 const wlr = @import("wlroots");
 const wayland = @import("wayland");
 const wl = wayland.server.wl;
@@ -35,6 +37,7 @@ const View = @import("View.zig");
 const ViewStack = @import("view_stack.zig").ViewStack;
 const AttachMode = @import("view_stack.zig").AttachMode;
 const OutputStatus = @import("OutputStatus.zig");
+const Option = @import("Option.zig");
 
 const State = struct {
     /// A bit field of focused tags
@@ -84,6 +87,9 @@ enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable),
 frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
 mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode),
 
+// Listeners for options
+output_title: wl.Listener(*Option) = wl.Listener(*Option).init(handleTitleChange),
+
 pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
     // Some backends don't have modes. DRM+KMS does, and we need to set a mode
     // before we can use the output. The mode is a tuple of (width, height,
@@ -138,6 +144,10 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
             .height = effective_resolution.height,
         };
     }
+
+    var buf: ["river - ".len + wlr_output.name.len + 1]u8 = undefined;
+    const default_title = fmt.bufPrintZ(&buf, "river - {}", .{mem.spanZ(&wlr_output.name)}) catch unreachable;
+    try self.defaultOption("output_title", .{ .string = default_title.ptr }, &self.output_title);
 }
 
 pub fn getLayer(self: *Self, layer: zwlr.LayerShellV1.Layer) *std.TailQueue(LayerSurface) {
@@ -545,3 +555,29 @@ pub fn getEffectiveResolution(self: *Self) struct { width: u32, height: u32 } {
         .height = @intCast(u32, height),
     };
 }
+
+pub fn setTitle(self: *Self, title: [*:0]const u8) void {
+    if (self.wlr_output.isWl()) {
+        self.wlr_output.wlSetTitle(title);
+    } else if (self.wlr_output.isX11()) {
+        self.wlr_output.x11SetTitle(title);
+    }
+}
+
+/// Create an option for this output, attach a listener which is called when
+/// the option changed and initialize with a default value. Note that the
+/// listener is called once through this function.
+fn defaultOption(
+    self: *Self,
+    key: [*:0]const u8,
+    value: Option.Value,
+    listener: *wl.Listener(*Option),
+) !void {
+    const option = try Option.create(&self.root.server.options_manager, self, key);
+    option.update.add(listener);
+    try option.set(value);
+}
+
+fn handleTitleChange(listener: *wl.Listener(*Option), option: *Option) void {
+    if (option.value.string) |title| option.output.?.setTitle(title);
+}
diff --git a/river/Server.zig b/river/Server.zig
index 7923065..5ea699b 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -112,12 +112,12 @@ pub fn init(self: *Self) !void {
 
     self.config = try Config.init();
     try self.decoration_manager.init(self);
+    try self.options_manager.init(self);
     try self.root.init(self);
     // Must be called after root is initialized
     try self.input_manager.init(self);
     try self.control.init(self);
     try self.status_manager.init(self);
-    try self.options_manager.init(self);
 
     // These all free themselves when the wl_server is destroyed
     _ = try wlr.DataDeviceManager.create(self.wl_server);