Compare commits

..

16 Commits

Author SHA1 Message Date
8488cd9d97 Merge branch 'master' of https://codeberg.org/river/river 2025-04-17 21:25:40 +09:00
46f77f30dc Seat: put all keyboards in a single group
Deprecate and ignore the riverctl commands for creating explicit
keyboard groups.

In my mind, the only reason to have more than one keyboard group is if
different keyboard devices are assigned different keymaps or repeat
rates. River does not currently allow such things to be configured
however.

When river eventually makes it possible to configure different keymaps
and repeat rates per keyboard device, there is no reason we can't 100%
automatically group keyboards based on the keymap/repeat rate.

Exposing this keyboard group abstraction to the user is just bad UX.

Failing to group keyboards automatically also creates confusing/buggy
behavior for the user if the hardware, for example, exposes some of the
the XF86 buttons on a laptop as a separate keyboard device from the main
keyboard. Creating keybindings for these XF86 buttons that use modifiers
doesn't work by default, but there's no reason it shouldn't just work.

Closes: https://codeberg.org/river/river/issues/1138
2025-03-29 15:44:56 +01:00
8490558b8b Cursor: restore previous image on unhide
If client A has an xdg_popup open and the user moves the cursor over a
surface of client B and waits for the cursor to be hidden after a
timeout, the cursor will not be shown on movement until the (invisible)
cursor is moved back into a surface of client A or somewhere the
compositor is responsible for rendering the cursor.

This is due to the (flawed) xdg popup grab interface of wlroots which
prevents wlr_seat_pointer_notify_enter() from sending events to clients
other than the one with the active xdg popup.

Closes: https://codeberg.org/river/river/issues/1192
2025-03-16 13:48:09 +01:00
14f63f3099 Merge branch 'master' of https://codeberg.org/river/river 2025-02-05 15:17:43 -08:00
543697847f Xwayland: don't inherit rlimit changes from river 2025-01-07 11:00:44 -06:00
c1fc15dbc6 Merge branch 'master' of https://codeberg.org/river/river 2025-01-06 20:14:25 -08:00
6abcc68a19 river: wrap monotonic time > 2^32-1 milliseconds
Fixes: https://codeberg.org/river/river/issues/1176
2024-12-30 09:23:52 -06:00
ab879e245c Output: check scene damage before rendering
This should fix adaptive sync/VRR, which was regressed by db7de8151.
2024-12-29 16:09:44 -06:00
9f8b689f8a security-context-v1: fix assertion failure
There's a bit of subtlety I missed with the zig-wayland upgrade.
Since zig-wayland now generates its own wl_interface structs the pointer
comparison we used to do here is no longer sufficient.
2024-12-27 09:49:14 -06:00
e575485f0d deps: upgrade to latest zig-wayland 2024-12-23 14:14:35 -06:00
4fba7505f3 deps: update to latest zig-wayland 2024-12-17 17:35:16 -06:00
5ca829bd5a docs: mention key repeat defaults 2024-11-24 15:11:42 +01:00
a2a5e8f463 alpha-modifier-v1: implement protocol
Implement the alpha-modifier-v1 protocol, which allows clients to
offload alpha blending operations to the compositor.

wlroots' scene graph code takes care of updating the opacity of
wlr_scene_buffers with an associated wp_alpha_modifier_surface_v1.
2024-11-08 18:56:20 -05:00
33a69405c4 Fix riverctl.1.csd 2024-11-02 08:16:58 -07:00
5080f07724 Add cursor warp option 2024-11-02 08:00:50 -07:00
d66decb7c4 Open view relative to mouse, take 2 2024-11-01 22:08:20 -07:00
20 changed files with 236 additions and 379 deletions

View File

@ -100,11 +100,11 @@ pub fn build(b: *Build) !void {
scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
scanner.addCustomProtocol("protocol/river-control-unstable-v1.xml");
scanner.addCustomProtocol("protocol/river-status-unstable-v1.xml");
scanner.addCustomProtocol("protocol/river-layout-v3.xml");
scanner.addCustomProtocol("protocol/wlr-layer-shell-unstable-v1.xml");
scanner.addCustomProtocol("protocol/wlr-output-power-management-unstable-v1.xml");
scanner.addCustomProtocol(b.path("protocol/river-control-unstable-v1.xml"));
scanner.addCustomProtocol(b.path("protocol/river-status-unstable-v1.xml"));
scanner.addCustomProtocol(b.path("protocol/river-layout-v3.xml"));
scanner.addCustomProtocol(b.path("protocol/wlr-layer-shell-unstable-v1.xml"));
scanner.addCustomProtocol(b.path("protocol/wlr-output-power-management-unstable-v1.xml"));
// Some of these versions may be out of date with what wlroots implements.
// This is not a problem in practice though as long as river successfully compiles.
@ -185,9 +185,6 @@ pub fn build(b: *Build) !void {
.flags = &.{ "-std=c99", "-O2" },
});
// TODO: remove when zig issue #131 is implemented
scanner.addCSource(river);
river.pie = pie;
river.root_module.omit_frame_pointer = omit_frame_pointer;
@ -211,8 +208,6 @@ pub fn build(b: *Build) !void {
riverctl.linkLibC();
riverctl.linkSystemLibrary("wayland-client");
scanner.addCSource(riverctl);
riverctl.pie = pie;
riverctl.root_module.omit_frame_pointer = omit_frame_pointer;
@ -236,8 +231,6 @@ pub fn build(b: *Build) !void {
rivertile.linkLibC();
rivertile.linkSystemLibrary("wayland-client");
scanner.addCSource(rivertile);
rivertile.pie = pie;
rivertile.root_module.omit_frame_pointer = omit_frame_pointer;

View File

@ -8,12 +8,12 @@
.hash = "12209db20ce873af176138b76632931def33a10539387cba745db72933c43d274d56",
},
.@"zig-wayland" = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.2.0.tar.gz",
.hash = "1220687c8c47a48ba285d26a05600f8700d37fc637e223ced3aa8324f3650bf52242",
.url = "https://codeberg.org/ifreund/zig-wayland/archive/bd8afd256fb6beed7d72e3580b00f33dea7155a1.tar.gz",
.hash = "1220218a0e5c2cd63a2311417f4d3f2411dd17d75815f67c704ee657bd846ecbc3e0",
},
.@"zig-wlroots" = .{
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/e486223799648d27e8b91c5fe0ea4c088b74b707.tar.gz",
.hash = "1220aeb3317e16c38583839961c9d695fa60d23a3d506c8275fb0e8fa9849844f2f7",
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/afbbbbe5579c750feed8de12b073fa50b0651137.tar.gz",
.hash = "122060ddef836b7872cb2088764a8bd2fb2e9254327673e8176b7f7a621ec897484f",
},
.@"zig-xkbcommon" = .{
.url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.2.0.tar.gz",

View File

@ -18,12 +18,11 @@
const std = @import("std");
const mem = std.mem;
/// Validate a glob, returning error.InvalidGlob if it is empty, "**" or has a
/// '*' at any position other than the first and/or last byte.
/// Validate a glob, returning error.InvalidGlob if is "**" or has a '*'
/// at any position other than the first and/or last byte.
pub fn validate(glob: []const u8) error{InvalidGlob}!void {
switch (glob.len) {
0 => return error.InvalidGlob,
1 => {},
0, 1 => {},
2 => if (glob[0] == '*' and glob[1] == '*') return error.InvalidGlob,
else => if (mem.indexOfScalar(u8, glob[1 .. glob.len - 1], '*') != null) {
return error.InvalidGlob;
@ -34,6 +33,7 @@ pub fn validate(glob: []const u8) error{InvalidGlob}!void {
test validate {
const testing = std.testing;
try validate("");
try validate("*");
try validate("a");
try validate("*a");
@ -48,7 +48,6 @@ test validate {
try validate("abc*");
try validate("*abc*");
try testing.expectError(error.InvalidGlob, validate(""));
try testing.expectError(error.InvalidGlob, validate("**"));
try testing.expectError(error.InvalidGlob, validate("***"));
try testing.expectError(error.InvalidGlob, validate("a*c"));
@ -67,7 +66,9 @@ pub fn match(s: []const u8, glob: []const u8) bool {
validate(glob) catch unreachable;
}
if (glob.len == 1) {
if (glob.len == 0) {
return s.len == 0;
} else if (glob.len == 1) {
return glob[0] == '*' or mem.eql(u8, s, glob);
}
@ -89,6 +90,9 @@ test match {
const testing = std.testing;
try testing.expect(match("", "*"));
try testing.expect(match("", ""));
try testing.expect(!match("a", ""));
try testing.expect(!match("", "a"));
try testing.expect(match("a", "*"));
try testing.expect(match("a", "*a*"));
@ -165,8 +169,10 @@ pub fn order(a: []const u8, b: []const u8) std.math.Order {
return .lt;
}
const count_a = @as(u2, @intFromBool(a[0] == '*')) + @intFromBool(a[a.len - 1] == '*');
const count_b = @as(u2, @intFromBool(b[0] == '*')) + @intFromBool(b[b.len - 1] == '*');
const count_a = if (a.len != 0) @as(u2, @intFromBool(a[0] == '*')) +
@intFromBool(a[a.len - 1] == '*') else 0;
const count_b = if (b.len != 0) @as(u2, @intFromBool(b[0] == '*')) +
@intFromBool(b[b.len - 1] == '*') else 0;
if (count_a == 0 and count_b == 0) {
return .eq;
@ -182,6 +188,7 @@ test order {
const testing = std.testing;
const Order = std.math.Order;
try testing.expectEqual(Order.eq, order("", ""));
try testing.expectEqual(Order.eq, order("*", "*"));
try testing.expectEqual(Order.eq, order("*a*", "*b*"));
try testing.expectEqual(Order.eq, order("a*", "*b"));
@ -204,6 +211,7 @@ test order {
"bababab",
"b",
"a",
"",
};
for (descending, 0..) |a, i| {

View File

@ -1,13 +1,9 @@
function __riverctl_completion ()
{
local rule_actions="float no-float ssd csd tags output position dimensions fullscreen no-fullscreen"
local rule_actions="float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp"
if [ "${COMP_CWORD}" -eq 1 ]
then
OPTS=" \
keyboard-group-create \
keyboard-group-destroy \
keyboard-group-add \
keyboard-group-remove \
keyboard-layout \
keyboard-layout-file \
close \

View File

@ -72,11 +72,6 @@ complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'hide-cursor'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'set-repeat' -d 'Set the keyboard repeat rate and repeat delay'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'set-cursor-warp' -d 'Set the cursor warp mode'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'xcursor-theme' -d 'Set the xcursor theme'
# Keyboardgroups
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-create' -d 'Create a keyboard group'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-destroy' -d 'Destroy a keyboard group'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-add' -d 'Add a keyboard to a keyboard group'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-remove' -d 'Remove a keyboard from a keyboard group'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-layout' -d 'Set the keyboard layout'
complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-layout-file' -d 'Set the keyboard layout from a file.'
@ -91,10 +86,10 @@ complete -c riverctl -n '__fish_seen_subcommand_from default-attach-mode'
complete -c riverctl -n '__fish_seen_subcommand_from output-attach-mode' -n '__fish_riverctl_complete_arg 2' -a 'top bottom above below after'
complete -c riverctl -n '__fish_seen_subcommand_from focus-follows-cursor' -n '__fish_riverctl_complete_arg 2' -a 'disabled normal always'
complete -c riverctl -n '__fish_seen_subcommand_from set-cursor-warp' -n '__fish_riverctl_complete_arg 2' -a 'disabled on-output-change on-focus-change'
complete -c riverctl -n '__fish_seen_subcommand_from list-rules' -n '__fish_riverctl_complete_arg 2' -a 'float ssd tags output position dimensions fullscreen'
complete -c riverctl -n '__fish_seen_subcommand_from list-rules' -n '__fish_riverctl_complete_arg 2' -a 'float ssd tags output position dimensions fullscreen warp'
# Options and subcommands for 'rule-add' and 'rule-del'
set -l rule_actions float no-float ssd csd tags output position dimensions fullscreen no-fullscreen
set -l rule_actions float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp
complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'not __fish_seen_argument -o app-id' -o 'app-id' -r
complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'not __fish_seen_argument -o title' -o 'title' -r
complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'test (math (count (commandline -opc)) % 2) -eq 0' -a "$rule_actions"

View File

@ -62,11 +62,6 @@ _riverctl_commands()
'set-repeat:Set the keyboard repeat rate and repeat delay'
'set-cursor-warp:Set the cursor warp mode.'
'xcursor-theme:Set the xcursor theme'
# Keyboard groups
'keyboard-group-create:Create a keyboard group'
'keyboard-group-destroy:Destroy a keyboard group'
'keyboard-group-add:Add a keyboard to a keyboard group'
'keyboard-group-remove:Remove a keyboard from a keyboard group'
'keyboard-layout:Set the keyboard layout'
'keyboard-layout-file:Set the keyboard layout from a file'
# Input
@ -207,9 +202,9 @@ _riverctl()
# In case of a new rule added in river, we just need
# to add it to the third option between '()',
# i.e (float no-float <new-option>)
_arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tags output position dimensions fullscreen no-fullscreen)'
_arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp)'
;;
list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen)' ;;
list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen warp)' ;;
*) return 0 ;;
esac
;;

View File

@ -300,9 +300,11 @@ matches everything while _\*\*_ and the empty string are invalid.
serial is unknown, the word "Unknown" is used instead.
- *position*: Set the initial position of the view, clamping to the bounds
of the output. Requires x and y coordinates of the view as arguments, both
of which must be non-negative. Optionally, the string "mouse" can appear
as the only argument. In this case, the view will appear at the mouse's
position. Applies only to new views.
of which must be non-negative. Applies only to new views.
- *relative-position*: Set the position of the view relative to
something. Requires the anchor and the x and y coordinates of the
view. The coordinates are either positive or negative numbers that are
relative to the anchor. Applies only to new views.
- *dimensions*: Set the initial dimensions of the view, clamping to the
constraints of the view. Requires width and height of the view as
arguments, both of which must be non-negative. Applies only to new views.
@ -313,12 +315,16 @@ matches everything while _\*\*_ and the empty string are invalid.
view's preference. Applies to new and existing views.
- *no-tearing*: Disable tearing for the view regardless of the view's
preference. Applies to new and existing views.
- *warp*: Always warp the cursor when switching to this view, regardless of
the _set-cursor-warp_ setting. Applies to new and existing views.
- *no-warp*: Never warp the cursor when switching to this view, regardless
of the _set-cursor-warp_ setting. Applies to new and existing views.
Both *float* and *no-float* rules are added to the same list,
which means that adding a *no-float* rule with the same arguments
as a *float* rule will overwrite it. The same holds for *ssd* and
*csd*, *fullscreen* and *no-fullscreen*, *tearing* and
*no-tearing* rules.
*no-tearing*, *warp* and *no-warp* rules.
If multiple rules in a list match a given view the most specific
rule will be applied. For example with the following rules
@ -346,7 +352,7 @@ matches everything while _\*\*_ and the empty string are invalid.
*rule-del* [*-app-id* _glob_|*-title* _glob_] _action_
Delete a rule created using *rule-add* with the given arguments.
*list-rules* *float*|*ssd*|*tags*|*position*|*dimensions*|*fullscreen*
*list-rules* *float*|*ssd*|*tags*|*position*|*dimensions*|*fullscreen*|*warp*
Print the specified rule list. The output is ordered from most specific
to least specific, the same order in which views are checked against
when searching for a match. Only the first matching rule in the list
@ -424,7 +430,8 @@ matches everything while _\*\*_ and the empty string are invalid.
*set-repeat* _rate_ _delay_
Set the keyboard repeat rate to _rate_ key repeats per second and
repeat delay to _delay_ milliseconds.
repeat delay to _delay_ milliseconds. The default is a rate of 25
repeats per second and a delay of 600ms.
*xcursor-theme* _theme_name_ [_size_]
Set the xcursor theme to _theme_name_ and optionally set the _size_.
@ -455,25 +462,6 @@ matches everything while _\*\*_ and the empty string are invalid.
following URL:
https://xkbcommon.org/doc/current/keymap-text-format-v1.html
*keyboard-group-create* _group_name_
Create a keyboard group. A keyboard group collects multiple keyboards in
a single logical keyboard. This means that all state, like the active
modifiers, is shared between the keyboards in a group.
*keyboard-group-destroy* _group_name_
Destroy the keyboard group with the given name. All attached keyboards
will be released, making them act as separate devices again.
*keyboard-group-add* _group_name_ _input_device_name_
Add a keyboard to a keyboard group, identified by the keyboard's
input device name. Any currently connected and future keyboards with
the given name will be added to the group. Simple globbing patterns are
supported, see the rules section for further information on globs.
*keyboard-group-remove* _group_name_ _input_device_name_
Remove a keyboard from a keyboard group, identified by the keyboard's
input device name.
The _input_ command can be used to create a configuration rule for an input
device identified by its _name_.
The _name_ of an input device consists of its type, its decimal vendor id,

View File

@ -58,19 +58,15 @@ pub const HideCursorWhenTypingMode = enum {
enabled,
};
pub const PositionType = enum {
pub const Anchor = enum {
absolute,
at_mouse,
mouse,
};
pub const Position = struct {
x: u31,
y: u31,
};
pub const FloatPosition = union(PositionType) {
absolute: Position,
at_mouse,
anchor: Anchor,
x: i31,
y: i31,
};
pub const Dimensions = struct {
@ -108,10 +104,11 @@ rules: struct {
ssd: RuleList(bool) = .{},
tags: RuleList(u32) = .{},
output: RuleList([]const u8) = .{},
position: RuleList(FloatPosition) = .{},
position: RuleList(Position) = .{},
dimensions: RuleList(Dimensions) = .{},
fullscreen: RuleList(bool) = .{},
tearing: RuleList(bool) = .{},
warp: RuleList(bool) = .{},
} = .{},
/// The selected focus_follows_cursor mode
@ -196,6 +193,7 @@ pub fn deinit(config: *Config) void {
config.rules.position.deinit();
config.rules.dimensions.deinit();
config.rules.fullscreen.deinit();
config.rules.warp.deinit();
util.gpa.free(config.default_layout_namespace);

View File

@ -108,6 +108,19 @@ const LayoutPoint = struct {
ly: f64,
};
const Image = union(enum) {
/// No cursor image
none,
/// Name of the current Xcursor shape
xcursor: [*:0]const u8,
/// Cursor surface configured by a client
client: struct {
surface: *wlr.Surface,
hotspot_x: i32,
hotspot_y: i32,
},
};
const log = std.log.scoped(.cursor);
/// Current cursor mode as well as any state needed to implement that mode
@ -124,9 +137,8 @@ wlr_cursor: *wlr.Cursor,
/// Xcursor manager for the currently configured Xcursor theme.
xcursor_manager: *wlr.XcursorManager,
/// Name of the current Xcursor shape, or null if a client has configured a
/// surface to be used as the cursor shape instead.
xcursor_name: ?[*:0]const u8 = null,
image: Image = .none,
image_surface_destroy: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleImageSurfaceDestroy),
/// Number of distinct buttons currently pressed
pressed_count: u32 = 0,
@ -286,18 +298,40 @@ pub fn setTheme(cursor: *Cursor, theme: ?[*:0]const u8, _size: ?u32) !void {
cursor.xcursor_manager.destroy();
cursor.xcursor_manager = xcursor_manager;
if (cursor.xcursor_name) |name| {
cursor.setXcursor(name);
switch (cursor.image) {
.none, .client => {},
.xcursor => |name| cursor.wlr_cursor.setXcursor(xcursor_manager, name),
}
}
pub fn setXcursor(cursor: *Cursor, name: [*:0]const u8) void {
cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, name);
cursor.xcursor_name = name;
pub fn setImage(cursor: *Cursor, image: Image) void {
switch (cursor.image) {
.none, .xcursor => {},
.client => {
cursor.image_surface_destroy.link.remove();
},
}
cursor.image = image;
switch (cursor.image) {
.none => cursor.wlr_cursor.unsetImage(),
.xcursor => |name| cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, name),
.client => |client| {
cursor.wlr_cursor.setSurface(client.surface, client.hotspot_x, client.hotspot_y);
client.surface.events.destroy.add(&cursor.image_surface_destroy);
},
}
}
fn handleImageSurfaceDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
const cursor: *Cursor = @fieldParentPtr("image_surface_destroy", listener);
// wlroots calls wlr_cursor_unset_image() automatically
// when the cursor surface is destroyed.
cursor.image = .none;
cursor.image_surface_destroy.link.remove();
}
fn clearFocus(cursor: *Cursor) void {
cursor.setXcursor("default");
cursor.setImage(.{ .xcursor = "default" });
cursor.seat.wlr_seat.pointerNotifyClearFocus();
}
@ -740,8 +774,15 @@ fn handleRequestSetCursor(
// on the output that it's currently on and continue to do so as the
// cursor moves between outputs.
log.debug("focused client set cursor", .{});
cursor.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
cursor.xcursor_name = null;
if (event.surface) |surface| {
cursor.setImage(.{ .client = .{
.surface = surface,
.hotspot_x = event.hotspot_x,
.hotspot_y = event.hotspot_y,
} });
} else {
cursor.setImage(.none);
}
}
}
@ -757,8 +798,6 @@ pub fn hide(cursor: *Cursor) void {
cursor.hidden = true;
cursor.wlr_cursor.unsetImage();
cursor.xcursor_name = null;
cursor.seat.wlr_seat.pointerNotifyClearFocus();
cursor.hide_cursor_timer.timerUpdate(0) catch {
log.err("failed to update cursor hide timeout", .{});
};
@ -770,6 +809,7 @@ pub fn unhide(cursor: *Cursor) void {
};
if (!cursor.hidden) return;
cursor.hidden = false;
cursor.setImage(cursor.image);
cursor.updateState();
}
@ -868,7 +908,7 @@ fn computeEdges(cursor: *const Cursor, view: *const View) wlr.Edges {
}
}
fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor_name: [*:0]const u8) void {
fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor: [*:0]const u8) void {
assert(cursor.mode == .passthrough or cursor.mode == .down);
assert(mode == .move or mode == .resize);
@ -884,7 +924,7 @@ fn enterMode(cursor: *Cursor, mode: Mode, view: *View, xcursor_name: [*:0]const
}
cursor.seat.wlr_seat.pointerNotifyClearFocus();
cursor.setXcursor(xcursor_name);
cursor.setImage(.{ .xcursor = xcursor });
server.root.applyPending();
}
@ -1091,8 +1131,13 @@ pub fn updateState(cursor: *Cursor) void {
if (!cursor.hidden) {
var now: posix.timespec = undefined;
posix.clock_gettime(posix.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
const msec: u32 = @intCast(now.tv_sec * std.time.ms_per_s +
@divTrunc(now.tv_nsec, std.time.ns_per_ms));
// 2^32-1 milliseconds is ~50 days, which is a realistic uptime.
// This means that we must wrap if the monotonic time is greater than
// 2^32-1 milliseconds and hope that clients don't get too confused.
const msec: u32 = @intCast(@rem(
now.tv_sec *% std.time.ms_per_s +% @divTrunc(now.tv_nsec, std.time.ns_per_ms),
math.maxInt(u32),
));
cursor.passthrough(msec);
}
},
@ -1178,10 +1223,18 @@ fn warp(cursor: *Cursor) void {
const focused_output = cursor.seat.focused_output orelse return;
var mode = server.config.warp_cursor;
if (cursor.seat.focused == .view) {
const view = cursor.seat.focused.view;
if (server.config.rules.warp.match(view)) |w| {
mode = if (w) .@"on-focus-change" else .disabled;
}
}
// Warp pointer to center of the focused view/output (In layout coordinates) if enabled.
var output_layout_box: wlr.Box = undefined;
server.root.output_layout.getBox(focused_output.wlr_output, &output_layout_box);
const target_box = switch (server.config.warp_cursor) {
const target_box = switch (mode) {
.disabled => return,
.@"on-output-change" => output_layout_box,
.@"on-focus-change" => switch (cursor.seat.focused) {

View File

@ -101,16 +101,9 @@ pub fn init(keyboard: *Keyboard, seat: *Seat, wlr_device: *wlr.InputDevice) !voi
// wlroots will log a more detailed error if this fails.
if (!wlr_keyboard.setKeymap(server.config.keymap)) return error.OutOfMemory;
// Add to keyboard-group, if applicable.
var group_it = seat.keyboard_groups.first;
outer: while (group_it) |group_node| : (group_it = group_node.next) {
for (group_node.data.globs.items) |glob| {
if (globber.match(glob, keyboard.device.identifier)) {
// wlroots will log an error if this fails explaining the reason.
_ = group_node.data.wlr_group.addKeyboard(wlr_keyboard);
break :outer;
}
}
if (wlr.KeyboardGroup.fromKeyboard(wlr_keyboard) == null) {
// wlroots will log an error on failure
_ = seat.keyboard_group.addKeyboard(wlr_keyboard);
}
wlr_keyboard.setRepeatInfo(server.config.repeat_rate, server.config.repeat_delay);

View File

@ -1,141 +0,0 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2022 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const KeyboardGroup = @This();
const std = @import("std");
const assert = std.debug.assert;
const mem = std.mem;
const globber = @import("globber");
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const xkb = @import("xkbcommon");
const log = std.log.scoped(.keyboard_group);
const server = &@import("main.zig").server;
const util = @import("util.zig");
const Seat = @import("Seat.zig");
const Keyboard = @import("Keyboard.zig");
seat: *Seat,
wlr_group: *wlr.KeyboardGroup,
name: []const u8,
globs: std.ArrayListUnmanaged([]const u8) = .{},
pub fn create(seat: *Seat, name: []const u8) !void {
log.debug("new keyboard group: '{s}'", .{name});
const node = try util.gpa.create(std.TailQueue(KeyboardGroup).Node);
errdefer util.gpa.destroy(node);
const wlr_group = try wlr.KeyboardGroup.create();
errdefer wlr_group.destroy();
const owned_name = try util.gpa.dupe(u8, name);
errdefer util.gpa.free(owned_name);
node.data = .{
.wlr_group = wlr_group,
.name = owned_name,
.seat = seat,
};
seat.addDevice(&wlr_group.keyboard.base);
seat.keyboard_groups.append(node);
}
pub fn destroy(group: *KeyboardGroup) void {
log.debug("destroying keyboard group: '{s}'", .{group.name});
util.gpa.free(group.name);
for (group.globs.items) |glob| {
util.gpa.free(glob);
}
group.globs.deinit(util.gpa);
group.wlr_group.destroy();
const node: *std.TailQueue(KeyboardGroup).Node = @fieldParentPtr("data", group);
group.seat.keyboard_groups.remove(node);
util.gpa.destroy(node);
}
pub fn addIdentifier(group: *KeyboardGroup, new_id: []const u8) !void {
for (group.globs.items) |glob| {
if (mem.eql(u8, glob, new_id)) return;
}
log.debug("keyboard group '{s}' adding identifier: '{s}'", .{ group.name, new_id });
const owned_id = try util.gpa.dupe(u8, new_id);
errdefer util.gpa.free(owned_id);
// Glob is validated in the command handler.
try group.globs.append(util.gpa, owned_id);
errdefer {
// Not used now, but if at any point this function is modified to that
// it may return an error after the glob pattern is added to the list,
// the list will have a pointer to freed memory in its last position.
_ = group.globs.pop();
}
// Add any existing matching keyboards to the group.
var it = server.input_manager.devices.iterator(.forward);
while (it.next()) |device| {
if (device.seat != group.seat) continue;
if (device.wlr_device.type != .keyboard) continue;
if (globber.match(device.identifier, new_id)) {
log.debug("found existing matching keyboard; adding to group", .{});
if (!group.wlr_group.addKeyboard(device.wlr_device.toKeyboard())) {
// wlroots logs an error message to explain why this failed.
continue;
}
}
// Continue, because we may have more than one device with the exact
// same identifier. That is in fact one reason for the keyboard group
// feature to exist in the first place.
}
}
pub fn removeIdentifier(group: *KeyboardGroup, id: []const u8) !void {
for (group.globs.items, 0..) |glob, index| {
if (mem.eql(u8, glob, id)) {
_ = group.globs.orderedRemove(index);
break;
}
} else {
return;
}
var it = server.input_manager.devices.iterator(.forward);
while (it.next()) |device| {
if (device.seat != group.seat) continue;
if (device.wlr_device.type != .keyboard) continue;
if (globber.match(device.identifier, id)) {
const wlr_keyboard = device.wlr_device.toKeyboard();
assert(wlr_keyboard.group == group.wlr_group);
group.wlr_group.removeKeyboard(wlr_keyboard);
}
}
}

View File

@ -542,6 +542,13 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
}
fn renderAndCommit(output: *Output, scene_output: *wlr.SceneOutput) !void {
// TODO(wlroots): replace this with wlr_scene_output_needs_frame()
if (!output.wlr_output.needs_frame and !output.gamma_dirty and
!scene_output.pending_commit_damage.notEmpty())
{
return;
}
var state = wlr.Output.State.init();
defer state.finish();

View File

@ -33,7 +33,6 @@ const InputDevice = @import("InputDevice.zig");
const InputManager = @import("InputManager.zig");
const InputRelay = @import("InputRelay.zig");
const Keyboard = @import("Keyboard.zig");
const KeyboardGroup = @import("KeyboardGroup.zig");
const LayerSurface = @import("LayerSurface.zig");
const LockSurface = @import("LockSurface.zig");
const Mapping = @import("Mapping.zig");
@ -84,7 +83,7 @@ mapping_repeat_timer: *wl.EventSource,
/// Currently repeating mapping, if any
repeating_mapping: ?*const Mapping = null,
keyboard_groups: std.TailQueue(KeyboardGroup) = .{},
keyboard_group: *wlr.KeyboardGroup,
/// Currently focused output. Null only when there are no outputs at all.
focused_output: ?*Output = null,
@ -121,12 +120,15 @@ pub fn init(seat: *Seat, name: [*:0]const u8) !void {
.cursor = undefined,
.relay = undefined,
.mapping_repeat_timer = mapping_repeat_timer,
.keyboard_group = try wlr.KeyboardGroup.create(),
};
seat.wlr_seat.data = @intFromPtr(seat);
try seat.cursor.init(seat);
seat.relay.init();
try seat.tryAddDevice(&seat.keyboard_group.keyboard.base);
seat.wlr_seat.events.request_set_selection.add(&seat.request_set_selection);
seat.wlr_seat.events.request_start_drag.add(&seat.request_start_drag);
seat.wlr_seat.events.start_drag.add(&seat.start_drag);
@ -142,9 +144,7 @@ pub fn deinit(seat: *Seat) void {
seat.cursor.deinit();
seat.mapping_repeat_timer.remove();
while (seat.keyboard_groups.first) |node| {
node.data.destroy();
}
seat.keyboard_group.destroy();
seat.request_set_selection.link.remove();
seat.request_start_drag.link.remove();

View File

@ -19,6 +19,7 @@ const Server = @This();
const build_options = @import("build_options");
const std = @import("std");
const assert = std.debug.assert;
const mem = std.mem;
const posix = std.posix;
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
@ -86,6 +87,8 @@ foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
tearing_control_manager: *wlr.TearingControlManagerV1,
alpha_modifier: *wlr.AlphaModifierV1,
input_manager: InputManager,
root: Root,
config: Config,
@ -163,6 +166,8 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
.tearing_control_manager = try wlr.TearingControlManagerV1.create(wl_server, 1),
.alpha_modifier = try wlr.AlphaModifierV1.create(wl_server),
.config = try Config.init(),
.root = undefined,
@ -295,13 +300,16 @@ fn allowlist(server: *Server, global: *const wl.Global) bool {
// such as wl_output and wl_seat since the wl_global_create() function will
// advertise the global to clients and invoke this filter before returning
// the new global pointer.
//
if ((mem.orderZ(u8, global.getInterface().name, "wl_output") == .eq) or
(mem.orderZ(u8, global.getInterface().name, "wl_seat") == .eq))
{
return true;
}
// For other globals I like the current pointer comparison approach as it
// should catch river accidentally exposing multiple copies of e.g. wl_shm
// with an assertion failure.
return global.getInterface() == wl.Output.getInterface() or
global.getInterface() == wl.Seat.getInterface() or
global == server.shm.global or
return global == server.shm.global or
global == server.single_pixel_buffer_manager.global or
global == server.viewporter.global or
global == server.fractional_scale_manager.global or
@ -321,7 +329,8 @@ fn allowlist(server: *Server, global: *const wl.Global) bool {
global == server.input_manager.tablet_manager.global or
global == server.input_manager.pointer_gestures.global or
global == server.idle_inhibit_manager.wlr_manager.global or
global == server.tearing_control_manager.global;
global == server.tearing_control_manager.global or
global == server.alpha_modifier.global;
}
/// Returns true if the global is blocked for security contexts
@ -510,7 +519,7 @@ fn handleRequestSetCursorShape(
// actually has pointer focus first.
if (focused_client == event.seat_client) {
const name = wlr.CursorShapeManagerV1.shapeName(event.shape);
seat.cursor.setXcursor(name);
seat.cursor.setImage(.{ .xcursor = name });
}
}
}

View File

@ -678,17 +678,18 @@ pub fn map(view: *View) !void {
server.input_manager.defaultSeat().focused_output;
if (server.config.rules.position.match(view)) |position| {
switch (position) {
.absolute => |pos| {
view.pending.box.x = pos.x;
view.pending.box.y = pos.y;
},
.at_mouse => {
const cursor = server.input_manager.defaultSeat().cursor.wlr_cursor;
view.pending.box.x = @as(c_int, @intFromFloat(cursor.x));
view.pending.box.y = @as(c_int, @intFromFloat(cursor.y));
var base_x: i31 = 0;
var base_y: i31 = 0;
switch (position.anchor) {
.absolute => {},
.mouse => {
const cursor = server.input_manager.defaultSeat().wlr_seat.pointer_state;
base_x = @intCast(@as(i31, @intFromFloat(cursor.sx)));
base_y = @intCast(@as(i31, @intFromFloat(cursor.sy)));
},
}
view.pending.box.x = base_x + position.x;
view.pending.box.y = base_y + position.y;
} else if (output) |o| {
// Center the initial pending box on the output
view.pending.box.x = @divTrunc(@max(0, o.usable_box.width - view.pending.box.width), 2);

View File

@ -24,77 +24,13 @@ const util = @import("../util.zig");
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
const KeyboardGroup = @import("../KeyboardGroup.zig");
pub fn keyboardGroupCreate(
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;
pub const keyboardGroupCreate = keyboardGroupDeprecated;
pub const keyboardGroupDestroy = keyboardGroupDeprecated;
pub const keyboardGroupAdd = keyboardGroupDeprecated;
pub const keyboardGroupRemove = keyboardGroupDeprecated;
if (keyboardGroupFromName(seat, args[1]) != null) {
const msg = try util.gpa.dupe(u8, "error: failed to create keybaord group: group of same name already exists\n");
out.* = msg;
return;
}
try KeyboardGroup.create(seat, args[1]);
}
pub fn keyboardGroupDestroy(
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;
const group = keyboardGroupFromName(seat, args[1]) orelse {
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
out.* = msg;
return;
};
group.destroy();
}
pub fn keyboardGroupAdd(
seat: *Seat,
args: []const [:0]const u8,
out: *?[]const u8,
) Error!void {
if (args.len < 3) return Error.NotEnoughArguments;
if (args.len > 3) return Error.TooManyArguments;
const group = keyboardGroupFromName(seat, args[1]) orelse {
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
out.* = msg;
return;
};
try globber.validate(args[2]);
try group.addIdentifier(args[2]);
}
pub fn keyboardGroupRemove(
seat: *Seat,
args: []const [:0]const u8,
out: *?[]const u8,
) Error!void {
if (args.len < 3) return Error.NotEnoughArguments;
if (args.len > 3) return Error.TooManyArguments;
const group = keyboardGroupFromName(seat, args[1]) orelse {
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
out.* = msg;
return;
};
try group.removeIdentifier(args[2]);
}
fn keyboardGroupFromName(seat: *Seat, name: []const u8) ?*KeyboardGroup {
var it = seat.keyboard_groups.first;
while (it) |node| : (it = node.next) {
if (mem.eql(u8, node.data.name, name)) return &node.data;
}
return null;
fn keyboardGroupDeprecated(_: *Seat, _: []const [:0]const u8, out: *?[]const u8) Error!void {
out.* = try util.gpa.dupe(u8, "warning: explicit keyboard groups are deprecated, " ++
"all keyboards are now automatically added to a single group\n");
}

View File

@ -26,6 +26,7 @@ const util = @import("../util.zig");
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
const View = @import("../View.zig");
const Anchor = @import("../Config.zig").Anchor;
const Action = enum {
float,
@ -35,11 +36,14 @@ const Action = enum {
tags,
output,
position,
@"relative-position",
dimensions,
fullscreen,
@"no-fullscreen",
tearing,
@"no-tearing",
warp,
@"no-warp",
};
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
@ -54,17 +58,11 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
const action = std.meta.stringToEnum(Action, result.args[0]) orelse return Error.UnknownOption;
var pos_is_mouse = false;
const positional_arguments_count: u8 = switch (action) {
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing" => 1,
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing", .warp, .@"no-warp" => 1,
.tags, .output => 2,
.dimensions => 3,
.position => blk: {
if (result.args.len >= 2 and std.mem.eql(u8, result.args[1], "mouse")) {
pos_is_mouse = true;
break :blk 2;
} else break :blk 3;
},
.position, .dimensions => 3,
.@"relative-position" => 4,
};
if (result.args.len > positional_arguments_count) return Error.TooManyArguments;
if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments;
@ -118,24 +116,34 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
});
},
.position => {
if (pos_is_mouse) {
try server.config.rules.position.add(.{
.app_id_glob = app_id_glob,
.title_glob = title_glob,
.value = .at_mouse,
});
} else {
const x = try fmt.parseInt(u31, result.args[1], 10);
const y = try fmt.parseInt(u31, result.args[2], 10);
try server.config.rules.position.add(.{
.app_id_glob = app_id_glob,
.title_glob = title_glob,
.value = .{ .absolute = .{
.x = x,
.y = y,
} },
});
}
const x = try fmt.parseInt(i31, result.args[1], 10);
const y = try fmt.parseInt(i31, result.args[2], 10);
if (x < 0 or y < 0) return Error.OutOfBounds;
try server.config.rules.position.add(.{
.app_id_glob = app_id_glob,
.title_glob = title_glob,
.value = .{
.anchor = .absolute,
.x = @intCast(x),
.y = @intCast(y),
},
});
},
.@"relative-position" => {
const anchor = std.meta.stringToEnum(Anchor, result.args[1]) orelse return Error.UnknownOption;
// force the use of the normal position command for absolute positions
if (anchor == .absolute) return Error.UnknownOption;
const x_off = try fmt.parseInt(i31, result.args[2], 10);
const y_off = try fmt.parseInt(i31, result.args[3], 10);
try server.config.rules.position.add(.{
.app_id_glob = app_id_glob,
.title_glob = title_glob,
.value = .{
.anchor = anchor,
.x = x_off,
.y = y_off,
},
});
},
.dimensions => {
const width = try fmt.parseInt(u31, result.args[1], 10);
@ -156,6 +164,13 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
.value = (action == .fullscreen),
});
},
.warp, .@"no-warp" => {
try server.config.rules.warp.add(.{
.app_id_glob = app_id_glob,
.title_glob = title_glob,
.value = (action == .warp),
});
},
}
}
@ -193,7 +208,7 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
util.gpa.free(output_rule);
}
},
.position => {
.position, .@"relative-position" => {
_ = server.config.rules.position.del(rule);
},
.dimensions => {
@ -206,6 +221,9 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
_ = server.config.rules.tearing.del(rule);
apply_tearing_rules();
},
.warp, .@"no-warp" => {
_ = server.config.rules.warp.del(rule);
},
}
}
@ -244,6 +262,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
dimensions,
fullscreen,
tearing,
warp,
}, args[1]) orelse return Error.UnknownOption;
const max_glob_len = switch (rule_list) {
inline else => |list| @field(server.config.rules, @tagName(list)).getMaxGlobLen(),
@ -259,13 +278,14 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
try writer.writeAll("action\n");
switch (rule_list) {
inline .float, .ssd, .output, .fullscreen, .tearing => |list| {
inline .float, .ssd, .output, .fullscreen, .tearing, .warp => |list| {
const rules = switch (list) {
.float => server.config.rules.float.rules.items,
.ssd => server.config.rules.ssd.rules.items,
.output => server.config.rules.output.rules.items,
.fullscreen => server.config.rules.fullscreen.rules.items,
.tearing => server.config.rules.tearing.rules.items,
.warp => server.config.rules.warp.rules.items,
else => unreachable,
};
for (rules) |rule| {
@ -277,6 +297,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
.output => rule.value,
.fullscreen => if (rule.value) "fullscreen" else "no-fullscreen",
.tearing => if (rule.value) "tearing" else "no-tearing",
.warp => if (rule.value) "warp" else "no-warp",
else => unreachable,
}});
}
@ -292,10 +313,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
for (server.config.rules.position.rules.items) |rule| {
try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .left }, writer);
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .left }, writer);
switch (rule.value) {
.absolute => |pos| try writer.print("{d},{d}\n", .{ pos.x, pos.y }),
.at_mouse => try writer.print("mouse\n", .{}),
}
try writer.print("{s},{d},{d}", .{ @tagName(rule.value.anchor), rule.value.x, rule.value.y });
}
},
.dimensions => {

View File

@ -84,7 +84,7 @@ pub fn main() anyerror!void {
posix.exit(1);
}
}
const enable_xwayland = !result.flags.@"no-xwayland";
const runtime_xwayland = !result.flags.@"no-xwayland";
const startup_command = blk: {
if (result.flags.c) |command| {
break :blk try util.gpa.dupeZ(u8, command);
@ -95,17 +95,25 @@ pub fn main() anyerror!void {
log.info("river version {s}, initializing server", .{build_options.version});
process.setup();
river_init_wlroots_log(switch (runtime_log_level) {
.debug => .debug,
.info => .info,
.warn, .err => .err,
});
try server.init(enable_xwayland);
try server.init(runtime_xwayland);
defer server.deinit();
// wlroots starts the Xwayland process from an idle event source, the reasoning being that
// this gives the compositor time to set up event listeners before Xwayland is actually
// started. We want Xwayland to be started by wlroots before we modify our rlimits in
// process.setup() since wlroots does not offer a way for us to reset the rlimit post-fork.
if (build_options.xwayland and runtime_xwayland) {
server.wl_server.getEventLoop().dispatchIdle();
}
process.setup();
try server.start();
// Run the child in a new process group so that we can send SIGTERM to all

View File

@ -110,10 +110,10 @@ fn _main() !void {
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
switch (event) {
.global => |global| {
if (mem.orderZ(u8, global.interface, wl.Seat.getInterface().name) == .eq) {
if (mem.orderZ(u8, global.interface, wl.Seat.interface.name) == .eq) {
assert(globals.seat == null); // TODO: support multiple seats
globals.seat = registry.bind(global.name, wl.Seat, 1) catch @panic("out of memory");
} else if (mem.orderZ(u8, global.interface, zriver.ControlV1.getInterface().name) == .eq) {
} else if (mem.orderZ(u8, global.interface, zriver.ControlV1.interface.name) == .eq) {
globals.control = registry.bind(global.name, zriver.ControlV1, 1) catch @panic("out of memory");
}
},

View File

@ -382,9 +382,9 @@ pub fn main() !void {
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
switch (event) {
.global => |global| {
if (mem.orderZ(u8, global.interface, river.LayoutManagerV3.getInterface().name) == .eq) {
if (mem.orderZ(u8, global.interface, river.LayoutManagerV3.interface.name) == .eq) {
context.layout_manager = registry.bind(global.name, river.LayoutManagerV3, 1) catch return;
} else if (mem.orderZ(u8, global.interface, wl.Output.getInterface().name) == .eq) {
} else if (mem.orderZ(u8, global.interface, wl.Output.interface.name) == .eq) {
context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
}
},