Compare commits
16 Commits
eab34c7c03
...
track-old-
Author | SHA1 | Date | |
---|---|---|---|
8488cd9d97
|
|||
46f77f30dc | |||
8490558b8b | |||
14f63f3099
|
|||
543697847f | |||
c1fc15dbc6
|
|||
6abcc68a19 | |||
ab879e245c | |||
9f8b689f8a | |||
e575485f0d | |||
4fba7505f3 | |||
5ca829bd5a | |||
a2a5e8f463 | |||
33a69405c4
|
|||
5080f07724
|
|||
d66decb7c4
|
17
build.zig
17
build.zig
@ -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;
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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| {
|
||||
|
@ -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 \
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
;;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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) {
|
||||
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 = .at_mouse,
|
||||
.value = .{
|
||||
.anchor = .absolute,
|
||||
.x = @intCast(x),
|
||||
.y = @intCast(y),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const x = try fmt.parseInt(u31, result.args[1], 10);
|
||||
const y = try fmt.parseInt(u31, result.args[2], 10);
|
||||
},
|
||||
.@"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 = .{ .absolute = .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
} },
|
||||
.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 => {
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
},
|
||||
|
@ -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});
|
||||
}
|
||||
},
|
||||
|
Reference in New Issue
Block a user