diff --git a/river/Config.zig b/river/Config.zig
index 5a14529..67b9771 100644
--- a/river/Config.zig
+++ b/river/Config.zig
@@ -25,7 +25,7 @@ const util = @import("util.zig");
const Server = @import("Server.zig");
const Mode = @import("Mode.zig");
-const RuleList = @import("RuleList.zig");
+const RuleList = @import("rule_list.zig").RuleList;
pub const AttachMode = enum {
top,
@@ -73,8 +73,8 @@ mode_to_id: std.StringHashMap(u32),
/// All user-defined keymap modes, indexed by mode id
modes: std.ArrayListUnmanaged(Mode),
-float_rules: RuleList = .{},
-ssd_rules: RuleList = .{},
+float_rules: RuleList(bool) = .{},
+ssd_rules: RuleList(bool) = .{},
/// The selected focus_follows_cursor mode
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
diff --git a/river/RuleList.zig b/river/RuleList.zig
deleted file mode 100644
index db765e7..0000000
--- a/river/RuleList.zig
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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 .
-
-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;
-}
diff --git a/river/command/rule.zig b/river/command/rule.zig
index c9665af..807b55e 100644
--- a/river/command/rule.zig
+++ b/river/command/rule.zig
@@ -24,7 +24,6 @@ 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");
@@ -95,14 +94,12 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
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();
diff --git a/river/rule_list.zig b/river/rule_list.zig
new file mode 100644
index 0000000..325dcc3
--- /dev/null
+++ b/river/rule_list.zig
@@ -0,0 +1,124 @@
+// 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 .
+
+const std = @import("std");
+const mem = std.mem;
+
+const globber = @import("globber");
+const util = @import("util.zig");
+
+const View = @import("View.zig");
+
+pub fn RuleList(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ const Rule = struct {
+ app_id_glob: []const u8,
+ title_glob: []const u8,
+ value: T,
+ };
+
+ /// 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: *Self) 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: *Self, 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: *Self, rule: struct { app_id_glob: []const u8, title_glob: []const u8 }) 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: *Self, view: *View) ?T {
+ 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;
+ }
+
+ /// Returns the length of the longest globs.
+ pub fn getMaxGlobLen(self: *const Self) MaxGlobLen {
+ var app_id_len: usize = 0;
+ var title_len: usize = 0;
+ for (self.rules.items) |rule| {
+ app_id_len = @max(app_id_len, rule.app_id_glob.len);
+ title_len = @max(title_len, rule.title_glob.len);
+ }
+ return .{
+ .app_id = app_id_len,
+ .title = title_len,
+ };
+ }
+ };
+}