Compare commits

33 Commits
Author SHA1 Message Date
Zander671 050b9976f1 Merge branch 'main' of https://codeberg.org/river/river-classic 2026-05-03 06:09:03 -07:00
Zander671 451eca54be Merge branch 'main' of https://codeberg.org/river/river-classic 2026-04-26 20:16:57 -07:00
Zander671 f17b9d7e71 Merge branch 'main' of https://codeberg.org/river/river-classic 2026-04-01 14:18:31 -07:00
Zander671 0a148369aa Merge branch 'main' of https://codeberg.org/river/river-classic 2026-01-08 17:06:11 -08:00
Zander671 96b54bde86 Merge branch 'main' of git.zander.im:Zander671/river 2025-12-30 16:08:45 -08:00
Zander671 f6fd7655e2 Merge branch 'main' of https://codeberg.org/river/river-classic 2025-12-30 16:07:48 -08:00
Zander671 242b62a9d4 Merge new river-classic 2025-12-14 08:12:14 -08:00
Zander671 82613a56db Fix focused view sometimes being reported as having no tags 2025-10-09 16:05:23 -07:00
Zander671 936d1157b4 Fix last commit 2025-10-09 15:51:32 -07:00
Zander671 7ce21a44d8 Fix current view tags being wrong 2025-10-09 15:36:11 -07:00
Zander671 52b312d08d Merge branch '0.3.x' of https://codeberg.org/river/river 2025-08-31 15:54:01 -07:00
Isaac Freund b3b79944cc build: bump version to 0.3.12 2025-08-30 18:13:56 +02:00
Zander671 e97a63f0fa Merge branch '0.3.x' of https://codeberg.org/river/river 2025-08-17 17:23:31 -07:00
Zander671 9cc6c1e09b Fix riverctl.1.csd 2025-06-29 19:40:56 +09:00
Zander671 82f2f02c00 Add cursor warp option 2025-06-29 19:40:36 +09:00
Zander671 dc1d5c8418 Allow floating views to appear at the mouse 2025-06-29 19:40:00 +09:00
Zander671 0c095f0c93 Merge branch '0.3.x' of https://codeberg.org/river/river 2025-06-29 19:31:25 +09:00
Zander671 ed9e9c4d16 Remove assert that was causing crashes 2024-05-16 00:55:37 -07:00
Zander671 930dcb7dcd Fix current view tags status 2024-05-15 19:20:48 -07:00
Zander671 929db0098a Merge branch 'master' of https://codeberg.org/river/river 2024-05-15 04:36:26 -07:00
Isaac Freund b5a80c7b9b Server: fix filtering of newly created globals
Fixes: https://codeberg.org/river/river/issues/1068
2024-05-15 09:48:55 +02:00
ymcx bed50f0dd2 command/swap: fix cursor warp on focus change 2024-05-14 23:37:10 +02:00
Isaac Freund ad0b71c310 github: automatically close prs 2024-05-14 13:02:29 +02:00
Isaac Freund 1e3ef88bd5 Keyboard: fix redundant leave/enter on creation
Currently if a second keyboard input device is created river will send
a wl_keyboard.leave event immediately followed by a wl_keyboard.enter
event. This serves no purpose and can confuse clients, in particular due
to fctix creating/destroying virtual keyboards on focus change.

Fixes: https://codeberg.org/river/river/issues/1062
References: https://github.com/fcitx/fcitx5/issues/1044
2024-05-12 16:16:04 +02:00
Isaac Freund f66eec9248 Cursor: don't hide while pointer constraint active
Fixes: https://codeberg.org/river/river/issues/1053
2024-05-12 10:42:19 +02:00
Zander671 16d76ae86c Add tags field to focused_view event 2024-05-03 03:02:41 -07:00
LordMZTE e29d66f1de Xwayland: disallow negative output coordinates
Xwayland clients on outputs at negative positions don't currently
receive mouse events due to a bug in Xwayland. As a workaround, we
disallow negative output positions when Xwayland is enabled.

References: https://gitlab.freedesktop.org/xorg/xserver/-/issues/899
Closes: #1058
2024-05-02 14:08:34 +02:00
Alex Mirrlees-Black 5262a4c5a6 input: change ScrollFactor and MapToOutput to align with other options 2024-04-24 23:59:54 +10:00
Alex Mirrlees-Black bd5da261eb input: fix scroll-factor resetting if unspecified 2024-04-23 23:28:42 +10:00
Alex Mirrlees-Black 5fbf174c36 input: prevent duplicate input configs 2024-04-23 22:38:18 +10:00
Isaac Freund 9bbd34a0e3 XdgToplevel: update clip on geometry x/y change
Currently if a client commits a geometry with a different x/y value but
does not change the width/height we might not update the clip
coordinates of the surface tree, potentially causing part of the surface
to be unintentionally clipped off.

To fix this, check for change in geometry x/y as well as width/height on
commit if the client is not currently part of an ongoing transaction.

Firefox for example it seems may respond to a configure non-atomically
with multiple commits:

1. commit new buffer and new geometry of a new width/height.
2. commit again with the same width/height but a new geometry x/y.

I don't think this is technically a bug but it doesn't seem like the
most efficient way to do things. I think this may also cause imperfect
frames. In any case, this should no longer cause river to crop off part
of firefox's surface.
2024-04-19 13:46:08 +02:00
Isaac Freund d1dc873408 XdgToplevel: don't update geometry if configure inflight 2024-04-18 10:48:59 +02:00
Isaac Freund 6b86af4f85 build: bump version to 0.4.0-dev 2024-04-17 14:39:55 +02:00
16 changed files with 172 additions and 45 deletions
+18
View File
@@ -0,0 +1,18 @@
name: Close Pull Request
on:
pull_request_target:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: |
Thanks for your interest in contributing to river!
Unfortunately, you are in the wrong place. As stated in the README, this github repo is a read-only mirror and development happens on codeberg: https://codeberg.org/river/river.
Please open a pull request on codeberg instead.
+16 -8
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| {
+1 -1
View File
@@ -1,6 +1,6 @@
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=" \
+2 -2
View File
@@ -86,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"
+2 -2
View File
@@ -202,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
;;
+13 -5
View File
@@ -298,9 +298,13 @@ matches everything while _\*\*_ and the empty string are invalid.
with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the
identifier would be: _HP Inc. HP 22w CNC93720WF_. If the make, model, or
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. Applies only to new views.
- *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. 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.
@@ -311,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
@@ -344,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
+3 -2
View File
@@ -100,7 +100,7 @@
</event>
</interface>
<interface name="zriver_seat_status_v1" version="3">
<interface name="zriver_seat_status_v1" version="4">
<description summary="track seat focus">
This interface allows clients to receive information about the current
focus of a seat. Note that (un)focused_output events will only be sent
@@ -128,13 +128,14 @@
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="focused_view">
<event name="focused_view" since="4">
<description summary="information on the focused view">
Sent once on binding the interface and again whenever the focused
view or a property thereof changes. The title may be an empty string
if no view is focused or the focused view did not set a title.
</description>
<arg name="title" type="string" summary="title of the focused view"/>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
<event name="mode" since="3">
+10 -2
View File
@@ -58,9 +58,15 @@ pub const HideCursorWhenTypingMode = enum {
enabled,
};
pub const Anchor = enum {
absolute,
mouse,
};
pub const Position = struct {
x: u31,
y: u31,
anchor: Anchor,
x: i31,
y: i31,
};
pub const Dimensions = struct {
@@ -102,6 +108,7 @@ rules: struct {
dimensions: RuleList(Dimensions) = .{},
fullscreen: RuleList(bool) = .{},
tearing: RuleList(bool) = .{},
warp: RuleList(bool) = .{},
} = .{},
/// The selected focus_follows_cursor mode
@@ -186,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);
+9 -1
View File
@@ -1236,10 +1236,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) {
+17 -1
View File
@@ -707,7 +707,10 @@ fn commitTransaction(root: *Root) void {
{
var it = server.input_manager.seats.iterator(.forward);
while (it.next()) |seat| seat.cursor.updateState();
while (it.next()) |seat| {
seat.cursor.updateState();
seat.sendFocusedView();
}
}
{
@@ -803,6 +806,19 @@ fn processOutputConfig(
var proposed_state = wlr.Output.State.init();
head.state.apply(&proposed_state);
// Negative output coordinates currently cause Xwayland clients to not receive click events.
// See: https://gitlab.freedesktop.org/xorg/xserver/-/issues/899
if (build_options.xwayland and server.xwayland != null and
(head.state.x < 0 or head.state.y < 0))
{
std.log.scoped(.output_manager).err(
\\Attempted to set negative coordinates for output {s}.
\\Negative output coordinates are disallowed if Xwayland is enabled due to a limitation of Xwayland.
, .{output.wlr_output.name});
success = false;
continue;
}
switch (action) {
.test_only => {
if (!wlr_output.testState(&proposed_state)) success = false;
+5
View File
@@ -242,6 +242,11 @@ pub fn focus(seat: *Seat, _target: ?*View) void {
}
}
pub fn sendFocusedView(seat: *Seat) void {
var it = seat.status_trackers.iterator(.forward);
while (it.next()) |tracker| tracker.sendFocusedView();
}
/// Switch focus to the target, handling unfocus and input inhibition
/// properly. This should only be called directly if dealing with layers or
/// override redirect xwayland views.
+15 -5
View File
@@ -17,6 +17,8 @@
const SeatStatus = @This();
const std = @import("std");
const assert = std.debug.assert;
const wayland = @import("wayland");
const wl = wayland.server.wl;
const zriver = wayland.server.zriver;
@@ -76,11 +78,19 @@ pub fn sendOutput(seat_status: SeatStatus, output: *Output, state: enum { focuse
}
pub fn sendFocusedView(seat_status: SeatStatus) void {
const title: [*:0]const u8 = if (seat_status.seat.focused == .view)
seat_status.seat.focused.view.getTitle() orelse ""
else
"";
seat_status.seat_status_v1.sendFocusedView(title);
if (seat_status.seat_status_v1.getVersion() >= 4) {
switch (seat_status.seat.focused) {
.view => |view| {
if (view.current.tags != 0) {
// A view can't be on no tags, so we need to wait for the
// layout to update. There is probably a better way to do
// this, but this way seems to work.
seat_status.seat_status_v1.sendFocusedView(view.getTitle() orelse "", view.current.tags);
}
},
else => seat_status.seat_status_v1.sendFocusedView("", 0),
}
}
}
pub fn sendMode(seat_status: SeatStatus, mode: [*:0]const u8) void {
+15 -6
View File
@@ -703,8 +703,18 @@ pub fn map(view: *View) !void {
server.input_manager.defaultSeat().focused_output;
if (server.config.rules.position.match(view)) |position| {
view.pending.box.x = position.x;
view.pending.box.y = position.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);
@@ -776,19 +786,18 @@ pub fn unmap(view: *View) void {
server.root.applyPending();
}
pub fn notifyTitle(view: *const View) void {
pub fn notifyState(view: *const View) void {
if (view.foreign_toplevel_handle.wlr_handle) |wlr_handle| {
if (view.getTitle()) |title| wlr_handle.setTitle(title);
}
// Send title to all status listeners attached to a seat which focuses this view
if (view.ext_foreign_toplevel_handle) |handle| {
handle.updateState(&.{
.title = view.getTitle(),
.app_id = view.getAppId(),
});
}
// Send title to all status listeners attached to a seat which focuses this view
// Send title and tags to all status listeners attached to a seat which focuses this view
var seat_it = server.input_manager.seats.iterator(.forward);
while (seat_it.next()) |seat| {
if (seat.focused == .view and seat.focused.view == view) {
+1 -1
View File
@@ -479,7 +479,7 @@ fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), ev
/// Called when the client sets / updates its title
fn handleSetTitle(listener: *wl.Listener(void)) void {
const toplevel: *XdgToplevel = @fieldParentPtr("set_title", listener);
toplevel.view.notifyTitle();
toplevel.view.notifyState();
}
/// Called when the client sets / updates its app_id
+1 -1
View File
@@ -284,7 +284,7 @@ fn handleSetOverrideRedirect(listener: *wl.Listener(void)) void {
fn handleSetTitle(listener: *wl.Listener(void)) void {
const xwayland_view: *XwaylandView = @fieldParentPtr("set_title", listener);
xwayland_view.view.notifyTitle();
xwayland_view.view.notifyState();
}
fn handleSetClass(listener: *wl.Listener(void)) void {
+44 -8
View File
@@ -27,6 +27,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 RuleGlobs = @import("../rule_list.zig").RuleGlobs;
const Action = enum {
@@ -37,11 +38,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 {
@@ -57,9 +61,10 @@ 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;
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,
.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;
@@ -113,14 +118,32 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
});
},
.position => {
const x = try fmt.parseInt(u31, result.args[1], 10);
const y = try fmt.parseInt(u31, result.args[2], 10);
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 = .{
.x = x,
.y = y,
.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,
},
});
},
@@ -143,6 +166,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),
});
},
}
}
@@ -180,7 +210,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 => {
@@ -193,6 +223,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);
},
}
}
@@ -237,6 +270,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(),
@@ -253,13 +287,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| {
@@ -271,6 +306,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,
}});
}
@@ -286,7 +322,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
for (server.config.rules.position.rules.items) |rule| {
try alignLeft(rule.title_glob, title_column_max, writer);
try alignLeft(rule.app_id_glob, app_id_column_max, writer);
try writer.print("{d},{d}\n", .{ rule.value.x, rule.value.y });
try writer.print("{s},{d},{d}\n", .{ @tagName(rule.value.anchor), rule.value.x, rule.value.y });
}
},
.dimensions => {