river: add rules system
This is a breaking change and replaces the previous csd-filter-add/remove and float-filter-add/remove commands. See the riverctl(1) man page for documentation on the new system.
This commit is contained in:
parent
05eac54b07
commit
b2b2c9ed13
10
build.zig
10
build.zig
@ -163,6 +163,7 @@ pub fn build(b: *zbs.Builder) !void {
|
|||||||
river.linkSystemLibrary("wlroots");
|
river.linkSystemLibrary("wlroots");
|
||||||
|
|
||||||
river.addPackagePath("flags", "common/flags.zig");
|
river.addPackagePath("flags", "common/flags.zig");
|
||||||
|
river.addPackagePath("globber", "common/globber.zig");
|
||||||
river.addCSourceFile("river/wlroots_log_wrapper.c", &[_][]const u8{ "-std=c99", "-O2" });
|
river.addCSourceFile("river/wlroots_log_wrapper.c", &[_][]const u8{ "-std=c99", "-O2" });
|
||||||
|
|
||||||
// TODO: remove when zig issue #131 is implemented
|
// TODO: remove when zig issue #131 is implemented
|
||||||
@ -254,6 +255,15 @@ pub fn build(b: *zbs.Builder) !void {
|
|||||||
if (fish_completion) {
|
if (fish_completion) {
|
||||||
b.installFile("completions/fish/riverctl.fish", "share/fish/vendor_completions.d/riverctl.fish");
|
b.installFile("completions/fish/riverctl.fish", "share/fish/vendor_completions.d/riverctl.fish");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const globber_test = b.addTest("common/globber.zig");
|
||||||
|
globber_test.setTarget(target);
|
||||||
|
globber_test.setBuildMode(mode);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run the tests");
|
||||||
|
test_step.dependOn(&globber_test.step);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScdocStep = struct {
|
const ScdocStep = struct {
|
||||||
|
223
common/globber.zig
Normal file
223
common/globber.zig
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
// Basic prefix, suffix, and substring glob matching.
|
||||||
|
//
|
||||||
|
// Released under the Zero Clause BSD (0BSD) license:
|
||||||
|
//
|
||||||
|
// Copyright 2023 Isaac Freund
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
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.
|
||||||
|
pub fn validate(glob: []const u8) error{InvalidGlob}!void {
|
||||||
|
switch (glob.len) {
|
||||||
|
0 => return error.InvalidGlob,
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test validate {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
_ = try validate("*");
|
||||||
|
_ = try validate("a");
|
||||||
|
_ = try validate("*a");
|
||||||
|
_ = try validate("a*");
|
||||||
|
_ = try validate("*a*");
|
||||||
|
_ = try validate("ab");
|
||||||
|
_ = try validate("*ab");
|
||||||
|
_ = try validate("ab*");
|
||||||
|
_ = try validate("*ab*");
|
||||||
|
_ = try validate("abc");
|
||||||
|
_ = try validate("*abc");
|
||||||
|
_ = 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"));
|
||||||
|
try testing.expectError(error.InvalidGlob, validate("ab*c*"));
|
||||||
|
try testing.expectError(error.InvalidGlob, validate("*ab*c"));
|
||||||
|
try testing.expectError(error.InvalidGlob, validate("ab*c"));
|
||||||
|
try testing.expectError(error.InvalidGlob, validate("a*bc*"));
|
||||||
|
try testing.expectError(error.InvalidGlob, validate("**a"));
|
||||||
|
try testing.expectError(error.InvalidGlob, validate("abc**"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true if s is matched by glob.
|
||||||
|
/// Asserts that the glob is valid, see `validate()`.
|
||||||
|
pub fn match(s: []const u8, glob: []const u8) bool {
|
||||||
|
if (std.debug.runtime_safety) {
|
||||||
|
validate(glob) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glob.len == 1) {
|
||||||
|
return glob[0] == '*' or mem.eql(u8, s, glob);
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffix_match = glob[0] == '*';
|
||||||
|
const prefix_match = glob[glob.len - 1] == '*';
|
||||||
|
|
||||||
|
if (suffix_match and prefix_match) {
|
||||||
|
return mem.indexOf(u8, s, glob[1 .. glob.len - 1]) != null;
|
||||||
|
} else if (suffix_match) {
|
||||||
|
return mem.endsWith(u8, s, glob[1..]);
|
||||||
|
} else if (prefix_match) {
|
||||||
|
return mem.startsWith(u8, s, glob[0 .. glob.len - 1]);
|
||||||
|
} else {
|
||||||
|
return mem.eql(u8, s, glob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test match {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
try testing.expect(match("", "*"));
|
||||||
|
|
||||||
|
try testing.expect(match("a", "*"));
|
||||||
|
try testing.expect(match("a", "*a*"));
|
||||||
|
try testing.expect(match("a", "a*"));
|
||||||
|
try testing.expect(match("a", "*a"));
|
||||||
|
try testing.expect(match("a", "a"));
|
||||||
|
|
||||||
|
try testing.expect(!match("a", "b"));
|
||||||
|
try testing.expect(!match("a", "*b*"));
|
||||||
|
try testing.expect(!match("a", "b*"));
|
||||||
|
try testing.expect(!match("a", "*b"));
|
||||||
|
|
||||||
|
try testing.expect(match("ab", "*"));
|
||||||
|
try testing.expect(match("ab", "*a*"));
|
||||||
|
try testing.expect(match("ab", "*b*"));
|
||||||
|
try testing.expect(match("ab", "a*"));
|
||||||
|
try testing.expect(match("ab", "*b"));
|
||||||
|
try testing.expect(match("ab", "*ab*"));
|
||||||
|
try testing.expect(match("ab", "ab*"));
|
||||||
|
try testing.expect(match("ab", "*ab"));
|
||||||
|
try testing.expect(match("ab", "ab"));
|
||||||
|
|
||||||
|
try testing.expect(!match("ab", "b*"));
|
||||||
|
try testing.expect(!match("ab", "*a"));
|
||||||
|
try testing.expect(!match("ab", "*c*"));
|
||||||
|
try testing.expect(!match("ab", "c*"));
|
||||||
|
try testing.expect(!match("ab", "*c"));
|
||||||
|
try testing.expect(!match("ab", "ac"));
|
||||||
|
try testing.expect(!match("ab", "*ac*"));
|
||||||
|
try testing.expect(!match("ab", "ac*"));
|
||||||
|
try testing.expect(!match("ab", "*ac"));
|
||||||
|
|
||||||
|
try testing.expect(match("abc", "*"));
|
||||||
|
try testing.expect(match("abc", "*a*"));
|
||||||
|
try testing.expect(match("abc", "*b*"));
|
||||||
|
try testing.expect(match("abc", "*c*"));
|
||||||
|
try testing.expect(match("abc", "a*"));
|
||||||
|
try testing.expect(match("abc", "*c"));
|
||||||
|
try testing.expect(match("abc", "*ab*"));
|
||||||
|
try testing.expect(match("abc", "ab*"));
|
||||||
|
try testing.expect(match("abc", "*bc*"));
|
||||||
|
try testing.expect(match("abc", "*bc"));
|
||||||
|
try testing.expect(match("abc", "*abc*"));
|
||||||
|
try testing.expect(match("abc", "abc*"));
|
||||||
|
try testing.expect(match("abc", "*abc"));
|
||||||
|
try testing.expect(match("abc", "abc"));
|
||||||
|
|
||||||
|
try testing.expect(!match("abc", "*a"));
|
||||||
|
try testing.expect(!match("abc", "*b"));
|
||||||
|
try testing.expect(!match("abc", "b*"));
|
||||||
|
try testing.expect(!match("abc", "c*"));
|
||||||
|
try testing.expect(!match("abc", "*ab"));
|
||||||
|
try testing.expect(!match("abc", "bc*"));
|
||||||
|
try testing.expect(!match("abc", "*d*"));
|
||||||
|
try testing.expect(!match("abc", "d*"));
|
||||||
|
try testing.expect(!match("abc", "*d"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns .lt if a is less general than b.
|
||||||
|
/// Returns .gt if a is more general than b.
|
||||||
|
/// Returns .eq if a and b are equally general.
|
||||||
|
/// Both a and b must be valid globs, see `validate()`.
|
||||||
|
pub fn order(a: []const u8, b: []const u8) std.math.Order {
|
||||||
|
if (std.debug.runtime_safety) {
|
||||||
|
validate(a) catch unreachable;
|
||||||
|
validate(b) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mem.eql(u8, a, "*") and mem.eql(u8, b, "*")) {
|
||||||
|
return .eq;
|
||||||
|
} else if (mem.eql(u8, a, "*")) {
|
||||||
|
return .gt;
|
||||||
|
} else if (mem.eql(u8, b, "*")) {
|
||||||
|
return .lt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const count_a = @as(u2, @boolToInt(a[0] == '*')) + @boolToInt(a[a.len - 1] == '*');
|
||||||
|
const count_b = @as(u2, @boolToInt(b[0] == '*')) + @boolToInt(b[b.len - 1] == '*');
|
||||||
|
|
||||||
|
if (count_a == 0 and count_b == 0) {
|
||||||
|
return .eq;
|
||||||
|
} else if (count_a == count_b) {
|
||||||
|
// This may look backwards since e.g. "c*" is more general than "cc*"
|
||||||
|
return std.math.order(b.len, a.len);
|
||||||
|
} else {
|
||||||
|
return std.math.order(count_a, count_b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test order {
|
||||||
|
const testing = std.testing;
|
||||||
|
const Order = std.math.Order;
|
||||||
|
|
||||||
|
try testing.expectEqual(Order.eq, order("*", "*"));
|
||||||
|
try testing.expectEqual(Order.eq, order("*a*", "*b*"));
|
||||||
|
try testing.expectEqual(Order.eq, order("a*", "*b"));
|
||||||
|
try testing.expectEqual(Order.eq, order("*a", "*b"));
|
||||||
|
try testing.expectEqual(Order.eq, order("*a", "b*"));
|
||||||
|
try testing.expectEqual(Order.eq, order("a*", "b*"));
|
||||||
|
|
||||||
|
const descending = [_][]const u8{
|
||||||
|
"*",
|
||||||
|
"*a*",
|
||||||
|
"*b*",
|
||||||
|
"*a*",
|
||||||
|
"*ab*",
|
||||||
|
"*bab*",
|
||||||
|
"*a",
|
||||||
|
"b*",
|
||||||
|
"*b",
|
||||||
|
"*a",
|
||||||
|
"a",
|
||||||
|
"bababab",
|
||||||
|
"b",
|
||||||
|
"a",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (descending) |a, i| {
|
||||||
|
for (descending[i..]) |b| {
|
||||||
|
try testing.expect(order(a, b) != .lt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ascending = descending;
|
||||||
|
mem.reverse([]const u8, &ascending);
|
||||||
|
|
||||||
|
for (ascending) |a, i| {
|
||||||
|
for (ascending[i..]) |b| {
|
||||||
|
try testing.expect(order(a, b) != .gt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,9 +8,7 @@ function __riverctl_completion ()
|
|||||||
keyboard-group-add \
|
keyboard-group-add \
|
||||||
keyboard-group-remove \
|
keyboard-group-remove \
|
||||||
keyboard-layout \
|
keyboard-layout \
|
||||||
csd-filter-add \
|
|
||||||
exit \
|
exit \
|
||||||
float-filter-add \
|
|
||||||
focus-output \
|
focus-output \
|
||||||
focus-view \
|
focus-view \
|
||||||
input \
|
input \
|
||||||
@ -18,6 +16,9 @@ function __riverctl_completion ()
|
|||||||
list-input-configs \
|
list-input-configs \
|
||||||
move \
|
move \
|
||||||
resize \
|
resize \
|
||||||
|
rule-add \
|
||||||
|
rule-del \
|
||||||
|
list-rules \
|
||||||
snap \
|
snap \
|
||||||
send-to-output \
|
send-to-output \
|
||||||
spawn \
|
spawn \
|
||||||
@ -61,6 +62,8 @@ function __riverctl_completion ()
|
|||||||
"focus-output"|"focus-view"|"send-to-output"|"swap") OPTS="next previous" ;;
|
"focus-output"|"focus-view"|"send-to-output"|"swap") OPTS="next previous" ;;
|
||||||
"move"|"snap") OPTS="up down left right" ;;
|
"move"|"snap") OPTS="up down left right" ;;
|
||||||
"resize") OPTS="horizontal vertical" ;;
|
"resize") OPTS="horizontal vertical" ;;
|
||||||
|
"rule-add"|"rule-del") OPTS="float no-float ssd csd" ;;
|
||||||
|
"list-rules") OPTS="float ssd" ;;
|
||||||
"map") OPTS="-release -repeat -layout" ;;
|
"map") OPTS="-release -repeat -layout" ;;
|
||||||
"unmap") OPTS="-release" ;;
|
"unmap") OPTS="-release" ;;
|
||||||
"attach-mode") OPTS="top bottom" ;;
|
"attach-mode") OPTS="top bottom" ;;
|
||||||
|
@ -12,9 +12,7 @@ end
|
|||||||
|
|
||||||
# Actions
|
# Actions
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'close' -d 'Close the focued view'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'close' -d 'Close the focued view'
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'csd-filter-add' -d 'Add app-id to the CSD filter list'
|
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'exit' -d 'Exit the compositor, terminating the Wayland session'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'exit' -d 'Exit the compositor, terminating the Wayland session'
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'float-filter-add' -d 'Add app-id to the float filter list'
|
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'focus-output' -d 'Focus the next or previous output'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'focus-output' -d 'Focus the next or previous output'
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'focus-view' -d 'Focus the next or previous view in the stack'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'focus-view' -d 'Focus the next or previous view in the stack'
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'input' -d 'Create a configuration rule for an input device'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'input' -d 'Create a configuration rule for an input device'
|
||||||
@ -49,6 +47,10 @@ complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map-switch '
|
|||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap' -d 'Remove the mapping defined by the arguments'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap' -d 'Remove the mapping defined by the arguments'
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap-pointer' -d 'Remove the pointer mapping defined by the arguments'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap-pointer' -d 'Remove the pointer mapping defined by the arguments'
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap-switch' -d 'Remove the switch mapping defined by the arguments'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap-switch' -d 'Remove the switch mapping defined by the arguments'
|
||||||
|
# Rules
|
||||||
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'rule-add' -d 'Apply an action to matching views'
|
||||||
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'rule-del' -d 'Delete a rule added with rule-add'
|
||||||
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'list-rules' -d 'Print rules in a given list'
|
||||||
# Configuration
|
# Configuration
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'attach-mode' -d 'Configure where new views should attach to the view stack'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'attach-mode' -d 'Configure where new views should attach to the view stack'
|
||||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'background-color' -d 'Set the background color'
|
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'background-color' -d 'Set the background color'
|
||||||
@ -81,6 +83,9 @@ complete -c riverctl -x -n '__fish_seen_subcommand_from unmap' -a
|
|||||||
complete -c riverctl -x -n '__fish_seen_subcommand_from attach-mode' -a 'top bottom'
|
complete -c riverctl -x -n '__fish_seen_subcommand_from attach-mode' -a 'top bottom'
|
||||||
complete -c riverctl -x -n '__fish_seen_subcommand_from focus-follows-cursor' -a 'disabled normal always'
|
complete -c riverctl -x -n '__fish_seen_subcommand_from focus-follows-cursor' -a 'disabled normal always'
|
||||||
complete -c riverctl -x -n '__fish_seen_subcommand_from set-cursor-warp' -a 'disabled on-output-change on-focus-change'
|
complete -c riverctl -x -n '__fish_seen_subcommand_from set-cursor-warp' -a 'disabled on-output-change on-focus-change'
|
||||||
|
complete -c riverctl -x -n '__fish_seen_subcommand_from rule-add' -a 'float no-float ssd csd'
|
||||||
|
complete -c riverctl -x -n '__fish_seen_subcommand_from rule-del' -a 'float no-float ssd csd'
|
||||||
|
complete -c riverctl -x -n '__fish_seen_subcommand_from list-rules' -a 'float ssd'
|
||||||
|
|
||||||
# Subcommands for 'input'
|
# Subcommands for 'input'
|
||||||
complete -c riverctl -x -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 2' -a "(__riverctl_list_input_devices)"
|
complete -c riverctl -x -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 2' -a "(__riverctl_list_input_devices)"
|
||||||
|
@ -9,9 +9,7 @@ _riverctl_subcommands()
|
|||||||
riverctl_subcommands=(
|
riverctl_subcommands=(
|
||||||
# Actions
|
# Actions
|
||||||
'close:Close the focused view'
|
'close:Close the focused view'
|
||||||
'csd-filter-add:Add app-id to the CSD filter list'
|
|
||||||
'exit:Exit the compositor, terminating the Wayland session'
|
'exit:Exit the compositor, terminating the Wayland session'
|
||||||
'float-filter-add:Add app-id to the float filter list'
|
|
||||||
'focus-output:Focus the next or previous output'
|
'focus-output:Focus the next or previous output'
|
||||||
'focus-view:Focus the next or previous view in the stack'
|
'focus-view:Focus the next or previous view in the stack'
|
||||||
'move:Move the focused view in the specified direction'
|
'move:Move the focused view in the specified direction'
|
||||||
@ -43,6 +41,10 @@ _riverctl_subcommands()
|
|||||||
'unmap:Remove the mapping defined by the arguments'
|
'unmap:Remove the mapping defined by the arguments'
|
||||||
'unmap-pointer:Remove the pointer mapping defined by the arguments'
|
'unmap-pointer:Remove the pointer mapping defined by the arguments'
|
||||||
'unmap-switch:Remove the switch mapping defined by the arguments'
|
'unmap-switch:Remove the switch mapping defined by the arguments'
|
||||||
|
# Rules
|
||||||
|
'rule-add:Apply an action to matching views'
|
||||||
|
'rule-del:Delete a rule added with rule-add'
|
||||||
|
'list-rules:Print rules in a given list'
|
||||||
# Configuration
|
# Configuration
|
||||||
'attach-mode:Configure where new views should attach to the view stack'
|
'attach-mode:Configure where new views should attach to the view stack'
|
||||||
'background-color:Set the background color'
|
'background-color:Set the background color'
|
||||||
@ -181,6 +183,9 @@ _riverctl()
|
|||||||
focus-follows-cursor) _alternative 'arguments:args:(disabled normal always)' ;;
|
focus-follows-cursor) _alternative 'arguments:args:(disabled normal always)' ;;
|
||||||
set-cursor-warp) _alternative 'arguments:args:(disabled on-output-change on-focus-change)' ;;
|
set-cursor-warp) _alternative 'arguments:args:(disabled on-output-change on-focus-change)' ;;
|
||||||
hide-cursor) _riverctl_hide_cursor ;;
|
hide-cursor) _riverctl_hide_cursor ;;
|
||||||
|
rule-add) _alternative 'arguments:args:(float no-float ssd csd)' ;;
|
||||||
|
rule-del) _alternative 'arguments:args:(float no-float ssd csd)' ;;
|
||||||
|
list-rules) _alternative 'arguments:args:(float ssd)' ;;
|
||||||
*) return 0 ;;
|
*) return 0 ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
@ -28,28 +28,9 @@ over the Wayland protocol.
|
|||||||
*close*
|
*close*
|
||||||
Close the focused view.
|
Close the focused view.
|
||||||
|
|
||||||
*csd-filter-add* *app-id*|*title* _pattern_
|
|
||||||
Add _pattern_ to the CSD filter list. Views with this _pattern_ are told to
|
|
||||||
use client side decoration instead of the default server side decoration.
|
|
||||||
Note that this affects new views as well as already existing ones. Title
|
|
||||||
updates are not taken into account.
|
|
||||||
|
|
||||||
*csd-filter-remove* *app-id*|*title* _pattern_
|
|
||||||
Remove _pattern_ from the CSD filter list. Note that this affects new views
|
|
||||||
as well as already existing ones.
|
|
||||||
|
|
||||||
*exit*
|
*exit*
|
||||||
Exit the compositor, terminating the Wayland session.
|
Exit the compositor, terminating the Wayland session.
|
||||||
|
|
||||||
*float-filter-add* *app-id*|*title* _pattern_
|
|
||||||
Add a pattern to the float filter list. Note that this affects only new
|
|
||||||
views, not already existing ones. Title updates are also not taken into
|
|
||||||
account.
|
|
||||||
|
|
||||||
*float-filter-remove* *app-id*|*title* _pattern_
|
|
||||||
Remove an app-id or title from the float filter list. Note that this
|
|
||||||
affects only new views, not already existing ones.
|
|
||||||
|
|
||||||
*focus-output* *next*|*previous*|*up*|*right*|*down*|*left*|_name_
|
*focus-output* *next*|*previous*|*up*|*right*|*down*|*left*|_name_
|
||||||
Focus the next or previous output, the closest output in any direction
|
Focus the next or previous output, the closest output in any direction
|
||||||
or an output by name.
|
or an output by name.
|
||||||
@ -192,18 +173,18 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
|
|||||||
*enter-mode* _name_
|
*enter-mode* _name_
|
||||||
Switch to given mode if it exists.
|
Switch to given mode if it exists.
|
||||||
|
|
||||||
*map* [_-release_|_-repeat_|_-layout_ _index_] _mode_ _modifiers_ _key_ _command_
|
*map* [*-release*|*-repeat*|*-layout* _index_] _mode_ _modifiers_ _key_ _command_
|
||||||
Run _command_ when _key_ is pressed while _modifiers_ are held down
|
Run _command_ when _key_ is pressed while _modifiers_ are held down
|
||||||
and in the specified _mode_.
|
and in the specified _mode_.
|
||||||
|
|
||||||
- _-release_: if passed activate on key release instead of key press
|
- *-release*: if passed activate on key release instead of key press
|
||||||
- _-repeat_: if passed activate repeatedly until key release; may not
|
- *-repeat*: if passed activate repeatedly until key release; may not
|
||||||
be used with -release
|
be used with *-release*
|
||||||
- _-layout_: if passed, a specific layout is pinned to the mapping.
|
- *-layout*: if passed, a specific layout is pinned to the mapping.
|
||||||
When the mapping is checked against a pressed key, this layout is
|
When the mapping is checked against a pressed key, this layout is
|
||||||
used to translate the key independent of the active layout
|
used to translate the key independent of the active layout
|
||||||
- _index_: zero-based index of a layout set with the *keyboard-layout*
|
- _index_: zero-based index of a layout set with the *keyboard-layout*
|
||||||
command. If the index is out of range, the _-layout_ option will
|
command. If the index is out of range, the *-layout* option will
|
||||||
have no effect
|
have no effect
|
||||||
- _mode_: name of the mode for which to create the mapping
|
- _mode_: name of the mode for which to create the mapping
|
||||||
- _modifiers_: one or more of the modifiers listed above, separated
|
- _modifiers_: one or more of the modifiers listed above, separated
|
||||||
@ -239,10 +220,10 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
|
|||||||
- off
|
- off
|
||||||
- _command_: any command that may be run with riverctl
|
- _command_: any command that may be run with riverctl
|
||||||
|
|
||||||
*unmap* [_-release_] _mode_ _modifiers_ _key_
|
*unmap* [*-release*] _mode_ _modifiers_ _key_
|
||||||
Remove the mapping defined by the arguments:
|
Remove the mapping defined by the arguments:
|
||||||
|
|
||||||
- _-release_: if passed unmap the key release instead of the key press
|
- *-release*: if passed unmap the key release instead of the key press
|
||||||
- _mode_: name of the mode for which to remove the mapping
|
- _mode_: name of the mode for which to remove the mapping
|
||||||
- _modifiers_: one or more of the modifiers listed above, separated
|
- _modifiers_: one or more of the modifiers listed above, separated
|
||||||
by a plus sign (+).
|
by a plus sign (+).
|
||||||
@ -263,6 +244,65 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
|
|||||||
- _lid_|_tablet_: the switch for which to remove the mapping
|
- _lid_|_tablet_: the switch for which to remove the mapping
|
||||||
- _state_: a state as listed above
|
- _state_: a state as listed above
|
||||||
|
|
||||||
|
## RULES
|
||||||
|
|
||||||
|
Rules match either the app-id and title of views against a _glob_ pattern.
|
||||||
|
A _glob_ is a string that may optionally have an _\*_ at the beginning and/or
|
||||||
|
end. A _\*_ in a _glob_ matches zero or more arbitrary characters in the
|
||||||
|
app-id or title.
|
||||||
|
|
||||||
|
For example, _abc_ is matched by _a\*_, _\*a\*_, _\*b\*_, _\*c_, _abc_, and
|
||||||
|
_\*_ but not matched by _\*a_, _b\*_, _\*b_, _\*c_, or _ab_. Note that _\*_
|
||||||
|
matches everything while _\*\*_ and the empty string are invalid.
|
||||||
|
|
||||||
|
*rule-add* _action_ [*-app-id* _glob_|*-title* _glob_]
|
||||||
|
Add a rule that applies an _action_ to views with *app-id* and *title*
|
||||||
|
matched by the respective _glob_. Omitting *-app-id* or *-title*
|
||||||
|
is equivalent to passing *-app-id* _\*_ or *-title* _\*_.
|
||||||
|
|
||||||
|
The supported _action_ types are:
|
||||||
|
|
||||||
|
- *float*: Make the view floating. Applies only to new views.
|
||||||
|
- *no-float*: Don't make the view floating. Applies only to
|
||||||
|
new views.
|
||||||
|
- *ssd*: Use server-side decorations for the view. Applies to new
|
||||||
|
and existing views.
|
||||||
|
- *csd*: Use client-side decorations for the view. 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* rules.
|
||||||
|
|
||||||
|
If multiple rules in a list match a given view the most specific
|
||||||
|
rule will be applied. For example with the following rules
|
||||||
|
```
|
||||||
|
action app-id title
|
||||||
|
ssd foo bar
|
||||||
|
csd foo *
|
||||||
|
csd * bar
|
||||||
|
ssd * baz
|
||||||
|
```
|
||||||
|
a view with app-id 'foo' and title 'bar' would get ssd despite matching
|
||||||
|
two csd rules as the first rule is most specific. Furthermore a view
|
||||||
|
with app-id 'foo' and title 'baz' would get csd despite matching the
|
||||||
|
last rule in the list since app-id specificity takes priority over
|
||||||
|
title specificity.
|
||||||
|
|
||||||
|
If a view is not matched by any rule, river will respect the csd/ssd
|
||||||
|
wishes of the client and may start the view floating based on simple
|
||||||
|
heuristics intended to catch popup-like views.
|
||||||
|
|
||||||
|
*rule-del* _action_ [*-app-id* _glob_|*-title* _glob_]
|
||||||
|
Delete a rule created using *rule-add* with the given arguments.
|
||||||
|
|
||||||
|
*list-rules* *float*|*ssd*
|
||||||
|
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
|
||||||
|
has an effect on a given view.
|
||||||
|
|
||||||
## CONFIGURATION
|
## CONFIGURATION
|
||||||
|
|
||||||
*attach-mode* *top*|*bottom*
|
*attach-mode* *top*|*bottom*
|
||||||
|
@ -18,13 +18,14 @@ const Self = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
|
const globber = @import("globber");
|
||||||
const xkb = @import("xkbcommon");
|
const xkb = @import("xkbcommon");
|
||||||
|
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
const Server = @import("Server.zig");
|
const Server = @import("Server.zig");
|
||||||
const Mode = @import("Mode.zig");
|
const Mode = @import("Mode.zig");
|
||||||
const View = @import("View.zig");
|
const RuleList = @import("RuleList.zig");
|
||||||
|
|
||||||
pub const AttachMode = enum {
|
pub const AttachMode = enum {
|
||||||
top,
|
top,
|
||||||
@ -72,13 +73,8 @@ mode_to_id: std.StringHashMap(u32),
|
|||||||
/// All user-defined keymap modes, indexed by mode id
|
/// All user-defined keymap modes, indexed by mode id
|
||||||
modes: std.ArrayListUnmanaged(Mode),
|
modes: std.ArrayListUnmanaged(Mode),
|
||||||
|
|
||||||
/// Sets of app_ids and titles which will be started floating
|
float_rules: RuleList = .{},
|
||||||
float_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
|
ssd_rules: RuleList = .{},
|
||||||
float_filter_titles: std.StringHashMapUnmanaged(void) = .{},
|
|
||||||
|
|
||||||
/// Sets of app_ids and titles which are allowed to use client side decorations
|
|
||||||
csd_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
|
|
||||||
csd_filter_titles: std.StringHashMapUnmanaged(void) = .{},
|
|
||||||
|
|
||||||
/// The selected focus_follows_cursor mode
|
/// The selected focus_follows_cursor mode
|
||||||
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
|
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
|
||||||
@ -152,64 +148,11 @@ pub fn deinit(self: *Self) void {
|
|||||||
for (self.modes.items) |*mode| mode.deinit();
|
for (self.modes.items) |*mode| mode.deinit();
|
||||||
self.modes.deinit(util.gpa);
|
self.modes.deinit(util.gpa);
|
||||||
|
|
||||||
{
|
self.float_rules.deinit();
|
||||||
var it = self.float_filter_app_ids.keyIterator();
|
self.ssd_rules.deinit();
|
||||||
while (it.next()) |key| util.gpa.free(key.*);
|
|
||||||
self.float_filter_app_ids.deinit(util.gpa);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = self.float_filter_titles.keyIterator();
|
|
||||||
while (it.next()) |key| util.gpa.free(key.*);
|
|
||||||
self.float_filter_titles.deinit(util.gpa);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = self.csd_filter_app_ids.keyIterator();
|
|
||||||
while (it.next()) |key| util.gpa.free(key.*);
|
|
||||||
self.csd_filter_app_ids.deinit(util.gpa);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var it = self.csd_filter_titles.keyIterator();
|
|
||||||
while (it.next()) |key| util.gpa.free(key.*);
|
|
||||||
self.csd_filter_titles.deinit(util.gpa);
|
|
||||||
}
|
|
||||||
|
|
||||||
util.gpa.free(self.default_layout_namespace);
|
util.gpa.free(self.default_layout_namespace);
|
||||||
|
|
||||||
self.keymap.unref();
|
self.keymap.unref();
|
||||||
self.xkb_context.unref();
|
self.xkb_context.unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shouldFloat(self: Self, view: *View) bool {
|
|
||||||
if (view.getAppId()) |app_id| {
|
|
||||||
if (self.float_filter_app_ids.contains(std.mem.span(app_id))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (view.getTitle()) |title| {
|
|
||||||
if (self.float_filter_titles.contains(std.mem.span(title))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn csdAllowed(self: Self, view: *View) bool {
|
|
||||||
if (view.getAppId()) |app_id| {
|
|
||||||
if (self.csd_filter_app_ids.contains(std.mem.span(app_id))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (view.getTitle()) |title| {
|
|
||||||
if (self.csd_filter_titles.contains(std.mem.span(title))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
@ -877,7 +877,7 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64,
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Modify the pending box, taking constraints into account
|
// Modify the pending box, taking constraints into account
|
||||||
const border_width = if (data.view.pending.borders) server.config.border_width else 0;
|
const border_width = if (data.view.pending.ssd) server.config.border_width else 0;
|
||||||
|
|
||||||
var output_width: i32 = undefined;
|
var output_width: i32 = undefined;
|
||||||
var output_height: i32 = undefined;
|
var output_height: i32 = undefined;
|
||||||
|
@ -132,7 +132,7 @@ pub fn apply(self: *Self, layout: *Layout) void {
|
|||||||
|
|
||||||
// Here we apply the offset to align the coords with the origin of the
|
// Here we apply the offset to align the coords with the origin of the
|
||||||
// usable area and shrink the dimensions to accommodate the border size.
|
// usable area and shrink the dimensions to accommodate the border size.
|
||||||
const border_width = if (view.inflight.borders) server.config.border_width else 0;
|
const border_width = if (view.inflight.ssd) server.config.border_width else 0;
|
||||||
view.inflight.box = .{
|
view.inflight.box = .{
|
||||||
.x = proposed.x + output.usable_box.x + border_width,
|
.x = proposed.x + output.usable_box.x + border_width,
|
||||||
.y = proposed.y + output.usable_box.y + border_width,
|
.y = proposed.y + output.usable_box.y + border_width,
|
||||||
|
106
river/RuleList.zig
Normal file
106
river/RuleList.zig
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// This file is part of river, a dynamic tiling wayland compositor.
|
||||||
|
//
|
||||||
|
// Copyright 2023 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 RuleList = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
|
||||||
|
const globber = @import("globber");
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
const View = @import("View.zig");
|
||||||
|
|
||||||
|
const Rule = struct {
|
||||||
|
app_id_glob: []const u8,
|
||||||
|
title_glob: []const u8,
|
||||||
|
value: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Ordered from most specific to most general.
|
||||||
|
/// Ordered first by app-id generality then by title generality.
|
||||||
|
rules: std.ArrayListUnmanaged(Rule) = .{},
|
||||||
|
|
||||||
|
pub fn deinit(list: *RuleList) void {
|
||||||
|
for (list.rules.items) |rule| {
|
||||||
|
util.gpa.free(rule.app_id_glob);
|
||||||
|
util.gpa.free(rule.title_glob);
|
||||||
|
}
|
||||||
|
list.rules.deinit(util.gpa);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(list: *RuleList, rule: Rule) error{OutOfMemory}!void {
|
||||||
|
const index = for (list.rules.items) |*existing, i| {
|
||||||
|
if (mem.eql(u8, rule.app_id_glob, existing.app_id_glob) and
|
||||||
|
mem.eql(u8, rule.title_glob, existing.title_glob))
|
||||||
|
{
|
||||||
|
existing.value = rule.value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (globber.order(rule.app_id_glob, existing.app_id_glob)) {
|
||||||
|
.lt => break i,
|
||||||
|
.eq => {
|
||||||
|
if (globber.order(rule.title_glob, existing.title_glob) == .lt) {
|
||||||
|
break i;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.gt => {},
|
||||||
|
}
|
||||||
|
} else list.rules.items.len;
|
||||||
|
|
||||||
|
const owned_app_id_glob = try util.gpa.dupe(u8, rule.app_id_glob);
|
||||||
|
errdefer util.gpa.free(owned_app_id_glob);
|
||||||
|
|
||||||
|
const owned_title_glob = try util.gpa.dupe(u8, rule.title_glob);
|
||||||
|
errdefer util.gpa.free(owned_title_glob);
|
||||||
|
|
||||||
|
try list.rules.insert(util.gpa, index, .{
|
||||||
|
.app_id_glob = owned_app_id_glob,
|
||||||
|
.title_glob = owned_title_glob,
|
||||||
|
.value = rule.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn del(list: *RuleList, rule: Rule) void {
|
||||||
|
for (list.rules.items) |existing, i| {
|
||||||
|
if (mem.eql(u8, rule.app_id_glob, existing.app_id_glob) and
|
||||||
|
mem.eql(u8, rule.title_glob, existing.title_glob))
|
||||||
|
{
|
||||||
|
util.gpa.free(existing.app_id_glob);
|
||||||
|
util.gpa.free(existing.title_glob);
|
||||||
|
_ = list.rules.orderedRemove(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the value of the most specific rule matching the view.
|
||||||
|
/// Returns null if no rule matches.
|
||||||
|
pub fn match(list: *RuleList, view: *View) ?bool {
|
||||||
|
const app_id = mem.sliceTo(view.getAppId(), 0) orelse "";
|
||||||
|
const title = mem.sliceTo(view.getTitle(), 0) orelse "";
|
||||||
|
|
||||||
|
for (list.rules.items) |rule| {
|
||||||
|
if (globber.match(app_id, rule.app_id_glob) and
|
||||||
|
globber.match(title, rule.title_glob))
|
||||||
|
{
|
||||||
|
return rule.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
@ -72,13 +72,13 @@ pub const State = struct {
|
|||||||
float: bool = false,
|
float: bool = false,
|
||||||
fullscreen: bool = false,
|
fullscreen: bool = false,
|
||||||
urgent: bool = false,
|
urgent: bool = false,
|
||||||
borders: bool = true,
|
ssd: bool = false,
|
||||||
resizing: bool = false,
|
resizing: bool = false,
|
||||||
|
|
||||||
/// Modify the x/y of the given state by delta_x/delta_y, clamping to the
|
/// Modify the x/y of the given state by delta_x/delta_y, clamping to the
|
||||||
/// bounds of the output.
|
/// bounds of the output.
|
||||||
pub fn move(state: *State, delta_x: i32, delta_y: i32) void {
|
pub fn move(state: *State, delta_x: i32, delta_y: i32) void {
|
||||||
const border_width = if (state.borders) server.config.border_width else 0;
|
const border_width = if (state.ssd) server.config.border_width else 0;
|
||||||
|
|
||||||
var output_width: i32 = math.maxInt(i32);
|
var output_width: i32 = math.maxInt(i32);
|
||||||
var output_height: i32 = math.maxInt(i32);
|
var output_height: i32 = math.maxInt(i32);
|
||||||
@ -106,7 +106,7 @@ pub const State = struct {
|
|||||||
var output_height: i32 = undefined;
|
var output_height: i32 = undefined;
|
||||||
output.wlr_output.effectiveResolution(&output_width, &output_height);
|
output.wlr_output.effectiveResolution(&output_width, &output_height);
|
||||||
|
|
||||||
const border_width = if (state.borders) server.config.border_width else 0;
|
const border_width = if (state.ssd) server.config.border_width else 0;
|
||||||
state.box.width = math.min(state.box.width, output_width - (2 * border_width));
|
state.box.width = math.min(state.box.width, output_width - (2 * border_width));
|
||||||
state.box.height = math.min(state.box.height, output_height - (2 * border_width));
|
state.box.height = math.min(state.box.height, output_height - (2 * border_width));
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ pub fn updateCurrent(view: *Self) void {
|
|||||||
view.tree.node.setPosition(box.x, box.y);
|
view.tree.node.setPosition(box.x, box.y);
|
||||||
view.popup_tree.node.setPosition(box.x, box.y);
|
view.popup_tree.node.setPosition(box.x, box.y);
|
||||||
|
|
||||||
const enable_borders = view.current.borders and !view.current.fullscreen;
|
const enable_borders = view.current.ssd and !view.current.fullscreen;
|
||||||
|
|
||||||
const border_width: c_int = config.border_width;
|
const border_width: c_int = config.border_width;
|
||||||
view.borders.left.node.setEnabled(enable_borders);
|
view.borders.left.node.setEnabled(enable_borders);
|
||||||
@ -428,7 +428,12 @@ pub fn map(view: *Self) !void {
|
|||||||
|
|
||||||
view.foreign_toplevel_handle.map();
|
view.foreign_toplevel_handle.map();
|
||||||
|
|
||||||
view.pending.borders = !server.config.csdAllowed(view);
|
if (server.config.float_rules.match(view)) |float| {
|
||||||
|
view.pending.float = float;
|
||||||
|
}
|
||||||
|
if (server.config.ssd_rules.match(view)) |ssd| {
|
||||||
|
view.pending.ssd = ssd;
|
||||||
|
}
|
||||||
|
|
||||||
if (server.input_manager.defaultSeat().focused_output) |output| {
|
if (server.input_manager.defaultSeat().focused_output) |output| {
|
||||||
// Center the initial pending box on the output
|
// Center the initial pending box on the output
|
||||||
|
@ -42,7 +42,14 @@ pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void {
|
|||||||
wlr_decoration.events.destroy.add(&decoration.destroy);
|
wlr_decoration.events.destroy.add(&decoration.destroy);
|
||||||
wlr_decoration.events.request_mode.add(&decoration.request_mode);
|
wlr_decoration.events.request_mode.add(&decoration.request_mode);
|
||||||
|
|
||||||
handleRequestMode(&decoration.request_mode, decoration.wlr_decoration);
|
const ssd = server.config.ssd_rules.match(xdg_toplevel.view) orelse
|
||||||
|
(decoration.wlr_decoration.requested_mode != .client_side);
|
||||||
|
|
||||||
|
// TODO(wlroots): make sure this is properly batched in a single configure
|
||||||
|
// with all other initial state when wlroots makes this possible.
|
||||||
|
_ = wlr_decoration.setMode(if (ssd) .server_side else .client_side);
|
||||||
|
|
||||||
|
xdg_toplevel.view.pending.ssd = ssd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(wlroots): remove this function when updating to 0.17.0
|
// TODO(wlroots): remove this function when updating to 0.17.0
|
||||||
@ -72,9 +79,13 @@ fn handleRequestMode(
|
|||||||
const decoration = @fieldParentPtr(XdgDecoration, "request_mode", listener);
|
const decoration = @fieldParentPtr(XdgDecoration, "request_mode", listener);
|
||||||
|
|
||||||
const xdg_toplevel = @intToPtr(*XdgToplevel, decoration.wlr_decoration.surface.data);
|
const xdg_toplevel = @intToPtr(*XdgToplevel, decoration.wlr_decoration.surface.data);
|
||||||
if (server.config.csdAllowed(xdg_toplevel.view)) {
|
const view = xdg_toplevel.view;
|
||||||
_ = decoration.wlr_decoration.setMode(.client_side);
|
|
||||||
} else {
|
const ssd = server.config.ssd_rules.match(xdg_toplevel.view) orelse
|
||||||
_ = decoration.wlr_decoration.setMode(.server_side);
|
(decoration.wlr_decoration.requested_mode != .client_side);
|
||||||
|
|
||||||
|
if (view.pending.ssd != ssd) {
|
||||||
|
view.pending.ssd = ssd;
|
||||||
|
server.root.applyPending();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,7 @@ pub fn configure(self: *Self) bool {
|
|||||||
(inflight.focus != 0) == (current.focus != 0) and
|
(inflight.focus != 0) == (current.focus != 0) and
|
||||||
inflight_fullscreen == current_fullscreen and
|
inflight_fullscreen == current_fullscreen and
|
||||||
inflight_float == current_float and
|
inflight_float == current_float and
|
||||||
|
inflight.ssd == current.ssd and
|
||||||
inflight.resizing == current.resizing)
|
inflight.resizing == current.resizing)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -130,6 +131,10 @@ pub fn configure(self: *Self) bool {
|
|||||||
_ = self.xdg_toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
|
_ = self.xdg_toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.decoration) |decoration| {
|
||||||
|
_ = decoration.wlr_decoration.setMode(if (inflight.ssd) .server_side else .client_side);
|
||||||
|
}
|
||||||
|
|
||||||
_ = self.xdg_toplevel.setResizing(inflight.resizing);
|
_ = self.xdg_toplevel.setResizing(inflight.resizing);
|
||||||
|
|
||||||
// Only track configures with the transaction system if they affect the dimensions of the view.
|
// Only track configures with the transaction system if they affect the dimensions of the view.
|
||||||
@ -226,9 +231,8 @@ fn handleMap(listener: *wl.Listener(void)) void {
|
|||||||
(state.min_width == state.max_width or state.min_height == state.max_height);
|
(state.min_width == state.max_width or state.min_height == state.max_height);
|
||||||
|
|
||||||
if (self.xdg_toplevel.parent != null or has_fixed_size) {
|
if (self.xdg_toplevel.parent != null or has_fixed_size) {
|
||||||
// If the self.xdg_toplevel has a parent or has a fixed size make it float
|
// If the self.xdg_toplevel has a parent or has a fixed size make it float.
|
||||||
view.pending.float = true;
|
// This will be overwritten in View.map() if the view is matched by a rule.
|
||||||
} else if (server.config.shouldFloat(view)) {
|
|
||||||
view.pending.float = true;
|
view.pending.float = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ set_override_redirect: wl.Listener(*wlr.XwaylandSurface) =
|
|||||||
// Listeners that are only active while the view is mapped
|
// Listeners that are only active while the view is mapped
|
||||||
set_title: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetTitle),
|
set_title: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetTitle),
|
||||||
set_class: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetClass),
|
set_class: wl.Listener(*wlr.XwaylandSurface) = wl.Listener(*wlr.XwaylandSurface).init(handleSetClass),
|
||||||
|
set_decorations: wl.Listener(*wlr.XwaylandSurface) =
|
||||||
|
wl.Listener(*wlr.XwaylandSurface).init(handleSetDecorations),
|
||||||
request_fullscreen: wl.Listener(*wlr.XwaylandSurface) =
|
request_fullscreen: wl.Listener(*wlr.XwaylandSurface) =
|
||||||
wl.Listener(*wlr.XwaylandSurface).init(handleRequestFullscreen),
|
wl.Listener(*wlr.XwaylandSurface).init(handleRequestFullscreen),
|
||||||
request_minimize: wl.Listener(*wlr.XwaylandSurface.event.Minimize) =
|
request_minimize: wl.Listener(*wlr.XwaylandSurface.event.Minimize) =
|
||||||
@ -168,6 +170,7 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface:
|
|||||||
// Add listeners that are only active while mapped
|
// Add listeners that are only active while mapped
|
||||||
xwayland_surface.events.set_title.add(&self.set_title);
|
xwayland_surface.events.set_title.add(&self.set_title);
|
||||||
xwayland_surface.events.set_class.add(&self.set_class);
|
xwayland_surface.events.set_class.add(&self.set_class);
|
||||||
|
xwayland_surface.events.set_decorations.add(&self.set_decorations);
|
||||||
xwayland_surface.events.request_fullscreen.add(&self.request_fullscreen);
|
xwayland_surface.events.request_fullscreen.add(&self.request_fullscreen);
|
||||||
xwayland_surface.events.request_minimize.add(&self.request_minimize);
|
xwayland_surface.events.request_minimize.add(&self.request_minimize);
|
||||||
|
|
||||||
@ -194,12 +197,14 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface:
|
|||||||
false;
|
false;
|
||||||
|
|
||||||
if (self.xwayland_surface.parent != null or has_fixed_size) {
|
if (self.xwayland_surface.parent != null or has_fixed_size) {
|
||||||
// If the toplevel has a parent or has a fixed size make it float
|
// If the toplevel has a parent or has a fixed size make it float by default.
|
||||||
view.pending.float = true;
|
// This will be overwritten in View.map() if the view is matched by a rule.
|
||||||
} else if (server.config.shouldFloat(view)) {
|
|
||||||
view.pending.float = true;
|
view.pending.float = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will be overwritten in View.map() if the view is matched by a rule.
|
||||||
|
view.pending.ssd = !xwayland_surface.decorations.no_border;
|
||||||
|
|
||||||
view.pending.fullscreen = xwayland_surface.fullscreen;
|
view.pending.fullscreen = xwayland_surface.fullscreen;
|
||||||
|
|
||||||
view.map() catch {
|
view.map() catch {
|
||||||
@ -276,6 +281,19 @@ fn handleSetClass(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.Xwayland
|
|||||||
self.view.notifyAppId();
|
self.view.notifyAppId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handleSetDecorations(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSurface) void {
|
||||||
|
const self = @fieldParentPtr(Self, "set_decorations", listener);
|
||||||
|
const view = self.view;
|
||||||
|
|
||||||
|
const ssd = server.config.ssd_rules.match(view) orelse
|
||||||
|
!self.xwayland_surface.decorations.no_border;
|
||||||
|
|
||||||
|
if (view.pending.ssd != ssd) {
|
||||||
|
view.pending.ssd = ssd;
|
||||||
|
server.root.applyPending();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handleRequestFullscreen(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
|
fn handleRequestFullscreen(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wlr.XwaylandSurface) void {
|
||||||
const self = @fieldParentPtr(Self, "request_fullscreen", listener);
|
const self = @fieldParentPtr(Self, "request_fullscreen", listener);
|
||||||
if (self.view.pending.fullscreen != xwayland_surface.fullscreen) {
|
if (self.view.pending.fullscreen != xwayland_surface.fullscreen) {
|
||||||
|
@ -47,28 +47,32 @@ const command_impls = std.ComptimeStringMap(
|
|||||||
.{ "border-color-urgent", @import("command/config.zig").borderColorUrgent },
|
.{ "border-color-urgent", @import("command/config.zig").borderColorUrgent },
|
||||||
.{ "border-width", @import("command/config.zig").borderWidth },
|
.{ "border-width", @import("command/config.zig").borderWidth },
|
||||||
.{ "close", @import("command/close.zig").close },
|
.{ "close", @import("command/close.zig").close },
|
||||||
.{ "csd-filter-add", @import("command/filter.zig").csdFilterAdd },
|
|
||||||
.{ "csd-filter-remove", @import("command/filter.zig").csdFilterRemove },
|
|
||||||
.{ "declare-mode", @import("command/declare_mode.zig").declareMode },
|
.{ "declare-mode", @import("command/declare_mode.zig").declareMode },
|
||||||
.{ "default-layout", @import("command/layout.zig").defaultLayout },
|
.{ "default-layout", @import("command/layout.zig").defaultLayout },
|
||||||
.{ "enter-mode", @import("command/enter_mode.zig").enterMode },
|
.{ "enter-mode", @import("command/enter_mode.zig").enterMode },
|
||||||
.{ "exit", @import("command/exit.zig").exit },
|
.{ "exit", @import("command/exit.zig").exit },
|
||||||
.{ "float-filter-add", @import("command/filter.zig").floatFilterAdd },
|
|
||||||
.{ "float-filter-remove", @import("command/filter.zig").floatFilterRemove },
|
|
||||||
.{ "focus-follows-cursor", @import("command/focus_follows_cursor.zig").focusFollowsCursor },
|
.{ "focus-follows-cursor", @import("command/focus_follows_cursor.zig").focusFollowsCursor },
|
||||||
.{ "focus-output", @import("command/output.zig").focusOutput },
|
.{ "focus-output", @import("command/output.zig").focusOutput },
|
||||||
.{ "focus-previous-tags", @import("command/tags.zig").focusPreviousTags },
|
.{ "focus-previous-tags", @import("command/tags.zig").focusPreviousTags },
|
||||||
.{ "focus-view", @import("command/focus_view.zig").focusView },
|
.{ "focus-view", @import("command/focus_view.zig").focusView },
|
||||||
.{ "hide-cursor", @import("command/cursor.zig").cursor },
|
.{ "hide-cursor", @import("command/cursor.zig").cursor },
|
||||||
.{ "input", @import("command/input.zig").input },
|
.{ "input", @import("command/input.zig").input },
|
||||||
|
.{ "keyboard-group-add", @import("command/keyboard_group.zig").keyboardGroupAdd },
|
||||||
|
.{ "keyboard-group-create", @import("command/keyboard_group.zig").keyboardGroupCreate },
|
||||||
|
.{ "keyboard-group-destroy", @import("command/keyboard_group.zig").keyboardGroupDestroy },
|
||||||
|
.{ "keyboard-group-remove", @import("command/keyboard_group.zig").keyboardGroupRemove },
|
||||||
|
.{ "keyboard-layout", @import("command/keyboard.zig").keyboardLayout },
|
||||||
.{ "list-input-configs", @import("command/input.zig").listInputConfigs},
|
.{ "list-input-configs", @import("command/input.zig").listInputConfigs},
|
||||||
.{ "list-inputs", @import("command/input.zig").listInputs },
|
.{ "list-inputs", @import("command/input.zig").listInputs },
|
||||||
|
.{ "list-rules", @import("command/rule.zig").listRules},
|
||||||
.{ "map", @import("command/map.zig").map },
|
.{ "map", @import("command/map.zig").map },
|
||||||
.{ "map-pointer", @import("command/map.zig").mapPointer },
|
.{ "map-pointer", @import("command/map.zig").mapPointer },
|
||||||
.{ "map-switch", @import("command/map.zig").mapSwitch },
|
.{ "map-switch", @import("command/map.zig").mapSwitch },
|
||||||
.{ "move", @import("command/move.zig").move },
|
.{ "move", @import("command/move.zig").move },
|
||||||
.{ "output-layout", @import("command/layout.zig").outputLayout },
|
.{ "output-layout", @import("command/layout.zig").outputLayout },
|
||||||
.{ "resize", @import("command/move.zig").resize },
|
.{ "resize", @import("command/move.zig").resize },
|
||||||
|
.{ "rule-add", @import("command/rule.zig").ruleAdd },
|
||||||
|
.{ "rule-del", @import("command/rule.zig").ruleDel },
|
||||||
.{ "send-layout-cmd", @import("command/layout.zig").sendLayoutCmd },
|
.{ "send-layout-cmd", @import("command/layout.zig").sendLayoutCmd },
|
||||||
.{ "send-to-output", @import("command/output.zig").sendToOutput },
|
.{ "send-to-output", @import("command/output.zig").sendToOutput },
|
||||||
.{ "send-to-previous-tags", @import("command/tags.zig").sendToPreviousTags },
|
.{ "send-to-previous-tags", @import("command/tags.zig").sendToPreviousTags },
|
||||||
@ -89,11 +93,6 @@ const command_impls = std.ComptimeStringMap(
|
|||||||
.{ "unmap-switch", @import("command/map.zig").unmapSwitch },
|
.{ "unmap-switch", @import("command/map.zig").unmapSwitch },
|
||||||
.{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme },
|
.{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme },
|
||||||
.{ "zoom", @import("command/zoom.zig").zoom },
|
.{ "zoom", @import("command/zoom.zig").zoom },
|
||||||
.{ "keyboard-layout", @import("command/keyboard.zig").keyboardLayout },
|
|
||||||
.{ "keyboard-group-create", @import("command/keyboard_group.zig").keyboardGroupCreate },
|
|
||||||
.{ "keyboard-group-destroy", @import("command/keyboard_group.zig").keyboardGroupDestroy },
|
|
||||||
.{ "keyboard-group-add", @import("command/keyboard_group.zig").keyboardGroupAdd },
|
|
||||||
.{ "keyboard-group-remove", @import("command/keyboard_group.zig").keyboardGroupRemove },
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
@ -107,6 +106,7 @@ pub const Error = error{
|
|||||||
InvalidButton,
|
InvalidButton,
|
||||||
InvalidCharacter,
|
InvalidCharacter,
|
||||||
InvalidDirection,
|
InvalidDirection,
|
||||||
|
InvalidGlob,
|
||||||
InvalidPhysicalDirection,
|
InvalidPhysicalDirection,
|
||||||
InvalidOutputIndicator,
|
InvalidOutputIndicator,
|
||||||
InvalidOrientation,
|
InvalidOrientation,
|
||||||
@ -136,7 +136,7 @@ pub fn run(
|
|||||||
try impl_fn(seat, args, out);
|
try impl_fn(seat, args, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a short error message for the given error. Passing Error.Other is UB
|
/// Return a short error message for the given error. Passing Error.Other is invalid.
|
||||||
pub fn errToMsg(err: Error) [:0]const u8 {
|
pub fn errToMsg(err: Error) [:0]const u8 {
|
||||||
return switch (err) {
|
return switch (err) {
|
||||||
Error.NoCommand => "no command given",
|
Error.NoCommand => "no command given",
|
||||||
@ -149,6 +149,7 @@ pub fn errToMsg(err: Error) [:0]const u8 {
|
|||||||
Error.InvalidButton => "invalid button",
|
Error.InvalidButton => "invalid button",
|
||||||
Error.InvalidCharacter => "invalid character in argument",
|
Error.InvalidCharacter => "invalid character in argument",
|
||||||
Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'",
|
Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'",
|
||||||
|
Error.InvalidGlob => "invalid glob. '*' is only allowed as the first and/or last character",
|
||||||
Error.InvalidPhysicalDirection => "invalid direction. Must be 'up', 'down', 'left' or 'right'",
|
Error.InvalidPhysicalDirection => "invalid direction. Must be 'up', 'down', 'left' or 'right'",
|
||||||
Error.InvalidOutputIndicator => "invalid indicator for an output. Must be 'next', 'previous', 'up', 'down', 'left', 'right' or a valid output name",
|
Error.InvalidOutputIndicator => "invalid indicator for an output. Must be 'next', 'previous', 'up', 'down', 'left', 'right' or a valid output name",
|
||||||
Error.InvalidOrientation => "invalid orientation. Must be 'horizontal', or 'vertical'",
|
Error.InvalidOrientation => "invalid orientation. Must be 'horizontal', or 'vertical'",
|
||||||
|
@ -1,147 +0,0 @@
|
|||||||
// This file is part of river, a dynamic tiling wayland compositor.
|
|
||||||
//
|
|
||||||
// Copyright 2020 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 std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const mem = std.mem;
|
|
||||||
|
|
||||||
const server = &@import("../main.zig").server;
|
|
||||||
const util = @import("../util.zig");
|
|
||||||
|
|
||||||
const View = @import("../View.zig");
|
|
||||||
const Error = @import("../command.zig").Error;
|
|
||||||
const Seat = @import("../Seat.zig");
|
|
||||||
|
|
||||||
const FilterKind = enum {
|
|
||||||
@"app-id",
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn floatFilterAdd(
|
|
||||||
_: *Seat,
|
|
||||||
args: []const [:0]const u8,
|
|
||||||
_: *?[]const u8,
|
|
||||||
) Error!void {
|
|
||||||
if (args.len < 3) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 3) return Error.TooManyArguments;
|
|
||||||
|
|
||||||
const kind = std.meta.stringToEnum(FilterKind, args[1]) orelse return Error.UnknownOption;
|
|
||||||
const map = switch (kind) {
|
|
||||||
.@"app-id" => &server.config.float_filter_app_ids,
|
|
||||||
.title => &server.config.float_filter_titles,
|
|
||||||
};
|
|
||||||
|
|
||||||
const key = args[2];
|
|
||||||
const gop = try map.getOrPut(util.gpa, key);
|
|
||||||
if (gop.found_existing) return;
|
|
||||||
errdefer assert(map.remove(key));
|
|
||||||
gop.key_ptr.* = try util.gpa.dupe(u8, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn floatFilterRemove(
|
|
||||||
_: *Seat,
|
|
||||||
args: []const [:0]const u8,
|
|
||||||
_: *?[]const u8,
|
|
||||||
) Error!void {
|
|
||||||
if (args.len < 3) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 3) return Error.TooManyArguments;
|
|
||||||
|
|
||||||
const kind = std.meta.stringToEnum(FilterKind, args[1]) orelse return Error.UnknownOption;
|
|
||||||
const map = switch (kind) {
|
|
||||||
.@"app-id" => &server.config.float_filter_app_ids,
|
|
||||||
.title => &server.config.float_filter_titles,
|
|
||||||
};
|
|
||||||
|
|
||||||
const key = args[2];
|
|
||||||
if (map.fetchRemove(key)) |kv| util.gpa.free(kv.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn csdFilterAdd(
|
|
||||||
_: *Seat,
|
|
||||||
args: []const [:0]const u8,
|
|
||||||
_: *?[]const u8,
|
|
||||||
) Error!void {
|
|
||||||
if (args.len < 3) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 3) return Error.TooManyArguments;
|
|
||||||
|
|
||||||
const kind = std.meta.stringToEnum(FilterKind, args[1]) orelse return Error.UnknownOption;
|
|
||||||
const map = switch (kind) {
|
|
||||||
.@"app-id" => &server.config.csd_filter_app_ids,
|
|
||||||
.title => &server.config.csd_filter_titles,
|
|
||||||
};
|
|
||||||
|
|
||||||
const key = args[2];
|
|
||||||
const gop = try map.getOrPut(util.gpa, key);
|
|
||||||
if (gop.found_existing) return;
|
|
||||||
errdefer assert(map.remove(key));
|
|
||||||
gop.key_ptr.* = try util.gpa.dupe(u8, key);
|
|
||||||
|
|
||||||
csdFilterUpdateViews(kind, key, .add);
|
|
||||||
server.root.applyPending();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn csdFilterRemove(
|
|
||||||
_: *Seat,
|
|
||||||
args: []const [:0]const u8,
|
|
||||||
_: *?[]const u8,
|
|
||||||
) Error!void {
|
|
||||||
if (args.len < 3) return Error.NotEnoughArguments;
|
|
||||||
if (args.len > 3) return Error.TooManyArguments;
|
|
||||||
|
|
||||||
const kind = std.meta.stringToEnum(FilterKind, args[1]) orelse return Error.UnknownOption;
|
|
||||||
const map = switch (kind) {
|
|
||||||
.@"app-id" => &server.config.csd_filter_app_ids,
|
|
||||||
.title => &server.config.csd_filter_titles,
|
|
||||||
};
|
|
||||||
|
|
||||||
const key = args[2];
|
|
||||||
if (map.fetchRemove(key)) |kv| {
|
|
||||||
util.gpa.free(kv.key);
|
|
||||||
csdFilterUpdateViews(kind, key, .remove);
|
|
||||||
server.root.applyPending();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn csdFilterUpdateViews(kind: FilterKind, pattern: []const u8, operation: enum { add, remove }) void {
|
|
||||||
var it = server.root.views.iterator(.forward);
|
|
||||||
while (it.next()) |view| {
|
|
||||||
if (view.impl == .xdg_toplevel) {
|
|
||||||
if (view.impl.xdg_toplevel.decoration) |decoration| {
|
|
||||||
if (viewMatchesPattern(kind, pattern, view)) {
|
|
||||||
switch (operation) {
|
|
||||||
.add => {
|
|
||||||
_ = decoration.wlr_decoration.setMode(.client_side);
|
|
||||||
view.pending.borders = false;
|
|
||||||
},
|
|
||||||
.remove => {
|
|
||||||
_ = decoration.wlr_decoration.setMode(.server_side);
|
|
||||||
view.pending.borders = true;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn viewMatchesPattern(kind: FilterKind, pattern: []const u8, view: *View) bool {
|
|
||||||
const p = switch (kind) {
|
|
||||||
.@"app-id" => mem.span(view.getAppId()),
|
|
||||||
.title => mem.span(view.getTitle()),
|
|
||||||
} orelse return false;
|
|
||||||
|
|
||||||
return mem.eql(u8, pattern, p);
|
|
||||||
}
|
|
164
river/command/rule.zig
Normal file
164
river/command/rule.zig
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// This file is part of river, a dynamic tiling wayland compositor.
|
||||||
|
//
|
||||||
|
// Copyright 2023 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 std = @import("std");
|
||||||
|
const fmt = std.fmt;
|
||||||
|
|
||||||
|
const globber = @import("globber");
|
||||||
|
const flags = @import("flags");
|
||||||
|
|
||||||
|
const server = &@import("../main.zig").server;
|
||||||
|
const util = @import("../util.zig");
|
||||||
|
|
||||||
|
const Error = @import("../command.zig").Error;
|
||||||
|
const RuleList = @import("../RuleList.zig");
|
||||||
|
const Seat = @import("../Seat.zig");
|
||||||
|
const View = @import("../View.zig");
|
||||||
|
|
||||||
|
const Action = enum {
|
||||||
|
float,
|
||||||
|
@"no-float",
|
||||||
|
ssd,
|
||||||
|
csd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
|
||||||
|
if (args.len < 2) return Error.NotEnoughArguments;
|
||||||
|
|
||||||
|
const result = flags.parser([:0]const u8, &.{
|
||||||
|
.{ .name = "app-id", .kind = .arg },
|
||||||
|
.{ .name = "title", .kind = .arg },
|
||||||
|
}).parse(args[2..]) catch {
|
||||||
|
return error.InvalidValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.args.len > 0) return Error.TooManyArguments;
|
||||||
|
|
||||||
|
const action = std.meta.stringToEnum(Action, args[1]) orelse return Error.UnknownOption;
|
||||||
|
const app_id_glob = result.flags.@"app-id" orelse "*";
|
||||||
|
const title_glob = result.flags.title orelse "*";
|
||||||
|
|
||||||
|
try globber.validate(app_id_glob);
|
||||||
|
try globber.validate(title_glob);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
.float, .@"no-float" => {
|
||||||
|
try server.config.float_rules.add(.{
|
||||||
|
.app_id_glob = app_id_glob,
|
||||||
|
.title_glob = title_glob,
|
||||||
|
.value = (action == .float),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.ssd, .csd => {
|
||||||
|
try server.config.ssd_rules.add(.{
|
||||||
|
.app_id_glob = app_id_glob,
|
||||||
|
.title_glob = title_glob,
|
||||||
|
.value = (action == .ssd),
|
||||||
|
});
|
||||||
|
apply_ssd_rules();
|
||||||
|
server.root.applyPending();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
|
||||||
|
if (args.len < 2) return Error.NotEnoughArguments;
|
||||||
|
|
||||||
|
const result = flags.parser([:0]const u8, &.{
|
||||||
|
.{ .name = "app-id", .kind = .arg },
|
||||||
|
.{ .name = "title", .kind = .arg },
|
||||||
|
}).parse(args[2..]) catch {
|
||||||
|
return error.InvalidValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.args.len > 0) return Error.TooManyArguments;
|
||||||
|
|
||||||
|
const action = std.meta.stringToEnum(Action, args[1]) orelse return Error.UnknownOption;
|
||||||
|
const app_id_glob = result.flags.@"app-id" orelse "*";
|
||||||
|
const title_glob = result.flags.title orelse "*";
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
.float, .@"no-float" => {
|
||||||
|
server.config.float_rules.del(.{
|
||||||
|
.app_id_glob = app_id_glob,
|
||||||
|
.title_glob = title_glob,
|
||||||
|
.value = (action == .float),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.ssd, .csd => {
|
||||||
|
server.config.ssd_rules.del(.{
|
||||||
|
.app_id_glob = app_id_glob,
|
||||||
|
.title_glob = title_glob,
|
||||||
|
.value = (action == .ssd),
|
||||||
|
});
|
||||||
|
apply_ssd_rules();
|
||||||
|
server.root.applyPending();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_ssd_rules() void {
|
||||||
|
var it = server.root.views.iterator(.forward);
|
||||||
|
while (it.next()) |view| {
|
||||||
|
if (server.config.ssd_rules.match(view)) |ssd| {
|
||||||
|
view.pending.ssd = ssd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listRules(_: *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 list = std.meta.stringToEnum(enum { float, ssd }, args[1]) orelse return Error.UnknownOption;
|
||||||
|
|
||||||
|
const rules = switch (list) {
|
||||||
|
.float => server.config.float_rules.rules.items,
|
||||||
|
.ssd => server.config.ssd_rules.rules.items,
|
||||||
|
};
|
||||||
|
|
||||||
|
var action_column_max = "action".len;
|
||||||
|
var app_id_column_max = "app-id".len;
|
||||||
|
for (rules) |rule| {
|
||||||
|
const action = switch (list) {
|
||||||
|
.float => if (rule.value) "float" else "no-float",
|
||||||
|
.ssd => if (rule.value) "ssd" else "csd",
|
||||||
|
};
|
||||||
|
action_column_max = @max(action_column_max, action.len);
|
||||||
|
app_id_column_max = @max(app_id_column_max, rule.app_id_glob.len);
|
||||||
|
}
|
||||||
|
action_column_max += 2;
|
||||||
|
app_id_column_max += 2;
|
||||||
|
|
||||||
|
var buffer = std.ArrayList(u8).init(util.gpa);
|
||||||
|
const writer = buffer.writer();
|
||||||
|
|
||||||
|
try fmt.formatBuf("action", .{ .width = action_column_max, .alignment = .Left }, writer);
|
||||||
|
try fmt.formatBuf("app-id", .{ .width = app_id_column_max, .alignment = .Left }, writer);
|
||||||
|
try writer.writeAll("title\n");
|
||||||
|
|
||||||
|
for (rules) |rule| {
|
||||||
|
const action = switch (list) {
|
||||||
|
.float => if (rule.value) "float" else "no-float",
|
||||||
|
.ssd => if (rule.value) "ssd" else "csd",
|
||||||
|
};
|
||||||
|
try fmt.formatBuf(action, .{ .width = action_column_max, .alignment = .Left }, writer);
|
||||||
|
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .Left }, writer);
|
||||||
|
try writer.print("{s}\n", .{rule.title_glob});
|
||||||
|
}
|
||||||
|
|
||||||
|
out.* = buffer.toOwnedSlice();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user