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 <ifreund@ifreund.xyz>
This commit is contained in:
novakane 2021-08-12 16:16:23 +02:00 committed by Isaac Freund
parent e9bfc5251e
commit e59c2a73d7
18 changed files with 105 additions and 7 deletions

View file

@ -39,6 +39,7 @@ function __riverctl_completion ()
background-color \ background-color \
border-color-focused \ border-color-focused \
border-color-unfocused \ border-color-unfocused \
border-color-urgent \
border-width \ border-width \
focus-follows-cursor \ focus-follows-cursor \
set-repeat \ set-repeat \

View file

@ -4,7 +4,7 @@ end
function __fish_riverctl_complete_no_subcommand function __fish_riverctl_complete_no_subcommand
for i in (commandline -opc) 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 return 1
end end
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 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-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-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 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 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' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-repeat -d 'Set the keyboard repeat rate and repeat delay'

View file

@ -45,6 +45,7 @@ _riverctl_subcommands()
'background-color:Set the background color' 'background-color:Set the background color'
'border-color-focused:Set the border color of focused views' 'border-color-focused:Set the border color of focused views'
'border-color-unfocused:Set the border color of unfocused 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' 'border-width:Set the border width to pixels'
'focus-follows-cursor:Configure the focus behavior when moving cursor' 'focus-follows-cursor:Configure the focus behavior when moving cursor'
'set-repeat:Set the keyboard repeat rate and repeat delay' 'set-repeat:Set the keyboard repeat rate and repeat delay'

2
deps/zig-wlroots vendored

@ -1 +1 @@
Subproject commit 4c4e598445a7c4143c5d3650d54c9ffa415119df Subproject commit 9bb6b03f0ea04d4ea6a102ed3e45badba9e8e262

View file

@ -241,6 +241,9 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
*border-color-unfocused* _0xRRGGBB_|_0xRRGGBBAA_ *border-color-unfocused* _0xRRGGBB_|_0xRRGGBBAA_
Set the border color of unfocused views. Set the border color of unfocused views.
*border-color-urgent* _0xRRGGBB_|_0xRRGGBBAA_
Set the border color of urgent views.
*border-width* _pixels_ *border-width* _pixels_
Set the border width to _pixels_. Set the border width to _pixels_.

View file

@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright> </copyright>
<interface name="zriver_status_manager_v1" version="1"> <interface name="zriver_status_manager_v1" version="2">
<description summary="manage river status objects"> <description summary="manage river status objects">
A global factory for objects that receive status information specific A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar. to river. It could be used to implement, for example, a status bar.
@ -47,7 +47,7 @@
</request> </request>
</interface> </interface>
<interface name="zriver_output_status_v1" version="1"> <interface name="zriver_output_status_v1" version="2">
<description summary="track output tags and focus"> <description summary="track output tags and focus">
This interface allows clients to receive information about the current This interface allows clients to receive information about the current
windowing state of an output. windowing state of an output.
@ -75,6 +75,14 @@
</description> </description>
<arg name="tags" type="array" summary="array of 32-bit bitfields"/> <arg name="tags" type="array" summary="array of 32-bit bitfields"/>
</event> </event>
<event name="urgent_tags" since="2">
<description summary="tags of the output with an urgent view">
Sent once on binding the interface and again whenever the set of
tags with at least one urgent view changes.
</description>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
</interface> </interface>
<interface name="zriver_seat_status_v1" version="1"> <interface name="zriver_seat_status_v1" version="1">

View file

@ -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 /// Color of border of unfocused window in RGBA
border_color_unfocused: [4]f32 = [_]f32{ 0.34509804, 0.43137255, 0.45882353, 1.0 }, // Solarized base01 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 /// Map of keymap mode name to mode id
mode_to_id: std.StringHashMap(usize), mode_to_id: std.StringHashMap(usize),

View file

@ -155,6 +155,18 @@ pub fn sendViewTags(self: Self) void {
while (it) |node| : (it = node.next) node.data.sendViewTags(); 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 { pub fn arrangeFilter(view: *View, filter_tags: u32) bool {
return !view.destroying and !view.pending.float and !view.pending.fullscreen and return !view.destroying and !view.pending.float and !view.pending.fullscreen and
view.pending.tags & filter_tags != 0; view.pending.tags & filter_tags != 0;

View file

@ -38,9 +38,16 @@ pub fn init(self: *Self, output: *Output, output_status: *zriver.OutputStatusV1)
output_status.setHandler(*Self, handleRequest, handleDestroy, self); 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.sendViewTags();
self.sendFocusedTags(output.current.tags); 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 { pub fn destroy(self: *Self) void {
@ -82,3 +89,9 @@ pub fn sendViewTags(self: Self) void {
pub fn sendFocusedTags(self: Self, tags: u32) void { pub fn sendFocusedTags(self: Self, tags: u32) void {
self.output_status.sendFocusedTags(tags); self.output_status.sendFocusedTags(tags);
} }
pub fn sendUrgentTags(self: Self, tags: u32) void {
if (self.output_status.getVersion() >= 2) {
self.output_status.sendUrgentTags(tags);
}
}

View file

@ -379,6 +379,7 @@ fn commitTransaction(self: *Self) void {
output.current = output.pending; output.current = output.pending;
var view_tags_changed = false; var view_tags_changed = false;
var urgent_tags_dirty = false;
var view_it = output.views.first; var view_it = output.views.first;
while (view_it) |view_node| { while (view_it) |view_node| {
@ -395,12 +396,15 @@ fn commitTransaction(self: *Self) void {
// Apply pending state of the view // Apply pending state of the view
view.pending_serial = null; view.pending_serial = null;
if (view.pending.tags != view.current.tags) view_tags_changed = true; 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.current = view.pending;
view.dropSavedBuffers(); view.dropSavedBuffers();
} }
if (view_tags_changed) output.sendViewTags(); if (view_tags_changed) output.sendViewTags();
if (urgent_tags_dirty) output.sendUrgentTags();
output.damage.addWhole(); output.damage.addWhole();
} }

View file

@ -218,6 +218,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
std.debug.assert(self.focused_output == target_view.output); std.debug.assert(self.focused_output == target_view.output);
if (target_view.pending.focus == 0) target_view.setActivated(true); if (target_view.pending.focus == 0) target_view.setActivated(true);
target_view.pending.focus += 1; target_view.pending.focus += 1;
target_view.pending.urgent = false;
}, },
.layer => |target_layer| std.debug.assert(self.focused_output == target_layer.output), .layer => |target_layer| std.debug.assert(self.focused_output == target_layer.output),
.none => {}, .none => {},

View file

@ -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, new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void,
foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1, foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
xdg_activation: *wlr.XdgActivationV1,
decoration_manager: DecorationManager, decoration_manager: DecorationManager,
input_manager: InputManager, 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.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); _ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server);

View file

@ -40,7 +40,7 @@ server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleSer
pub fn init(self: *Self) !void { pub fn init(self: *Self) !void {
self.* = .{ 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); server.wl_server.addDestroyListener(&self.server_destroy);

View file

@ -71,6 +71,7 @@ const State = struct {
float: bool = false, float: bool = false,
fullscreen: bool = false, fullscreen: bool = false,
urgent: bool = false,
}; };
const SavedBuffer = struct { const SavedBuffer = struct {
@ -130,6 +131,9 @@ foreign_fullscreen: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen) =
foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) = foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) =
wl.Listener(*wlr.ForeignToplevelHandleV1).init(handleForeignClose), 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 { pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
self.* = .{ self.* = .{
.output = output, .output = output,
@ -164,6 +168,8 @@ pub fn destroy(self: *Self) void {
.xwayland_view => |*xwayland_view| xwayland_view.deinit(), .xwayland_view => |*xwayland_view| xwayland_view.deinit(),
} }
self.request_activate.link.remove();
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
self.output.views.remove(node); self.output.views.remove(node);
util.gpa.destroy(node); util.gpa.destroy(node);
@ -277,6 +283,11 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void {
self.output.sendViewTags(); self.output.sendViewTags();
destination_output.sendViewTags(); destination_output.sendViewTags();
if (self.pending.urgent) {
self.output.sendUrgentTags();
destination_output.sendUrgentTags();
}
if (self.surface) |surface| { if (self.surface) |surface| {
surface.sendLeave(self.output.wlr_output); surface.sendLeave(self.output.wlr_output);
surface.sendEnter(destination_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); 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 // Add the view to the stack of its output
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self); const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
self.output.views.attach(node, server.config.attach_mode); self.output.views.attach(node, server.config.attach_mode);
@ -535,3 +548,16 @@ fn handleForeignClose(
const self = @fieldParentPtr(Self, "foreign_close", listener); const self = @fieldParentPtr(Self, "foreign_close", listener);
self.close(); 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();
}
}
}

View file

@ -275,8 +275,11 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), surface: *wlr.Surface) voi
server.root.notifyConfigured(); server.root.notifyConfigured();
} else { } else {
const self_tags_changed = view.pending.tags != view.current.tags; 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; view.current = view.pending;
if (self_tags_changed) view.output.sendViewTags(); 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 // 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. // before some change occured that caused shouldTrackConfigure() to return false.

View file

@ -46,6 +46,7 @@ const str_to_impl_fn = [_]struct {
.{ .name = "background-color", .impl = @import("command/config.zig").backgroundColor }, .{ .name = "background-color", .impl = @import("command/config.zig").backgroundColor },
.{ .name = "border-color-focused", .impl = @import("command/config.zig").borderColorFocused }, .{ .name = "border-color-focused", .impl = @import("command/config.zig").borderColorFocused },
.{ .name = "border-color-unfocused", .impl = @import("command/config.zig").borderColorUnfocused }, .{ .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 = "border-width", .impl = @import("command/config.zig").borderWidth },
.{ .name = "close", .impl = @import("command/close.zig").close }, .{ .name = "close", .impl = @import("command/close.zig").close },
.{ .name = "csd-filter-add", .impl = @import("command/filter.zig").csdFilterAdd }, .{ .name = "csd-filter-add", .impl = @import("command/filter.zig").csdFilterAdd },

View file

@ -83,6 +83,21 @@ pub fn borderColorUnfocused(
while (it) |node| : (it = node.next) node.data.damage.addWhole(); 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( pub fn setCursorWarp(
allocator: *std.mem.Allocator, allocator: *std.mem.Allocator,
seat: *Seat, seat: *Seat,

View file

@ -311,7 +311,11 @@ fn renderTexture(
fn renderBorders(output: *const Output, view: *View, now: *os.timespec) void { fn renderBorders(output: *const Output, view: *View, now: *os.timespec) void {
const config = &server.config; 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 border_width = config.border_width;
const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box; const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box;