From e59c2a73d72853cb54f55eecc446f337c94cda24 Mon Sep 17 00:00:00 2001 From: novakane Date: Thu, 12 Aug 2021 16:16:23 +0200 Subject: [PATCH] river: implement xdg-activation-v1 - add a new "urgent" border color - add a new event to river-status-unstable-v1 Co-authored-by: Isaac Freund --- completions/bash/riverctl | 1 + completions/fish/riverctl.fish | 3 ++- completions/zsh/_riverctl | 1 + deps/zig-wlroots | 2 +- doc/riverctl.1.scd | 3 +++ protocol/river-status-unstable-v1.xml | 12 ++++++++++-- river/Config.zig | 3 +++ river/Output.zig | 12 ++++++++++++ river/OutputStatus.zig | 15 ++++++++++++++- river/Root.zig | 4 ++++ river/Seat.zig | 1 + river/Server.zig | 2 ++ river/StatusManager.zig | 2 +- river/View.zig | 26 ++++++++++++++++++++++++++ river/XdgToplevel.zig | 3 +++ river/command.zig | 1 + river/command/config.zig | 15 +++++++++++++++ river/render.zig | 6 +++++- 18 files changed, 105 insertions(+), 7 deletions(-) diff --git a/completions/bash/riverctl b/completions/bash/riverctl index 3475386..dd18ac5 100644 --- a/completions/bash/riverctl +++ b/completions/bash/riverctl @@ -39,6 +39,7 @@ function __riverctl_completion () background-color \ border-color-focused \ border-color-unfocused \ + border-color-urgent \ border-width \ focus-follows-cursor \ set-repeat \ diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish index 88be779..c0112c1 100644 --- a/completions/fish/riverctl.fish +++ b/completions/fish/riverctl.fish @@ -4,7 +4,7 @@ end function __fish_riverctl_complete_no_subcommand for i in (commandline -opc) - if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view input list-inputs list-input-configs move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom default-layout output-layout send-layout-cmd set-focused-tags set-view-tags toggle-focused-tags toggle-view-tags spawn-tagmask declare-mode enter-mode map map-pointer unmap unmap-pointer attach-mode background-color border-color-focused border-color-unfocused border-width focus-follows-cursor set-repeat set-cursor-warp xcursor-theme + if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view input list-inputs list-input-configs move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom default-layout output-layout send-layout-cmd set-focused-tags set-view-tags toggle-focused-tags toggle-view-tags spawn-tagmask declare-mode enter-mode map map-pointer unmap unmap-pointer attach-mode background-color border-color-focused border-color-unfocused border-color-urgent border-width focus-follows-cursor set-repeat set-cursor-warp xcursor-theme return 1 end end @@ -76,6 +76,7 @@ complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a attach-mo complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a background-color -d 'Set the background color' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a border-color-focused -d 'Set the border color of focused views' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a border-color-unfocused -d 'Set the border color of unfocused views' +complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a border-color-urgent -d 'Set the border color of urgent views' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a border-width -d 'Set the border width to pixels' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a focus-follows-cursor -d 'Configure the focus behavior when moving cursor' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-repeat -d 'Set the keyboard repeat rate and repeat delay' diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl index d782340..1c7034a 100644 --- a/completions/zsh/_riverctl +++ b/completions/zsh/_riverctl @@ -45,6 +45,7 @@ _riverctl_subcommands() 'background-color:Set the background color' 'border-color-focused:Set the border color of focused views' 'border-color-unfocused:Set the border color of unfocused views' + 'border-color-urgent:Set the border color of urgent views' 'border-width:Set the border width to pixels' 'focus-follows-cursor:Configure the focus behavior when moving cursor' 'set-repeat:Set the keyboard repeat rate and repeat delay' diff --git a/deps/zig-wlroots b/deps/zig-wlroots index 4c4e598..9bb6b03 160000 --- a/deps/zig-wlroots +++ b/deps/zig-wlroots @@ -1 +1 @@ -Subproject commit 4c4e598445a7c4143c5d3650d54c9ffa415119df +Subproject commit 9bb6b03f0ea04d4ea6a102ed3e45badba9e8e262 diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 52e14f2..ae6ac48 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -241,6 +241,9 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_ *border-color-unfocused* _0xRRGGBB_|_0xRRGGBBAA_ Set the border color of unfocused views. +*border-color-urgent* _0xRRGGBB_|_0xRRGGBBAA_ + Set the border color of urgent views. + *border-width* _pixels_ Set the border width to _pixels_. diff --git a/protocol/river-status-unstable-v1.xml b/protocol/river-status-unstable-v1.xml index e31da23..13affaa 100644 --- a/protocol/river-status-unstable-v1.xml +++ b/protocol/river-status-unstable-v1.xml @@ -16,7 +16,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - + A global factory for objects that receive status information specific to river. It could be used to implement, for example, a status bar. @@ -47,7 +47,7 @@ - + This interface allows clients to receive information about the current windowing state of an output. @@ -75,6 +75,14 @@ + + + + Sent once on binding the interface and again whenever the set of + tags with at least one urgent view changes. + + + diff --git a/river/Config.zig b/river/Config.zig index 39150f7..af80a19 100644 --- a/river/Config.zig +++ b/river/Config.zig @@ -48,6 +48,9 @@ border_color_focused: [4]f32 = [_]f32{ 0.57647059, 0.63137255, 0.63137255, 1.0 } /// Color of border of unfocused window in RGBA border_color_unfocused: [4]f32 = [_]f32{ 0.34509804, 0.43137255, 0.45882353, 1.0 }, // Solarized base01 +/// Color of border of urgent window in RGBA +border_color_urgent: [4]f32 = [_]f32{ 0.86274510, 0.19607843, 0.18431373, 1.0 }, // Solarized red + /// Map of keymap mode name to mode id mode_to_id: std.StringHashMap(usize), diff --git a/river/Output.zig b/river/Output.zig index c7d866f..2a6a1b3 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -155,6 +155,18 @@ pub fn sendViewTags(self: Self) void { while (it) |node| : (it = node.next) node.data.sendViewTags(); } +pub fn sendUrgentTags(self: Self) void { + var urgent_tags: u32 = 0; + + var view_it = self.views.first; + while (view_it) |node| : (view_it = node.next) { + if (node.view.current.urgent) urgent_tags |= node.view.current.tags; + } + + var it = self.status_trackers.first; + while (it) |node| : (it = node.next) node.data.sendUrgentTags(urgent_tags); +} + pub fn arrangeFilter(view: *View, filter_tags: u32) bool { return !view.destroying and !view.pending.float and !view.pending.fullscreen and view.pending.tags & filter_tags != 0; diff --git a/river/OutputStatus.zig b/river/OutputStatus.zig index 2000126..1f088f8 100644 --- a/river/OutputStatus.zig +++ b/river/OutputStatus.zig @@ -38,9 +38,16 @@ pub fn init(self: *Self, output: *Output, output_status: *zriver.OutputStatusV1) output_status.setHandler(*Self, handleRequest, handleDestroy, self); - // Send view/focused tags once on bind. + // Send view/focused/urgent tags once on bind. self.sendViewTags(); self.sendFocusedTags(output.current.tags); + + var urgent_tags: u32 = 0; + var view_it = self.output.views.first; + while (view_it) |node| : (view_it = node.next) { + if (node.view.current.urgent) urgent_tags |= node.view.current.tags; + } + self.sendUrgentTags(urgent_tags); } pub fn destroy(self: *Self) void { @@ -82,3 +89,9 @@ pub fn sendViewTags(self: Self) void { pub fn sendFocusedTags(self: Self, tags: u32) void { self.output_status.sendFocusedTags(tags); } + +pub fn sendUrgentTags(self: Self, tags: u32) void { + if (self.output_status.getVersion() >= 2) { + self.output_status.sendUrgentTags(tags); + } +} diff --git a/river/Root.zig b/river/Root.zig index 3af4ec1..bc6dbef 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -379,6 +379,7 @@ fn commitTransaction(self: *Self) void { output.current = output.pending; var view_tags_changed = false; + var urgent_tags_dirty = false; var view_it = output.views.first; while (view_it) |view_node| { @@ -395,12 +396,15 @@ fn commitTransaction(self: *Self) void { // Apply pending state of the view view.pending_serial = null; if (view.pending.tags != view.current.tags) view_tags_changed = true; + if (view.pending.urgent != view.current.urgent) urgent_tags_dirty = true; + if (view.pending.urgent and view_tags_changed) urgent_tags_dirty = true; view.current = view.pending; view.dropSavedBuffers(); } if (view_tags_changed) output.sendViewTags(); + if (urgent_tags_dirty) output.sendUrgentTags(); output.damage.addWhole(); } diff --git a/river/Seat.zig b/river/Seat.zig index 0a5db7d..5cee706 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -218,6 +218,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { std.debug.assert(self.focused_output == target_view.output); if (target_view.pending.focus == 0) target_view.setActivated(true); target_view.pending.focus += 1; + target_view.pending.urgent = false; }, .layer => |target_layer| std.debug.assert(self.focused_output == target_layer.output), .none => {}, diff --git a/river/Server.zig b/river/Server.zig index 6c0a5cc..ebe9096 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -59,6 +59,7 @@ xwayland: if (build_options.xwayland) *wlr.Xwayland else void, new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void, foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1, +xdg_activation: *wlr.XdgActivationV1, decoration_manager: DecorationManager, input_manager: InputManager, @@ -109,6 +110,7 @@ pub fn init(self: *Self) !void { } self.foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(self.wl_server); + self.xdg_activation = try wlr.XdgActivationV1.create(self.wl_server); _ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server); diff --git a/river/StatusManager.zig b/river/StatusManager.zig index 245cef2..f8ea03e 100644 --- a/river/StatusManager.zig +++ b/river/StatusManager.zig @@ -40,7 +40,7 @@ server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleSer pub fn init(self: *Self) !void { self.* = .{ - .global = try wl.Global.create(server.wl_server, zriver.StatusManagerV1, 1, *Self, self, bind), + .global = try wl.Global.create(server.wl_server, zriver.StatusManagerV1, 2, *Self, self, bind), }; server.wl_server.addDestroyListener(&self.server_destroy); diff --git a/river/View.zig b/river/View.zig index c3148b2..86863d1 100644 --- a/river/View.zig +++ b/river/View.zig @@ -71,6 +71,7 @@ const State = struct { float: bool = false, fullscreen: bool = false, + urgent: bool = false, }; const SavedBuffer = struct { @@ -130,6 +131,9 @@ foreign_fullscreen: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen) = foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) = wl.Listener(*wlr.ForeignToplevelHandleV1).init(handleForeignClose), +request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) = + wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate), + pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void { self.* = .{ .output = output, @@ -164,6 +168,8 @@ pub fn destroy(self: *Self) void { .xwayland_view => |*xwayland_view| xwayland_view.deinit(), } + self.request_activate.link.remove(); + const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); self.output.views.remove(node); util.gpa.destroy(node); @@ -277,6 +283,11 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void { self.output.sendViewTags(); destination_output.sendViewTags(); + if (self.pending.urgent) { + self.output.sendUrgentTags(); + destination_output.sendUrgentTags(); + } + if (self.surface) |surface| { surface.sendLeave(self.output.wlr_output); surface.sendEnter(destination_output.wlr_output); @@ -446,6 +457,8 @@ pub fn map(self: *Self) !void { handle.outputEnter(self.output.wlr_output); } + server.xdg_activation.events.request_activate.add(&self.request_activate); + // Add the view to the stack of its output const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); self.output.views.attach(node, server.config.attach_mode); @@ -535,3 +548,16 @@ fn handleForeignClose( const self = @fieldParentPtr(Self, "foreign_close", listener); self.close(); } + +fn handleRequestActivate( + listener: *wl.Listener(*wlr.XdgActivationV1.event.RequestActivate), + event: *wlr.XdgActivationV1.event.RequestActivate, +) void { + const self = @fieldParentPtr(Self, "request_activate", listener); + if (fromWlrSurface(event.surface)) |view| { + if (view.current.focus == 0) { + view.pending.urgent = true; + server.root.startTransaction(); + } + } +} diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index 454ce77..dfdf188 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -275,8 +275,11 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) voi server.root.notifyConfigured(); } else { const self_tags_changed = view.pending.tags != view.current.tags; + const urgent_tags_dirty = view.pending.urgent != view.current.urgent or + (view.pending.urgent and self_tags_changed); view.current = view.pending; if (self_tags_changed) view.output.sendViewTags(); + if (urgent_tags_dirty) view.output.sendUrgentTags(); // This is necessary if this view was part of a transaction that didn't get completed // before some change occured that caused shouldTrackConfigure() to return false. diff --git a/river/command.zig b/river/command.zig index d32101e..9db3995 100644 --- a/river/command.zig +++ b/river/command.zig @@ -46,6 +46,7 @@ const str_to_impl_fn = [_]struct { .{ .name = "background-color", .impl = @import("command/config.zig").backgroundColor }, .{ .name = "border-color-focused", .impl = @import("command/config.zig").borderColorFocused }, .{ .name = "border-color-unfocused", .impl = @import("command/config.zig").borderColorUnfocused }, + .{ .name = "border-color-urgent", .impl = @import("command/config.zig").borderColorUrgent }, .{ .name = "border-width", .impl = @import("command/config.zig").borderWidth }, .{ .name = "close", .impl = @import("command/close.zig").close }, .{ .name = "csd-filter-add", .impl = @import("command/filter.zig").csdFilterAdd }, diff --git a/river/command/config.zig b/river/command/config.zig index 9161c7d..f2046ce 100644 --- a/river/command/config.zig +++ b/river/command/config.zig @@ -83,6 +83,21 @@ pub fn borderColorUnfocused( while (it) |node| : (it = node.next) node.data.damage.addWhole(); } +pub fn borderColorUrgent( + allocator: *std.mem.Allocator, + seat: *Seat, + args: []const [:0]const u8, + out: *?[]const u8, +) Error!void { + if (args.len < 2) return Error.NotEnoughArguments; + if (args.len > 2) return Error.TooManyArguments; + + server.config.border_color_urgent = try parseRgba(args[1]); + + var it = server.root.outputs.first; + while (it) |node| : (it = node.next) node.data.damage.addWhole(); +} + pub fn setCursorWarp( allocator: *std.mem.Allocator, seat: *Seat, diff --git a/river/render.zig b/river/render.zig index 9c0bd52..4192d14 100644 --- a/river/render.zig +++ b/river/render.zig @@ -311,7 +311,11 @@ fn renderTexture( fn renderBorders(output: *const Output, view: *View, now: *os.timespec) void { const config = &server.config; - const color = if (view.current.focus != 0) &config.border_color_focused else &config.border_color_unfocused; + const color = blk: { + if (view.current.urgent) break :blk &config.border_color_urgent; + if (view.current.focus != 0) break :blk &config.border_color_focused; + break :blk &config.border_color_unfocused; + }; const border_width = config.border_width; const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box;