aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/Cargo.lock698
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/Cargo.toml12
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/TODO1
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/TODO.license9
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/contrib/example.json7
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/contrib/init.json258
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/flake.lock6
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/flake.nix12
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/package.nix18
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/resources/river-control-unstable-v1.xml85
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/resources/river-status-unstable-v1.xml148
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/cli.rs14
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs367
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs105
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/main.rs52
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs173
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs214
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs272
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs57
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs129
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs1
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs28
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs21
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs437
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs290
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs596
26 files changed, 3830 insertions, 180 deletions
diff --git a/pkgs/by-name/ri/river-mk-keymap/Cargo.lock b/pkgs/by-name/ri/river-mk-keymap/Cargo.lock
index c58253b2..725c02d7 100644
--- a/pkgs/by-name/ri/river-mk-keymap/Cargo.lock
+++ b/pkgs/by-name/ri/river-mk-keymap/Cargo.lock
@@ -12,6 +12,22 @@
version = 4
[[package]]
+name = "ab_glyph"
+version = "0.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0"
+dependencies = [
+ "ab_glyph_rasterizer",
+ "owned_ttf_parser",
+]
+
+[[package]]
+name = "ab_glyph_rasterizer"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
+
+[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -47,7 +63,7 @@ version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
- "windows-sys",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -58,7 +74,7 @@ checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
- "windows-sys",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -68,6 +84,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -114,6 +169,196 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "core-text"
+version = "20.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5"
+dependencies = [
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "dwrote"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "winapi",
+ "wio",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "float-ord"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
+
+[[package]]
+name = "font-kit"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
+dependencies = [
+ "bitflags 2.9.1",
+ "byteorder",
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "dirs",
+ "dwrote",
+ "float-ord",
+ "freetype-sys",
+ "lazy_static",
+ "libc",
+ "log",
+ "pathfinder_geometry",
+ "pathfinder_simd",
+ "walkdir",
+ "winapi",
+ "yeslogic-fontconfig-sys",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "freetype-sys"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -133,27 +378,132 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "keymaps"
-version = "1.1.1"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a522bbaa39bddd54945580e369ed37113ea96f4cb8f0322be0d5e04aa4d7293"
+checksum = "ea59e8e461942cf1d6a7ad938848d6fd2e40eb43799c21192c09226ecc86710f"
dependencies = [
"serde",
"thiserror",
]
[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+
+[[package]]
+name = "libloading"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
+dependencies = [
+ "cfg-if",
+ "windows-targets 0.53.2",
+]
+
+[[package]]
+name = "libredox"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
+dependencies = [
+ "bitflags 2.9.1",
+ "libc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
+name = "memmap2"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "owned_ttf_parser"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
+dependencies = [
+ "ttf-parser",
+]
+
+[[package]]
+name = "pathfinder_geometry"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
+dependencies = [
+ "log",
+ "pathfinder_simd",
+]
+
+[[package]]
+name = "pathfinder_simd"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -163,6 +513,15 @@ dependencies = [
]
[[package]]
+name = "quick-xml"
+version = "0.37.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -172,14 +531,70 @@ dependencies = [
]
[[package]]
+name = "redox_users"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
name = "river-mk-keymap"
version = "0.1.0"
dependencies = [
+ "ab_glyph",
"anyhow",
"clap",
+ "font-kit",
"keymaps",
+ "memmap2",
+ "rustix 1.0.7",
"serde",
"serde_json",
+ "shlex",
+ "thiserror",
+ "vte",
+ "wayland-client",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags 2.9.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.15",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags 2.9.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.9.4",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -189,6 +604,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -221,6 +651,18 @@ dependencies = [
]
[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -228,9 +670,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
-version = "2.0.103"
+version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
+checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
@@ -258,6 +700,12 @@ dependencies = [
]
[[package]]
+name = "ttf-parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
+
+[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -270,12 +718,148 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+name = "vte"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd"
+dependencies = [
+ "arrayvec",
+ "memchr",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix 0.38.44",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61"
+dependencies = [
+ "bitflags 2.9.1",
+ "rustix 0.38.44",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a"
+dependencies = [
+ "bitflags 2.9.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf"
+dependencies = [
+ "bitflags 2.9.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
- "windows-targets",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.2",
]
[[package]]
@@ -284,14 +868,30 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
+dependencies = [
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
]
[[package]]
@@ -301,43 +901,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "wio"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "yeslogic-fontconfig-sys"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd"
+dependencies = [
+ "dlib",
+ "once_cell",
+ "pkg-config",
+]
diff --git a/pkgs/by-name/ri/river-mk-keymap/Cargo.toml b/pkgs/by-name/ri/river-mk-keymap/Cargo.toml
index ef2f0499..8198738a 100644
--- a/pkgs/by-name/ri/river-mk-keymap/Cargo.toml
+++ b/pkgs/by-name/ri/river-mk-keymap/Cargo.toml
@@ -16,11 +16,21 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+ab_glyph = "0.2.29"
anyhow = "1.0.98"
clap = { version = "4.5.40", features = ["derive"] }
-keymaps = { version = "1.1.1", features = ["serde", "mouse-keys"] }
+font-kit = "0.14.3"
+keymaps = { version = "1.2.0", features = ["serde", "mouse-keys", "modifier-keys"] }
+memmap2 = "0.9.5"
+rustix = { version = "1.0.7", features = ["fs", "shm"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
+shlex = "1.3.0"
+thiserror = "2.0.12"
+vte = "0.15.0"
+wayland-client = {version = "0.31.10", default-features = false}
+wayland-protocols-wlr = { version = "0.3.8", features = ["client"] }
+wayland-scanner = {version = "0.31.6", default-features = false}
[profile.release]
lto = true
diff --git a/pkgs/by-name/ri/river-mk-keymap/TODO b/pkgs/by-name/ri/river-mk-keymap/TODO
deleted file mode 100644
index be77953e..00000000
--- a/pkgs/by-name/ri/river-mk-keymap/TODO
+++ /dev/null
@@ -1 +0,0 @@
-Look at https://github.com/stefur/flow for river wayland inclusion
diff --git a/pkgs/by-name/ri/river-mk-keymap/TODO.license b/pkgs/by-name/ri/river-mk-keymap/TODO.license
deleted file mode 100644
index eae6a84c..00000000
--- a/pkgs/by-name/ri/river-mk-keymap/TODO.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/pkgs/by-name/ri/river-mk-keymap/contrib/example.json b/pkgs/by-name/ri/river-mk-keymap/contrib/example.json
index c8673f9a..bddd61c0 100644
--- a/pkgs/by-name/ri/river-mk-keymap/contrib/example.json
+++ b/pkgs/by-name/ri/river-mk-keymap/contrib/example.json
@@ -1,5 +1,8 @@
{
- "<M-a>": {
- "command": ["focus-view", "next"]
+ "Kbad": {
+ "command": ["spawn", "/nix/store/1xfyw9c5ala73y8sayrsf98vcrr3jrww-libnotify-0.8.6/bin/notify-send hi"]
+ },
+ "Kbae": {
+ "command": ["e"]
}
}
diff --git a/pkgs/by-name/ri/river-mk-keymap/contrib/init.json b/pkgs/by-name/ri/river-mk-keymap/contrib/init.json
new file mode 100644
index 00000000..7faaeb43
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/contrib/init.json
@@ -0,0 +1,258 @@
+{
+ "<Alt+Ctrl+Super+Shift-Z>": [
+ "spawn",
+ "/nix/store/h71ca2rxlnlcyv4604ih2b2gla5ly27d-qmk-unicode-type-1.0.0/bin/qmk-unicode-type 106 65377"
+ ],
+ "<MEDIA_LOWERVOLUME>": {
+ "allow_locked": true,
+ "command": [
+ "spawn",
+ "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%-"
+ ]
+ },
+ "<MEDIA_MUTEVOLUME>": {
+ "allow_locked": true,
+ "command": [
+ "spawn",
+ "/nix/store/08bgv5x7gfhkczf0lgrpim1rw51jlxvn-mpp/bin/mpp toggle"
+ ]
+ },
+ "<MEDIA_RAISEVOLUME>": {
+ "allow_locked": true,
+ "command": [
+ "spawn",
+ "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%+"
+ ]
+ },
+ "<LEFT_SUPER>": {
+ "c": {
+ "<ENTER>": [
+ "zoom"
+ ],
+ " ": [
+ "toggle-float"
+ ],
+ "c": [
+ "close"
+ ],
+ "f": [
+ "toggle-fullscreen"
+ ],
+ "n": [
+ "swap",
+ "previous"
+ ],
+ "o": [
+ "send-to-output",
+ "next"
+ ],
+ "t": [
+ "swap",
+ "next"
+ ]
+ },
+ "f": {
+ "0": [
+ "set-focused-tags",
+ "4294967295"
+ ],
+ "1": [
+ "set-focused-tags",
+ "1"
+ ],
+ "2": [
+ "set-focused-tags",
+ "2"
+ ],
+ "3": [
+ "set-focused-tags",
+ "4"
+ ],
+ "4": [
+ "set-focused-tags",
+ "8"
+ ],
+ "5": [
+ "set-focused-tags",
+ "16"
+ ],
+ "6": [
+ "set-focused-tags",
+ "32"
+ ],
+ "7": [
+ "set-focused-tags",
+ "64"
+ ],
+ "8": [
+ "set-focused-tags",
+ "128"
+ ],
+ "9": [
+ "set-focused-tags",
+ "256"
+ ],
+ "<Ctrl-n>": [
+ "focus-output",
+ "previous"
+ ],
+ "<Ctrl-t>": [
+ "focus-output",
+ "next"
+ ],
+ "n": [
+ "focus-view",
+ "previous"
+ ],
+ "p": [
+ "focus-previous-tags"
+ ],
+ "t": [
+ "focus-view",
+ "next"
+ ]
+ },
+ "m": {
+ "l": [
+ "spawn",
+ "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%-"
+ ],
+ "m": [
+ "spawn",
+ "/nix/store/08bgv5x7gfhkczf0lgrpim1rw51jlxvn-mpp/bin/mpp toggle"
+ ],
+ "r": [
+ "spawn",
+ "/nix/store/7h9w2sycqj3i2lp61nibgv7qhvwy3pi9-wireplumber-0.5.10/bin/wpctl set-volume @DEFAULT_SINK@ 5%+"
+ ]
+ },
+ "r": {
+ "a": [
+ "spawn",
+ "/nix/store/h601phmb09d9dwwziwsim6m0r31qajr3-alacritty-0.15.1/bin/alacritty"
+ ],
+ "b": [
+ "spawn",
+ "/nix/store/k8gfhk1lglwr8k6477ygkr9hh037a4kw-tskm-0.1.0/bin/tskm open select"
+ ],
+ "k": [
+ "spawn",
+ "/nix/store/xpinf75gxhl8aglw2z7631k89iiml7rz-keepassxc-2.7.10/bin/keepassxc"
+ ],
+ "p": [
+ "spawn",
+ "/nix/store/skgvjhmqp3jbmaw70xlz86a66lg13395-screenshot_persistent/bin/screenshot_persistent"
+ ],
+ "s": [
+ "spawn",
+ "/nix/store/zvzr8cj57jhxyrzjym2rv3w95w7zw901-signal-desktop-7.56.1/bin/signal-desktop"
+ ]
+ },
+ "v": {
+ "0": [
+ "set-view-tags",
+ "4294967295"
+ ],
+ "1": [
+ "set-view-tags",
+ "1"
+ ],
+ "2": [
+ "set-view-tags",
+ "2"
+ ],
+ "3": [
+ "set-view-tags",
+ "4"
+ ],
+ "4": [
+ "set-view-tags",
+ "8"
+ ],
+ "5": [
+ "set-view-tags",
+ "16"
+ ],
+ "6": [
+ "set-view-tags",
+ "32"
+ ],
+ "7": [
+ "set-view-tags",
+ "64"
+ ],
+ "8": [
+ "set-view-tags",
+ "128"
+ ],
+ "9": [
+ "set-view-tags",
+ "256"
+ ],
+ "a": {
+ "1": [
+ "toggle-view-tags",
+ "1"
+ ],
+ "2": [
+ "toggle-view-tags",
+ "2"
+ ],
+ "3": [
+ "toggle-view-tags",
+ "4"
+ ],
+ "4": [
+ "toggle-view-tags",
+ "8"
+ ],
+ "5": [
+ "toggle-view-tags",
+ "16"
+ ],
+ "6": [
+ "toggle-view-tags",
+ "32"
+ ],
+ "7": [
+ "toggle-view-tags",
+ "64"
+ ],
+ "8": [
+ "toggle-view-tags",
+ "128"
+ ],
+ "9": [
+ "toggle-view-tags",
+ "256"
+ ]
+ },
+ "p": [
+ "send-to-previous-tags"
+ ]
+ },
+ "x": {
+ "l": [
+ "spawn",
+ "/nix/store/4gp8yj8cz3d78hn01firv7dlqf4ap1fj-lock/bin/lock"
+ ],
+ "q": [
+ "exit"
+ ]
+ }
+ },
+ "<Super-<MOUSE_LEFT>>": [
+ "move-view"
+ ],
+ "<Super-<MOUSE_RIGHT>>": [
+ "resize-view"
+ ],
+ "<Super-L>": [
+ "spawn",
+ "/nix/store/4gp8yj8cz3d78hn01firv7dlqf4ap1fj-lock/bin/lock"
+ ],
+ "<PRINTSCREEN>": [
+ "spawn",
+ "/nix/store/skgvjhmqp3jbmaw70xlz86a66lg13395-screenshot_persistent/bin/screenshot_persistent"
+ ]
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/flake.lock b/pkgs/by-name/ri/river-mk-keymap/flake.lock
index 412714b7..a267d6fb 100644
--- a/pkgs/by-name/ri/river-mk-keymap/flake.lock
+++ b/pkgs/by-name/ri/river-mk-keymap/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1749903597,
- "narHash": "sha256-jp0D4vzBcRKwNZwfY4BcWHemLGUs4JrS3X9w5k/JYDA=",
+ "lastModified": 1750731501,
+ "narHash": "sha256-Ah4qq+SbwMaGkuXCibyg+Fwn00el4KmI3XFX6htfDuk=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e",
+ "rev": "69dfebb3d175bde602f612915c5576a41b18486b",
"type": "github"
},
"original": {
diff --git a/pkgs/by-name/ri/river-mk-keymap/flake.nix b/pkgs/by-name/ri/river-mk-keymap/flake.nix
index e15e99fa..b7e2a0c4 100644
--- a/pkgs/by-name/ri/river-mk-keymap/flake.nix
+++ b/pkgs/by-name/ri/river-mk-keymap/flake.nix
@@ -17,8 +17,20 @@
outputs = {nixpkgs, ...}: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages."${system}";
+
+ nativeBuildInputs = [
+ pkgs.pkg-config
+ ];
+
+ buildInputs = [
+ pkgs.wayland
+ pkgs.libxkbcommon
+ pkgs.fontconfig
+ ];
in {
devShells."${system}".default = pkgs.mkShell {
+ inherit nativeBuildInputs buildInputs;
+
packages = with pkgs; [
cargo
clippy
diff --git a/pkgs/by-name/ri/river-mk-keymap/package.nix b/pkgs/by-name/ri/river-mk-keymap/package.nix
index 7d6d4f3a..bb3dc285 100644
--- a/pkgs/by-name/ri/river-mk-keymap/package.nix
+++ b/pkgs/by-name/ri/river-mk-keymap/package.nix
@@ -7,7 +7,13 @@
#
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{rustPlatform}:
+{
+ rustPlatform,
+ pkg-config,
+ wayland,
+ libxkbcommon,
+ fontconfig,
+}:
rustPlatform.buildRustPackage {
pname = "river-mk-keymap";
version = "0.1.0";
@@ -17,6 +23,16 @@ rustPlatform.buildRustPackage {
lockFile = ./Cargo.lock;
};
+ nativeBuildInputs = [
+ pkg-config
+ ];
+
+ buildInputs = [
+ wayland
+ libxkbcommon
+ fontconfig
+ ];
+
meta = {
mainProgram = "river-mk-keymap";
};
diff --git a/pkgs/by-name/ri/river-mk-keymap/resources/river-control-unstable-v1.xml b/pkgs/by-name/ri/river-mk-keymap/resources/river-control-unstable-v1.xml
new file mode 100644
index 00000000..aa5fc4dc
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/resources/river-control-unstable-v1.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_control_unstable_v1">
+ <copyright>
+ Copyright 2020 The River Developers
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ 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.
+ </copyright>
+
+ <interface name="zriver_control_v1" version="1">
+ <description summary="run compositor commands">
+ This interface allows clients to run compositor commands and receive a
+ success/failure response with output or a failure message respectively.
+
+ Each command is built up in a series of add_argument requests and
+ executed with a run_command request. The first argument is the command
+ to be run.
+
+ A complete list of commands should be made available in the man page of
+ the compositor.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_control object">
+ This request indicates that the client will not use the
+ river_control object any more. Objects that have been created
+ through this instance are not affected.
+ </description>
+ </request>
+
+ <request name="add_argument">
+ <description summary="add an argument to the current command">
+ Arguments are stored by the server in the order they were sent until
+ the run_command request is made.
+ </description>
+ <arg name="argument" type="string" summary="the argument to add"/>
+ </request>
+
+ <request name="run_command">
+ <description summary="run the current command">
+ Execute the command built up using the add_argument request for the
+ given seat.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ <arg name="callback" type="new_id" interface="zriver_command_callback_v1"
+ summary="callback object"/>
+ </request>
+ </interface>
+
+ <interface name="zriver_command_callback_v1" version="1">
+ <description summary="callback object">
+ This object is created by the run_command request. Exactly one of the
+ success or failure events will be sent. This object will be destroyed
+ by the compositor after one of the events is sent.
+ </description>
+
+ <event name="success" type="destructor">
+ <description summary="command successful">
+ Sent when the command has been successfully received and executed by
+ the compositor. Some commands may produce output, in which case the
+ output argument will be a non-empty string.
+ </description>
+ <arg name="output" type="string" summary="the output of the command"/>
+ </event>
+
+ <event name="failure" type="destructor">
+ <description summary="command failed">
+ Sent when the command could not be carried out. This could be due to
+ sending a non-existent command, no command, not enough arguments, too
+ many arguments, invalid arguments, etc.
+ </description>
+ <arg name="failure_message" type="string"
+ summary="a message explaining why failure occurred"/>
+ </event>
+ </interface>
+</protocol>
diff --git a/pkgs/by-name/ri/river-mk-keymap/resources/river-status-unstable-v1.xml b/pkgs/by-name/ri/river-mk-keymap/resources/river-status-unstable-v1.xml
new file mode 100644
index 00000000..e9629dde
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/resources/river-status-unstable-v1.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_status_unstable_v1">
+ <copyright>
+ Copyright 2020 The River Developers
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ 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.
+ </copyright>
+
+ <interface name="zriver_status_manager_v1" version="4">
+ <description summary="manage river status objects">
+ A global factory for objects that receive status information specific
+ to river. It could be used to implement, for example, a status bar.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_status_manager object">
+ This request indicates that the client will not use the
+ river_status_manager object any more. Objects that have been created
+ through this instance are not affected.
+ </description>
+ </request>
+
+ <request name="get_river_output_status">
+ <description summary="create an output status object">
+ This creates a new river_output_status object for the given wl_output.
+ </description>
+ <arg name="id" type="new_id" interface="zriver_output_status_v1"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="get_river_seat_status">
+ <description summary="create a seat status object">
+ This creates a new river_seat_status object for the given wl_seat.
+ </description>
+ <arg name="id" type="new_id" interface="zriver_seat_status_v1"/>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+ </interface>
+
+ <interface name="zriver_output_status_v1" version="4">
+ <description summary="track output tags and focus">
+ This interface allows clients to receive information about the current
+ windowing state of an output.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_output_status object">
+ This request indicates that the client will not use the
+ river_output_status object any more.
+ </description>
+ </request>
+
+ <event name="focused_tags">
+ <description summary="focused tags of the output">
+ Sent once binding the interface and again whenever the tag focus of
+ the output changes.
+ </description>
+ <arg name="tags" type="uint" summary="32-bit bitfield"/>
+ </event>
+
+ <event name="view_tags">
+ <description summary="tag state of an output's views">
+ Sent once on binding the interface and again whenever the tag state
+ of the output changes.
+ </description>
+ <arg name="tags" type="array" summary="array of 32-bit bitfields"/>
+ </event>
+
+ <event name="urgent_tags" since="2">
+ <description summary="tags of the output with an urgent view">
+ Sent once on binding the interface and again whenever the set of
+ tags with at least one urgent view changes.
+ </description>
+ <arg name="tags" type="uint" summary="32-bit bitfield"/>
+ </event>
+
+ <event name="layout_name" since="4">
+ <description summary="name of the layout">
+ Sent once on binding the interface should a layout name exist and again
+ whenever the name changes.
+ </description>
+ <arg name="name" type="string" summary="layout name"/>
+ </event>
+
+ <event name="layout_name_clear" since="4">
+ <description summary="name of the layout">
+ Sent when the current layout name has been removed without a new one
+ being set, for example when the active layout generator disconnects.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zriver_seat_status_v1" version="3">
+ <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
+ if the client has bound the relevant wl_output globals.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_seat_status object">
+ This request indicates that the client will not use the
+ river_seat_status object any more.
+ </description>
+ </request>
+
+ <event name="focused_output">
+ <description summary="the seat focused an output">
+ Sent on binding the interface and again whenever an output gains focus.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="unfocused_output">
+ <description summary="the seat unfocused an output">
+ Sent whenever an output loses focus.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="focused_view">
+ <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"/>
+ </event>
+
+ <event name="mode" since="3">
+ <description summary="the active mode changed">
+ Sent once on binding the interface and again whenever a new mode
+ is entered (e.g. with riverctl enter-mode foobar).
+ </description>
+ <arg name="name" type="string" summary="name of the mode"/>
+ </event>
+ </interface>
+</protocol>
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/cli.rs b/pkgs/by-name/ri/river-mk-keymap/src/cli.rs
index e3c49310..61646cfd 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/cli.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/cli.rs
@@ -16,6 +16,16 @@ use clap::Parser;
#[command(author, version, about, long_about = None)]
/// A tool to manage your key mappings for the river window manager
pub(super) struct Args {
- /// Path to mappings JSON file
- pub path: PathBuf,
+ #[command(subcommand)]
+ pub command: SubCommand,
+
+ #[arg(long, short)]
+ /// Path to mapping config JSON file
+ pub keymap: PathBuf,
+}
+
+#[derive(clap::Subcommand, Clone, Debug)]
+pub(super) enum SubCommand {
+ Init {},
+ ShowHelp {},
}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
index e948ccfe..07c41918 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
@@ -8,112 +8,289 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-use std::process::Command;
+use std::{env::current_exe, path::Path, process::Command};
-use keymaps::key_repr::{KeyValue, MediaKeyCode, MouseKeyValue};
+use anyhow::{bail, Result};
+use keymaps::key_repr::{Key, KeyValue, Keys, MediaKeyCode, ModifierKeyCode, MouseKeyValue};
+use rustix::path::Arg;
-use super::{KeyMap, MapMode};
+use super::KeyMap;
impl KeyMap {
- #[must_use]
- pub fn to_commands(self) -> Vec<Command> {
- self.0
- .iter()
- .flat_map(|(key, value)| {
- let key = key.last().expect("Will exist");
- let mods = {
- let modifiers = key.modifiers();
- let mut output = vec![];
+ /// # Errors
+ /// If impossible requests are made.
+ ///
+ /// # Panics
+ /// If internal assertions fail.
+ #[allow(clippy::too_many_lines)]
+ pub fn to_commands(self, keymap_path: &Path) -> Result<Vec<Command>> {
+ self.0.iter().try_for_each(|(keys, value)| {
+ let (prefix, last) = keys.split_at(keys.len() - 1);
+ let prefix = prefix.to_owned();
- if modifiers.alt() {
- output.push("Alt");
- }
- if modifiers.ctrl() {
- output.push("Control");
- }
- if modifiers.meta() {
- output.push("Super");
- }
- if modifiers.shift() {
- output.push("Shift");
- }
- if output.is_empty() {
- "None".to_owned()
- } else {
- output.join("+")
- }
- };
- let key_value = match key.value() {
- KeyValue::Backspace => "BackSpace".to_owned(),
- KeyValue::Enter => "Enter".to_owned(),
- KeyValue::Left => "Left".to_owned(),
- KeyValue::Right => "Right".to_owned(),
- KeyValue::Up => "Up".to_owned(),
- KeyValue::Down => "Down".to_owned(),
- KeyValue::Home => "Home".to_owned(),
- KeyValue::End => "End".to_owned(),
- KeyValue::PageUp => "Page_Up".to_owned(),
- KeyValue::PageDown => "Page_Down".to_owned(),
- KeyValue::Tab => "Tab".to_owned(),
- KeyValue::BackTab => "BackTab".to_owned(),
- KeyValue::Delete => "Delete".to_owned(),
- KeyValue::Insert => "Insert".to_owned(),
- KeyValue::F(num) => format!("F{num}"),
- KeyValue::Char(a) => a.to_string(),
- KeyValue::Null => "Null".to_owned(),
- KeyValue::Esc => "Esc".to_owned(),
- KeyValue::CapsLock => "CapsLock".to_owned(),
- KeyValue::ScrollLock => "ScrollLock".to_owned(),
- KeyValue::NumLock => "NumLock".to_owned(),
- KeyValue::PrintScreen => "Print".to_owned(),
- KeyValue::Pause => "Pause".to_owned(),
- KeyValue::Menu => "Menu".to_owned(),
- KeyValue::KeypadBegin => "KeypadBegin".to_owned(),
- KeyValue::Media(media_key_code) => match media_key_code {
- MediaKeyCode::Play => "XF86AudioPlay".to_owned(),
- MediaKeyCode::Pause => "XF86AudioPause".to_owned(),
- MediaKeyCode::PlayPause => "XF86AudioPlayPause".to_owned(),
- MediaKeyCode::Reverse => "XF86AudioReverse".to_owned(),
- MediaKeyCode::Stop => "XF86AudioStop".to_owned(),
- MediaKeyCode::FastForward => "XF86AudioFastForward".to_owned(),
- MediaKeyCode::Rewind => "XF86AudioRewind".to_owned(),
- MediaKeyCode::TrackNext => "XF86AudioTrackNext".to_owned(),
- MediaKeyCode::TrackPrevious => "XF86AudioTrackPrevious".to_owned(),
- MediaKeyCode::Record => "XF86AudioRecord".to_owned(),
- MediaKeyCode::LowerVolume => "XF86AudioLowerVolume".to_owned(),
- MediaKeyCode::RaiseVolume => "XF86AudioRaiseVolume".to_owned(),
- MediaKeyCode::MuteVolume => "XF86AudioMuteVolume".to_owned(),
- },
- KeyValue::MouseKey(mouse_key_value) => match mouse_key_value {
- MouseKeyValue::Left => "BTN_LEFT".to_owned(),
- MouseKeyValue::Right => "BTN_RIGHT".to_owned(),
- MouseKeyValue::Middle => "BTN_MIDDLE".to_owned(),
- },
- _ => todo!(),
- };
+ if value.allow_locked && !prefix.is_empty() {
+ bail!(
+ "Only single key mappings can be used \
+ in locked mode, but '{}' contains multiple ('{}').",
+ Keys::from(keys),
+ Keys::from(prefix),
+ )
+ }
- value
- .modes
- .iter()
- .map(|mode| {
- let mut riverctl = Command::new("riverctl");
- riverctl.args([value.map_mode.as_command(), mode, &mods, &key_value]);
+ if !prefix.is_empty()
+ && [
+ "<ESC>".parse().expect("hardcoded"),
+ "<BACKSPACE>".parse().expect("hardcoded"),
+ ]
+ .contains(&last[0])
+ {
+ bail!(
+ "You cannot use <ESC> or <BACKSPACE> as the final part of a \
+ prefixed mapping, as that is used to return \
+ to 'normal' or the upper mode; found in '{}'",
+ Keys::from(keys),
+ )
+ }
- riverctl.args(value.command.iter().map(String::as_str));
- riverctl
- })
- .collect::<Vec<_>>()
+ Ok(())
+ })?;
+
+ let output = self
+ .0
+ .into_iter()
+ .flat_map(|(keys, value)| {
+ let (prefix, mapping) = keys.split_at(keys.len() - 1);
+
+ let (final_mode, mut base): (Option<String>, _) =
+ prefix
+ .iter()
+ .fold((None, vec![]), |(acc_mode, mut acc_vec), key| {
+ // Declare intermediate modes for each key.
+ let mode_name: String = {
+ let base = key.to_string_repr();
+
+ if let Some(result) = &acc_mode {
+ result.to_owned() + base.as_str()
+ } else {
+ base
+ }
+ };
+
+ let mut riverctl = Command::new("riverctl");
+ riverctl.args(["declare-mode", mode_name.as_str()]);
+
+ let mut output = vec![riverctl];
+
+ // Provide keymaps for entering and leaving the mode
+ if let Some(acc_mode) = acc_mode.clone() {
+ output.extend(key_to_command(
+ key.to_owned(),
+ &["enter-mode".to_owned(), mode_name.clone()],
+ &acc_mode,
+ false,
+ ));
+ } else {
+ // Also spawn the help display if we start from the “normal” mode.
+ output.extend(key_to_command(
+ key.to_owned(),
+ &[
+ "spawn".to_owned(),
+ format!(
+ "{} && sleep 1 && {}",
+ shlex::try_join([
+ "riverctl",
+ "enter-mode",
+ mode_name.as_str()
+ ])
+ .expect("Should work"),
+ shlex::try_join([
+ current_exe()
+ .expect("Should have a current exe")
+ .as_os_str()
+ .as_str()
+ .expect("Should be valid utf8"),
+ "--keymap",
+ keymap_path.to_str().expect("Should be valid utf8"),
+ "show-help",
+ ])
+ .expect("Should work"),
+ ),
+ ],
+ "normal",
+ false,
+ ));
+ }
+
+ // Provide a mapping for going up a mode
+ output.extend(key_to_command(
+ "<BACKSPACE>".parse().expect("Hardcoded"),
+ &[
+ "enter-mode".to_owned(),
+ acc_mode.unwrap_or("normal".to_owned()),
+ ],
+ &mode_name,
+ false,
+ ));
+
+ // Another one for going back to normal.
+ output.extend(key_to_command(
+ "<ESC>".parse().expect("Hardcoded"),
+ &["enter-mode".to_owned(), "normal".to_owned()],
+ &mode_name,
+ false,
+ ));
+
+ acc_vec.extend(output);
+
+ (Some(mode_name), acc_vec)
+ });
+
+ base.extend(key_to_command(
+ mapping[0],
+ &value.command,
+ final_mode.as_ref().map_or("normal", |v| v.as_str()),
+ value.allow_locked,
+ ));
+
+ base
})
- .collect()
+ .collect();
+
+ Ok(output)
}
}
-impl MapMode {
- pub(crate) fn as_command(self) -> &'static str {
- match self {
- MapMode::Map => "map",
- MapMode::MapMouse => "map-pointer",
- MapMode::Unmap => "unmap",
+fn key_value_to_xkb_common_name(value: KeyValue) -> (String, Vec<&'static str>) {
+ let mut extra_modifiers = vec![];
+
+ let output = match value {
+ KeyValue::Backspace => "BackSpace".to_owned(),
+ KeyValue::Enter => "Return".to_owned(),
+ KeyValue::Left => "Left".to_owned(),
+ KeyValue::Right => "Right".to_owned(),
+ KeyValue::Up => "Up".to_owned(),
+ KeyValue::Down => "Down".to_owned(),
+ KeyValue::Home => "Home".to_owned(),
+ KeyValue::End => "End".to_owned(),
+ KeyValue::PageUp => "Page_Up".to_owned(),
+ KeyValue::PageDown => "Page_Down".to_owned(),
+ KeyValue::Tab => "Tab".to_owned(),
+ KeyValue::BackTab => "BackTab".to_owned(),
+ KeyValue::Delete => "Delete".to_owned(),
+ KeyValue::Insert => "Insert".to_owned(),
+ KeyValue::F(num) => format!("F{num}"),
+ KeyValue::Char(a) => {
+ // River does not differentiate between 'a' and 'A',
+ // so we need to do it beforehand.
+ if a.is_ascii_uppercase() {
+ extra_modifiers.push("Shift");
+ }
+
+ if a == ' ' {
+ "Space".to_string()
+ } else {
+ a.to_string()
+ }
}
+ KeyValue::Null => "Null".to_owned(),
+ KeyValue::Esc => "Escape".to_owned(),
+ KeyValue::CapsLock => "CapsLock".to_owned(),
+ KeyValue::ScrollLock => "ScrollLock".to_owned(),
+ KeyValue::NumLock => "NumLock".to_owned(),
+ KeyValue::PrintScreen => "Print".to_owned(),
+ KeyValue::Pause => "Pause".to_owned(),
+ KeyValue::Menu => "Menu".to_owned(),
+ KeyValue::KeypadBegin => "KeypadBegin".to_owned(),
+ KeyValue::Media(media_key_code) => match media_key_code {
+ MediaKeyCode::Play => "XF86AudioPlay".to_owned(),
+ MediaKeyCode::Pause => "XF86AudioPause".to_owned(),
+ MediaKeyCode::PlayPause => "XF86AudioPlayPause".to_owned(),
+ MediaKeyCode::Reverse => "XF86AudioReverse".to_owned(),
+ MediaKeyCode::Stop => "XF86AudioStop".to_owned(),
+ MediaKeyCode::FastForward => "XF86AudioFastForward".to_owned(),
+ MediaKeyCode::Rewind => "XF86AudioRewind".to_owned(),
+ MediaKeyCode::TrackNext => "XF86AudioTrackNext".to_owned(),
+ MediaKeyCode::TrackPrevious => "XF86AudioTrackPrevious".to_owned(),
+ MediaKeyCode::Record => "XF86AudioRecord".to_owned(),
+ MediaKeyCode::LowerVolume => "XF86AudioLowerVolume".to_owned(),
+ MediaKeyCode::RaiseVolume => "XF86AudioRaiseVolume".to_owned(),
+ MediaKeyCode::MuteVolume => "XF86AudioMute".to_owned(),
+ },
+ KeyValue::MouseKey(mouse_key_value) => match mouse_key_value {
+ MouseKeyValue::Left => "BTN_LEFT".to_owned(),
+ MouseKeyValue::Right => "BTN_RIGHT".to_owned(),
+ MouseKeyValue::Middle => "BTN_MIDDLE".to_owned(),
+ },
+ KeyValue::ModifierKey(modifier_key_code) => match modifier_key_code {
+ ModifierKeyCode::LeftAlt => "ALT_L".to_owned(),
+ ModifierKeyCode::RightAlt => "ALT_R".to_owned(),
+ ModifierKeyCode::LeftCtrl => "CTRL_L".to_owned(),
+ ModifierKeyCode::RightCtrl => "CTRL_R".to_owned(),
+ ModifierKeyCode::LeftMeta => "SUPER_L".to_owned(),
+ ModifierKeyCode::RightMeta => "SUPER_R".to_owned(),
+ ModifierKeyCode::LeftShift => "SHIFT_L".to_owned(),
+ ModifierKeyCode::RightShift => "SHIFT_R".to_owned(),
+ },
+ other => todo!("Key value: {other} not known."),
+ };
+
+ (output, extra_modifiers)
+}
+
+fn key_to_command(key: Key, command: &[String], mode: &str, allow_locked: bool) -> Vec<Command> {
+ let mut modifiers = {
+ let modifiers = key.modifiers();
+ let mut output = vec![];
+
+ if modifiers.alt() {
+ output.push("Alt");
+ }
+ if modifiers.ctrl() {
+ output.push("Control");
+ }
+ if modifiers.meta() {
+ output.push("Super");
+ }
+ if modifiers.shift() {
+ output.push("Shift");
+ }
+ output
+ };
+
+ let (key_value, extra_modifiers) = key_value_to_xkb_common_name(key.value());
+ modifiers.extend(extra_modifiers);
+
+ let map_mode = if let KeyValue::MouseKey(_) = key.value() {
+ "map-pointer"
+ } else {
+ "map"
+ };
+
+ let modifiers = if modifiers.is_empty() {
+ "None".to_owned()
+ } else {
+ modifiers.join("+")
+ };
+
+ let mut output = vec![{
+ let mut riverctl = Command::new("riverctl");
+ riverctl.args([map_mode, mode, &modifiers, &key_value]);
+
+ riverctl.args(command.iter().map(String::as_str));
+
+ riverctl
+ }];
+
+ if allow_locked {
+ output.push({
+ let mut riverctl = Command::new("riverctl");
+ riverctl.args([map_mode, "locked", &modifiers, &key_value]);
+
+ riverctl.args(command.iter().map(String::as_str));
+
+ riverctl
+ });
}
+
+ output
}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
index 2c82ee05..60ed41b8 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
@@ -8,40 +8,91 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr};
+use std::{fmt::Display, ops::Deref, str::FromStr};
-use anyhow::Context;
-use keymaps::{key_repr::Key, map_tree::MapTrie};
+use anyhow::{anyhow, bail, Context, Result};
+use keymaps::{
+ key_repr::{Key, Keys},
+ map_tree::MapTrie,
+};
use serde::{Deserialize, Serialize};
+use serde_json::{Map, Value};
pub mod commands;
-#[derive(Deserialize, Serialize, Debug)]
-#[allow(clippy::module_name_repetitions)]
-pub struct RawKeyMap(HashMap<Key, KeyConfig>);
-
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, PartialOrd)]
-/// What values to use for: `riverctl <map_mode> <mode> <mods> <key> <command..>`
+/// What values to use for: `riverctl <command..>`
+#[serde(deny_unknown_fields)]
pub struct KeyConfig {
command: Vec<String>,
- #[serde(default = "default_mode")]
- modes: Vec<String>,
-
- #[serde(default = "MapMode::default")]
- map_mode: MapMode,
+ /// Whether to allow this key mapping in the “locked” mode.
+ #[serde(default)]
+ allow_locked: bool,
}
impl FromStr for KeyMap {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- let raw: RawKeyMap =
- serde_json::from_str(s).context("Failed to parse the keymap config file as json.")?;
+ fn decode_value(
+ output: &mut MapTrie<KeyConfig>,
+ current_key: Vec<Key>,
+ value: &Value,
+ ) -> Result<()> {
+ let key_config = if let Some(value) = value.as_array() {
+ KeyConfig {
+ command: value
+ .iter()
+ .map(|v| v.as_str().map(ToOwned::to_owned))
+ .collect::<Option<_>>()
+ .ok_or(anyhow!("A array contained a non-string value: {value:#?}"))?,
+ allow_locked: false,
+ }
+ } else if let Some(object) = value.as_object() {
+ if object.contains_key("command") {
+ serde_json::from_value(value.to_owned())
+ .with_context(|| format!("Failed to parse key config: {value:#?}"))?
+ } else {
+ for (key, value) in object {
+ let mut local_current_key = current_key.clone();
+ local_current_key.push(
+ Key::from_str(key)
+ .with_context(|| format!("Failed to parse key '{key}'"))?,
+ );
+
+ decode_value(output, local_current_key, value)?;
+ }
+ return Ok(());
+ }
+ } else {
+ bail!("Value ({}) is invalid (not array or object).", value)
+ };
+
+ output
+ .insert(&current_key, key_config.clone())
+ .with_context(|| {
+ format!(
+ "Failed to insert mapping {} -> {key_config}",
+ Keys::from(current_key)
+ )
+ })?;
+
+ Ok(())
+ }
+
let mut out = MapTrie::<KeyConfig>::new();
- for (key, value) in raw.0 {
- out.insert(&[key], value.clone())
- .with_context(|| format!("Failed to insert mapping {key} -> {value}"))?;
+
+ let raw: Map<String, Value> =
+ serde_json::from_str(s).context("Failed to parse the keymap config file as json.")?;
+
+ for (key, value) in raw {
+ decode_value(
+ &mut out,
+ vec![Key::from_str(&key)
+ .with_context(|| format!("Failed to parse key ('{key}')"))?],
+ &value,
+ )?;
}
Ok(Self(out))
@@ -53,24 +104,6 @@ impl Display for KeyConfig {
}
}
-fn default_mode() -> Vec<String> {
- vec!["normal".to_owned()]
-}
-
-#[derive(Copy, Deserialize, Serialize, Debug, Clone, Default, PartialEq, PartialOrd)]
-enum MapMode {
- #[default]
- Map,
- MapMouse,
- Unmap,
-}
-
-impl Display for MapMode {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- <Self as std::fmt::Debug>::fmt(self, f)
- }
-}
-
#[derive(Debug)]
pub struct KeyMap(MapTrie<KeyConfig>);
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/main.rs b/pkgs/by-name/ri/river-mk-keymap/src/main.rs
index 63955f7f..7d7736b9 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/main.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/main.rs
@@ -13,31 +13,53 @@ use std::fs;
use anyhow::Context;
use clap::Parser;
-mod cli;
+pub mod cli;
pub mod key_map;
+pub mod wayland;
use crate::{cli::Args, key_map::KeyMap};
fn main() -> Result<(), anyhow::Error> {
let args = Args::parse();
- let keymap_file = fs::read_to_string(&args.path)
- .with_context(|| format!("Failed to open keymap file at: '{}'.", args.path.display()))?;
- let keymap: KeyMap = keymap_file
- .parse()
- .with_context(|| format!("Failed to parse keymap file at: {}", args.path.display()))?;
+ let keymap_path = &args.keymap.canonicalize().with_context(|| {
+ format!(
+ "Failed to canonicalize kepmay path: '{}'",
+ args.keymap.display()
+ )
+ })?;
- // println!("{keymap}");
- // println!("Commands:");
- for mut command in keymap.to_commands() {
- // println!("Executing {command:?}");
- let status = command
- .status()
- .with_context(|| format!("Failed to run command: '{command:?}'"))?;
+ let config = {
+ let keymap_file = fs::read_to_string(keymap_path).with_context(|| {
+ format!(
+ "Failed to open keymap file at: '{}'.",
+ keymap_path.display()
+ )
+ })?;
- if !status.success() {
- eprintln!("Command ('{command:?}') returned with non zero exit code: {status}");
+ let keymap: KeyMap = keymap_file.parse().with_context(|| {
+ format!("Failed to parse keymap file at: {}", keymap_path.display())
+ })?;
+
+ keymap
+ };
+
+ match args.command {
+ cli::SubCommand::Init {} => {
+ println!("{config}");
+ // println!("Commands:");
+ for mut command in config.to_commands(keymap_path)? {
+ // println!("{command:?}");
+ let status = command
+ .status()
+ .with_context(|| format!("Failed to run command: '{command:?}'"))?;
+
+ if !status.success() {
+ eprintln!("Command ('{command:?}') returned with non zero exit code: {status}");
+ }
+ }
}
+ cli::SubCommand::ShowHelp {} => wayland::main(config)?,
}
Ok(())
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs
new file mode 100644
index 00000000..0517ecf2
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs
@@ -0,0 +1,173 @@
+use std::mem;
+
+use vte::{Params, Parser, Perform};
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum Color {
+ Black,
+ Red,
+ Green,
+ Yellow,
+ Blue,
+ Purple,
+ Cyan,
+ White,
+}
+
+#[derive(Debug)]
+struct Cleaner {
+ current_color: Option<Color>,
+ styles: StyledString,
+ current: String,
+}
+
+#[derive(Debug)]
+struct StyledStringInner {
+ val: String,
+ color: Option<Color>,
+}
+
+pub(crate) struct StyledChar {
+ ch: char,
+ color: Option<Color>,
+}
+
+impl StyledChar {
+ pub(crate) fn as_char(&self) -> char {
+ self.ch
+ }
+
+ pub(crate) fn is_bold(&self) -> bool {
+ self.color.is_some()
+ }
+
+ pub(crate) fn color(&self) -> Option<Color> {
+ self.color
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct StyledString {
+ inner: Vec<StyledStringInner>,
+}
+
+impl StyledString {
+ fn push(&mut self, val: StyledStringInner) {
+ self.inner.push(val);
+ }
+
+ pub(crate) fn chars(&self) -> impl Iterator<Item = StyledChar> + use<'_> {
+ self.inner.iter().flat_map(|inner| {
+ inner.val.chars().map(|ch| StyledChar {
+ ch,
+ color: inner.color,
+ })
+ })
+ }
+}
+
+impl Cleaner {
+ fn reset_color(&mut self) {
+ self.styles.push(StyledStringInner {
+ val: mem::take(&mut self.current),
+ color: mem::take(&mut self.current_color),
+ });
+ }
+
+ fn set_color(&mut self, color: Color) {
+ self.current_color = Some(color);
+ }
+
+ fn add_char(&mut self, c: char) {
+ self.current.push(c);
+ }
+}
+
+impl Perform for Cleaner {
+ fn print(&mut self, c: char) {
+ self.add_char(c);
+ }
+
+ fn execute(&mut self, byte: u8) {
+ if byte == b'\n' {
+ self.reset_color();
+ self.add_char('\n');
+ self.reset_color();
+ } else {
+ eprintln!("Unknown [execute]: {byte:02x}");
+ }
+ }
+
+ fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) {
+ eprintln!(
+ "Unknown [hook] params={params:?}, intermediates={intermediates:?}, ignore={ignore:?}, char={c:?}"
+ );
+ }
+
+ fn put(&mut self, byte: u8) {
+ eprintln!("Unknonw [put] {byte:02x}");
+ }
+
+ fn unhook(&mut self) {
+ eprintln!("Unknown [unhook]");
+ }
+
+ fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
+ eprintln!("Unkown [osc_dispatch] params={params:?} bell_terminated={bell_terminated}");
+ }
+
+ fn csi_dispatch(&mut self, params: &Params, _: &[u8], _: bool, c: char) {
+ let params: Vec<u16> = params.iter().flatten().copied().collect();
+
+ if c != 'm' {
+ return;
+ }
+
+ // See: https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124
+ match params[..] {
+ [0] => self.reset_color(),
+ // [0, regular] if matches!(regular, 30..=37) => {}
+ [1, bold] if matches!(bold, 30..=37) => match bold {
+ 30 => self.set_color(Color::Black),
+ 31 => self.set_color(Color::Red),
+ 32 => self.set_color(Color::Green),
+ 36 => self.set_color(Color::Yellow),
+ 34 => self.set_color(Color::Blue),
+ 35 => self.set_color(Color::Purple),
+ 33 => self.set_color(Color::Cyan),
+ 37 => self.set_color(Color::White),
+ _ => unreachable!("Was filtered out"),
+ },
+ // [4, underline] if matches!(underline, 30..=37) => {}
+ // [background] if matches!(background, 40..=47) => {}
+ _ => todo!(),
+ }
+
+ // println!(
+ // "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}",
+ // params, intermediates, ignore, c
+ // );
+ }
+
+ fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
+ eprintln!(
+ "Unkown [esc_dispatch] intermediates={intermediates:?}, ignore={ignore:?}, byte={byte:02x}"
+ );
+ }
+}
+
+pub(crate) fn parse(input: &str) -> StyledString {
+ let mut statemachine = Parser::new();
+ let mut performer = Cleaner {
+ current_color: None,
+ styles: StyledString { inner: vec![] },
+ current: String::new(),
+ };
+
+ let buf: Vec<_> = input.bytes().collect();
+
+ statemachine.advance(&mut performer, &buf[..]);
+
+ assert!(performer.current.is_empty() && performer.current_color.is_none());
+ performer.styles
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs
new file mode 100644
index 00000000..c6e04fdf
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs
@@ -0,0 +1,214 @@
+use std::num::NonZero;
+
+use keymaps::key_repr::Key;
+use wayland_client::{
+ globals::GlobalListContents,
+ protocol::{
+ wl_buffer::WlBuffer, wl_compositor::WlCompositor, wl_registry, wl_seat::WlSeat,
+ wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
+ },
+ Connection, Dispatch, QueueHandle,
+};
+
+use wayland_protocols_wlr::layer_shell::v1::client::{
+ zwlr_layer_shell_v1::ZwlrLayerShellV1,
+ zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1},
+};
+
+use crate::wayland::{
+ ansi, render,
+ river::protocols::river_protocols::{
+ zriver_seat_status_v1::{self, ZriverSeatStatusV1},
+ zriver_status_manager_v1::ZriverStatusManagerV1,
+ },
+ AppData,
+};
+
+impl Dispatch<ZriverSeatStatusV1, ()> for AppData {
+ fn event(
+ state: &mut Self,
+ _: &ZriverSeatStatusV1,
+ event: <ZriverSeatStatusV1 as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ if let zriver_seat_status_v1::Event::Mode { name } = event {
+ let new_text = {
+ if name == "normal" {
+ // We are back at the normal mode.
+ // There is no need to display the mappings anymore, exit.
+ state.should_exit = true;
+ return;
+ } else if let Ok(keys) = Key::parse_multiple(&name) {
+ if let Some(val) = state.config.get(&keys) {
+ ansi::parse(val.to_string().as_str())
+ } else {
+ // Mode name not know, do nothing.
+ return;
+ }
+ } else {
+ // Mode name not valid, do nothing.
+ return;
+ }
+ };
+
+ let px_height;
+ (state.pixel_data, (state.max_px_width, px_height)) =
+ render::text(&new_text).expect("Works?");
+
+ // We add the `5` here, so that our letters don't stop exactly at the border.
+ state
+ .window
+ .0
+ .set_size(state.max_px_width + 5, px_height + 5);
+ state.window.1.commit();
+
+ if state.configured {
+ state.draw();
+ }
+ }
+ }
+}
+
+impl Dispatch<ZwlrLayerSurfaceV1, ()> for AppData {
+ fn event(
+ state: &mut Self,
+ proxy: &ZwlrLayerSurfaceV1,
+ event: <ZwlrLayerSurfaceV1 as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ match event {
+ zwlr_layer_surface_v1::Event::Configure {
+ serial,
+ width,
+ height,
+ } => {
+ state.buffer = None;
+
+ proxy.ack_configure(serial);
+
+ state.width = NonZero::new(width).map_or_else(|| state.width, NonZero::get);
+ state.height = NonZero::new(height).map_or_else(|| state.height, NonZero::get);
+
+ state.draw();
+
+ state.configured = true;
+ }
+ zwlr_layer_surface_v1::Event::Closed => {
+ state.should_exit = true;
+ }
+ _ => (),
+ }
+ }
+}
+
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for AppData {
+ fn event(
+ _: &mut AppData,
+ _: &wl_registry::WlRegistry,
+ _: wl_registry::Event,
+ _: &GlobalListContents,
+ _: &Connection,
+ _: &QueueHandle<AppData>,
+ ) {
+ }
+}
+
+impl Dispatch<WlShmPool, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &WlShmPool,
+ _: <WlShmPool as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<WlShm, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &WlShm,
+ _: <WlShm as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<WlSurface, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &WlSurface,
+ _: <WlSurface as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<WlCompositor, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &WlCompositor,
+ _: <WlCompositor as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<WlSeat, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &WlSeat,
+ _: <WlSeat as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<WlBuffer, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &WlBuffer,
+ _: <WlBuffer as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<ZriverStatusManagerV1, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &ZriverStatusManagerV1,
+ _: <ZriverStatusManagerV1 as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<ZwlrLayerShellV1, ()> for AppData {
+ fn event(
+ _: &mut Self,
+ _: &ZwlrLayerShellV1,
+ _: <ZwlrLayerShellV1 as wayland_client::Proxy>::Event,
+ (): &(),
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ }
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs
new file mode 100644
index 00000000..44c010d5
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs
@@ -0,0 +1,272 @@
+#![allow(
+ clippy::cast_sign_loss,
+ clippy::cast_possible_wrap,
+ clippy::cast_precision_loss,
+ clippy::cast_possible_truncation
+)]
+
+use anyhow::Result;
+use wayland_client::{
+ globals::registry_queue_init,
+ protocol::{
+ wl_compositor::WlCompositor,
+ wl_seat::WlSeat,
+ wl_shm::{self, WlShm},
+ wl_surface::WlSurface,
+ },
+ Connection,
+};
+use wayland_protocols_wlr::layer_shell::v1::client::{
+ zwlr_layer_shell_v1::{self, ZwlrLayerShellV1},
+ zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1},
+};
+
+use crate::{
+ key_map::KeyMap,
+ wayland::{
+ ansi::Color,
+ river::protocols::river_protocols::zriver_status_manager_v1::ZriverStatusManagerV1,
+ shm::slot::{Buffer, SlotPool},
+ },
+};
+
+mod ansi;
+mod render;
+mod river;
+mod shm;
+
+mod dispatches;
+
+struct AppData {
+ pool: SlotPool,
+ window: (ZwlrLayerSurfaceV1, WlSurface),
+
+ configured: bool,
+ buffer: Option<Buffer>,
+
+ width: u32,
+ height: u32,
+
+ max_px_width: u32,
+ pixel_data: (Vec<f32>, Vec<Option<Color>>),
+
+ config: KeyMap,
+ should_exit: bool,
+}
+
+impl AppData {
+ #[allow(clippy::too_many_lines)]
+ fn draw(&mut self) {
+ let width = self.width;
+ let height = self.height;
+ let stride = self.width as i32 * 4;
+
+ let buffer = self.buffer.get_or_insert_with(|| {
+ self.pool
+ .create_buffer(
+ width as i32,
+ height as i32,
+ stride,
+ wl_shm::Format::Argb8888,
+ )
+ .expect("Works?")
+ .0
+ });
+
+ let canvas = if let Some(canvas) = self.pool.canvas(buffer) {
+ canvas
+ } else {
+ // This should be rare, but if the compositor has not released the previous
+ // buffer, we need double-buffering.
+ let (second_buffer, canvas) = self
+ .pool
+ .create_buffer(
+ self.width as i32,
+ self.height as i32,
+ stride,
+ wl_shm::Format::Argb8888,
+ )
+ .expect("create buffer");
+ *buffer = second_buffer;
+ canvas
+ };
+
+ // Draw to the window.
+ {
+ canvas
+ .chunks_exact_mut(stride as usize)
+ .enumerate()
+ .for_each(|(row_index, row)| {
+ // let row_slice = row_slice(self.height, row_index as u32, 0.97);
+ // let allowed_columns = (f64::from(self.width) * row_slice).ceil() as usize;
+
+ row.chunks_exact_mut(4)
+ .enumerate()
+ .for_each(|(column_index, chunk)| {
+ // const BACKGROUND_COLOR: u32 = 0xee58_5b70;
+ const BACKGROUND_COLOR: u32 = 0xee00_0000;
+
+ assert!(column_index as u32 <= self.width);
+
+ // if column_index > allowed_columns
+ // || column_index < (self.width as usize - allowed_columns)
+ // {
+ // let array: &mut [u8; 4] = chunk.try_into().unwrap();
+ // *array = 0u32.to_le_bytes();
+ // return;
+ // }
+
+ if column_index >= (self.max_px_width as usize) {
+ let array: &mut [u8; 4] = chunk.try_into().unwrap();
+ *array = BACKGROUND_COLOR.to_le_bytes();
+ } else {
+ assert!(column_index < self.max_px_width as usize);
+
+ let position =
+ column_index + row_index * self.max_px_width as usize;
+
+ if let Some(coverage) = &self.pixel_data.0.get(position) {
+ let a = (BACKGROUND_COLOR & (0xff << (6 * 4))) >> 24;
+
+ let (r, g, b) = if let Some(color) = self
+ .pixel_data
+ .1
+ .get(position)
+ .expect("If the pixel is set, the color will too")
+ {
+ let (r, g, b) = match color {
+ Color::Black => (0, 0, 0),
+ Color::Red => (0xff, 0, 0),
+ Color::Green => (0, 0xff, 0),
+ Color::Yellow => (0xff, 0xff, 0),
+ Color::Blue => (0, 0, 0xff),
+ Color::Purple => (0x80, 0, 0x80),
+ Color::Cyan => (0, 0xff, 0xff),
+ Color::White => (0xff, 0xff, 0xff),
+ };
+
+ let r = (r as f32 * **coverage).ceil() as u32;
+ let g = (g as f32 * **coverage).ceil() as u32;
+ let b = (b as f32 * **coverage).ceil() as u32;
+
+ (r, g, b)
+ } else {
+ let r = (255.0 * **coverage).ceil() as u32;
+ let g = (255.0 * **coverage).ceil() as u32;
+ let b = (255.0 * **coverage).ceil() as u32;
+
+ (r, g, b)
+ };
+
+ let color: u32 = (a << 24) + (r << 16) + (g << 8) + b;
+
+ let array: &mut [u8; 4] = chunk.try_into().unwrap();
+ *array = color.to_le_bytes();
+ } else {
+ let array: &mut [u8; 4] = chunk.try_into().unwrap();
+ *array = BACKGROUND_COLOR.to_le_bytes();
+ }
+ }
+ });
+ });
+ }
+
+ self.window
+ .1
+ .damage_buffer(0, 0, self.width as i32, self.height as i32);
+
+ buffer.attach_to(&self.window.1).expect("works");
+ self.window.1.commit();
+ }
+}
+
+/// # Errors
+/// If a protocol error arises.
+pub fn main(config: KeyMap) -> Result<()> {
+ let conn = Connection::connect_to_env()?;
+ let (globals, mut queue) = registry_queue_init::<AppData>(&conn)?;
+ let qh = queue.handle();
+
+ let seat: WlSeat = globals.bind(&qh, 9..=9, ())?;
+ let status_manager: ZriverStatusManagerV1 = globals.bind(&qh, 4..=4, ())?;
+ let _seat_status = status_manager.get_river_seat_status(&seat, &qh, ());
+
+ let compositor: WlCompositor = globals.bind(&qh, 6..=6, ())?;
+ let shm: WlShm = globals.bind(&qh, 1..=1, ())?;
+ // let xdg_wm: XdgWmBase = globals.bind(&qh, 5..=5, ())?;
+
+ let surface = compositor.create_surface(&qh, ());
+ let pool = SlotPool::new(1024 * 1024, &shm)?;
+
+ let zwlr_layer_shell: ZwlrLayerShellV1 = globals.bind(&qh, 4..=4, ())?;
+ let layer_surface = zwlr_layer_shell.get_layer_surface(
+ &surface,
+ None,
+ zwlr_layer_shell_v1::Layer::Overlay,
+ "river-mk-keymap which-key".to_owned(),
+ &qh,
+ (),
+ );
+
+ layer_surface.set_size(256, 256);
+ layer_surface
+ .set_anchor(zwlr_layer_surface_v1::Anchor::Left | zwlr_layer_surface_v1::Anchor::Top);
+
+ surface.commit();
+
+ let mut me = AppData {
+ config,
+ should_exit: false,
+
+ configured: false,
+ buffer: None,
+
+ width: 256,
+ height: 256,
+
+ max_px_width: 0,
+ pixel_data: (vec![], vec![]),
+
+ window: (layer_surface, surface),
+
+ pool,
+ };
+
+ loop {
+ queue.blocking_dispatch(&mut me)?;
+
+ if me.should_exit {
+ break;
+ }
+ }
+
+ Ok(())
+}
+
+// /// Calculate which amount of the current row (`i`) should be painted, if we want a corner
+// /// rounding of percent `p` and have an total of `n` rows.
+// fn row_slice(n_u32: u32, i_u32: u32, p: f64) -> f64 {
+// fn within_tolerance(a: f64, b: f64) -> bool {
+// const ALLOWED_ERROR: f64 = 0.000_000_1;
+//
+// (a - b).abs() < ALLOWED_ERROR
+// }
+//
+// let i = f64::from(i_u32);
+// let n = f64::from(n_u32);
+//
+// let out = p + (1.0 - p) * (PI * i / n).sin();
+//
+// assert!(out >= 0.0);
+// assert!(out <= 1.0);
+//
+// if i_u32 == 0 || i_u32 == n_u32 {
+// assert!(within_tolerance(out, p));
+// }
+//
+// if i_u32 < n_u32 / 2 {
+// assert!(within_tolerance(out, row_slice(n_u32, n_u32 - i_u32, p)));
+// }
+//
+// out
+// }
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs
new file mode 100644
index 00000000..7f0aaec9
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs
@@ -0,0 +1,57 @@
+use ab_glyph::{point, Font, Glyph, Point, ScaleFont};
+
+use crate::wayland::ansi::{StyledChar, StyledString};
+
+/// Simple paragraph layout for glyphs into `target`.
+/// Starts at position `(0, ascent)`.
+///
+/// This is for testing and examples.
+pub(super) fn layout_paragraph<F, SF, BF, BSF>(
+ font: SF,
+ bold_font: BSF,
+ position: Point,
+ max_width: f32,
+ text: &StyledString,
+ target: &mut Vec<(Glyph, StyledChar)>,
+) where
+ F: Font,
+ SF: ScaleFont<F>,
+ BF: Font,
+ BSF: ScaleFont<BF>,
+{
+ let v_advance = font.height() + font.line_gap();
+ let mut caret = position + point(0.0, font.ascent());
+ let mut last_glyph: Option<Glyph> = None;
+
+ for c in text.chars() {
+ if c.as_char().is_control() {
+ if c.as_char() == '\n' {
+ caret = point(position.x, caret.y + v_advance);
+ last_glyph = None;
+ }
+ continue;
+ }
+
+ let mut glyph = if c.is_bold() {
+ bold_font.scaled_glyph(c.as_char())
+ } else {
+ font.scaled_glyph(c.as_char())
+ };
+
+ if let Some(previous) = last_glyph.take() {
+ caret.x += font.kern(previous.id, glyph.id);
+ }
+ glyph.position = caret;
+
+ last_glyph = Some(glyph.clone());
+ caret.x += font.h_advance(glyph.id);
+
+ if !c.as_char().is_whitespace() && caret.x > position.x + max_width {
+ caret = point(position.x, caret.y + v_advance);
+ glyph.position = caret;
+ last_glyph = None;
+ }
+
+ target.push((glyph, c));
+ }
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs
new file mode 100644
index 00000000..e92def3c
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs
@@ -0,0 +1,129 @@
+use std::{fs::File, io::Read};
+
+use ab_glyph::{point, Font, FontVec, PxScale, ScaleFont};
+use anyhow::{Context, Result};
+use font_kit::{
+ family_name::FamilyName, handle::Handle, properties::Properties, source::SystemSource,
+};
+
+use crate::wayland::ansi::{Color, StyledString};
+
+mod layout;
+
+fn get_font(weight: f32) -> Result<impl Font> {
+ let handle = SystemSource::new()
+ .select_best_match(
+ &[FamilyName::Monospace],
+ Properties::new().weight(font_kit::properties::Weight(weight)),
+ )
+ .context("Failed to find a monospace font")?;
+
+ match handle {
+ Handle::Path { path, font_index } => {
+ let data = {
+ let mut buffer = vec![];
+
+ let mut file = File::open(&path)?;
+ file.read_to_end(&mut buffer)?;
+ buffer
+ };
+
+ FontVec::try_from_vec_and_index(data, font_index).with_context(|| {
+ format!(
+ "Failed to load font at '{}' with index {}",
+ path.display(),
+ font_index
+ )
+ })
+ }
+ Handle::Memory { .. } => unimplemented!(),
+ }
+}
+
+pub(super) type ColorVec = (Vec<f32>, Vec<Option<Color>>);
+pub(super) fn text(input: &StyledString) -> Result<(ColorVec, (u32, u32))> {
+ let normal_font = get_font(400.0)?;
+ let bold_font = get_font(600.0)?;
+
+ let height: f32 = 15.0;
+ let px_height = height.ceil() as usize;
+
+ let scale = PxScale {
+ x: height,
+ y: height,
+ };
+
+ let scaled_font = normal_font.into_scaled(scale);
+ let bold_scaled_font = bold_font.into_scaled(scale);
+
+ let mut glyphs = Vec::new();
+ layout::layout_paragraph(
+ &scaled_font,
+ &bold_scaled_font,
+ point(0.0, 0.0),
+ 9999.0,
+ input,
+ &mut glyphs,
+ );
+
+ let px_width = glyphs
+ .iter()
+ .fold(0.0, |acc, (g, c)| {
+ let next = g.position.x
+ + if c.is_bold() {
+ bold_scaled_font.h_advance(g.id)
+ } else {
+ scaled_font.h_advance(g.id)
+ };
+
+ if next > acc {
+ next
+ } else {
+ acc
+ }
+ })
+ .ceil() as usize;
+
+ // Rasterise to a f32 alpha vec
+ let mut pixel_data = vec![0.0; px_width * px_height];
+ let mut color_data = vec![None; px_width * px_height];
+ for (g, c) in glyphs {
+ let maybe_glyph = if c.is_bold() {
+ bold_scaled_font.outline_glyph(g)
+ } else {
+ scaled_font.outline_glyph(g)
+ };
+
+ if let Some(og) = maybe_glyph {
+ let bounds = og.px_bounds();
+ og.draw(|x, y, v| {
+ let x = x as f32 + bounds.min.x;
+ let y = y as f32 + bounds.min.y;
+ let next_idx = x as usize + y as usize * px_width;
+
+ assure_idx(&mut pixel_data, next_idx, 0.0);
+ assure_idx(&mut color_data, next_idx, None);
+
+ // save the coverage alpha
+ pixel_data[next_idx] += v;
+ color_data[next_idx] = c.color();
+ });
+ }
+ }
+
+ let len = pixel_data.len();
+ Ok((
+ (pixel_data, color_data),
+ (px_width as u32, (len / px_width) as u32),
+ ))
+}
+
+fn assure_idx<T: Copy + Clone>(pixel_data: &mut Vec<T>, next_idx: usize, fill: T) {
+ let last = pixel_data.len() - 1;
+
+ if next_idx > last {
+ let needed = next_idx - last;
+
+ pixel_data.extend(vec![fill; needed]);
+ }
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs
new file mode 100644
index 00000000..f17c7ac8
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod protocols;
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs
new file mode 100644
index 00000000..e54b65e1
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs
@@ -0,0 +1,28 @@
+pub(crate) mod river_protocols {
+ use wayland_client;
+ // import objects from the core protocol if needed
+ use wayland_client::protocol::{wl_output, wl_seat};
+
+ // This module hosts a low-level representation of the protocol objects
+ // you will not need to interact with it yourself, but the code generated
+ // by the generate_client_code! macro will use it
+ // import the interfaces from the core protocol if needed
+
+ #[allow(non_upper_case_globals)]
+ pub(crate) mod __status {
+ use wayland_client::backend as wayland_backend;
+ use wayland_client::protocol::__interfaces::{
+ wl_output_interface, wl_seat_interface, WL_OUTPUT_INTERFACE, WL_SEAT_INTERFACE,
+ };
+ wayland_scanner::generate_interfaces!("./resources/river-status-unstable-v1.xml");
+ }
+
+ use self::__status::{
+ ZRIVER_OUTPUT_STATUS_V1_INTERFACE, ZRIVER_SEAT_STATUS_V1_INTERFACE,
+ ZRIVER_STATUS_MANAGER_V1_INTERFACE,
+ };
+
+ // This macro generates the actual types that represent the wayland objects of
+ // your custom protocol
+ wayland_scanner::generate_client_code!("./resources/river-status-unstable-v1.xml");
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs
new file mode 100644
index 00000000..65d3c590
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs
@@ -0,0 +1,21 @@
+#![allow(dead_code)]
+
+pub(crate) mod multi;
+pub(crate) mod raw;
+pub(crate) mod slot;
+
+use std::io;
+
+use wayland_client::globals::GlobalError;
+
+/// An error that may occur when creating a pool.
+#[derive(Debug, thiserror::Error)]
+pub enum CreatePoolError {
+ /// The [`wl_shm`] global is not bound.
+ #[error(transparent)]
+ Global(#[from] GlobalError),
+
+ /// Error while allocating the shared memory.
+ #[error(transparent)]
+ Create(#[from] io::Error),
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs
new file mode 100644
index 00000000..0b1fdc1b
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs
@@ -0,0 +1,437 @@
+//! A pool implementation which automatically manage buffers.
+//!
+//! This pool is built on the [`RawPool`].
+//!
+//! The [`MultiPool`] takes a key which is used to identify buffers and tries to return the buffer associated to the key
+//! if possible. If no buffer in the pool is associated to the key, it will create a new one.
+//!
+//! # Example
+//!
+//! ```rust
+//! use smithay_client_toolkit::reexports::client::{
+//! QueueHandle,
+//! protocol::wl_surface::WlSurface,
+//! protocol::wl_shm::Format,
+//! };
+//! use smithay_client_toolkit::shm::multi::MultiPool;
+//!
+//! struct WlFoo {
+//! // The surface we'll draw on and the index of buffer associated to it
+//! surface: (WlSurface, usize),
+//! pool: MultiPool<(WlSurface, usize)>
+//! }
+//!
+//! impl WlFoo {
+//! fn draw(&mut self, qh: &QueueHandle<WlFoo>) {
+//! let surface = &self.surface.0;
+//! // We'll increment "i" until the pool can create a new buffer
+//! // if there's no buffer associated with our surface and "i" or if
+//! // a buffer with the obuffer associated with our surface and "i" is free for use.
+//! //
+//! // There's no limit to the amount of buffers we can allocate to our surface but since
+//! // shm buffers are released fairly fast, it's unlikely we'll need more than double buffering.
+//! for i in 0..2 {
+//! self.surface.1 = i;
+//! if let Ok((offset, buffer, slice)) = self.pool.create_buffer(
+//! 100,
+//! 100 * 4,
+//! 100,
+//! &self.surface,
+//! Format::Argb8888,
+//! ) {
+//! /*
+//! insert drawing code here
+//! */
+//! surface.attach(Some(buffer), 0, 0);
+//! surface.commit();
+//! // We exit the function after the draw.
+//! return;
+//! }
+//! }
+//! /*
+//! If there's no buffer available we can for example request a frame callback
+//! and trigger a redraw when it fires.
+//! (not shown in this example)
+//! */
+//! }
+//! }
+//!
+//! fn draw(slice: &mut [u8]) {
+//! todo!()
+//! }
+//!
+//! ```
+//!
+
+use std::borrow::Borrow;
+use std::io;
+use std::os::unix::io::OwnedFd;
+
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
+use wayland_client::backend::protocol::Message;
+use wayland_client::backend::{Backend, ObjectData, ObjectId};
+use wayland_client::{
+ protocol::{wl_buffer, wl_shm},
+ Proxy,
+};
+
+use crate::wayland::shm::CreatePoolError;
+
+use super::raw::RawPool;
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum PoolError {
+ #[error("buffer is currently used")]
+ InUse,
+ #[error("buffer is overlapping another")]
+ Overlap,
+ #[error("buffer could not be found")]
+ NotFound,
+}
+
+/// This pool manages buffers associated with keys.
+/// Only one buffer can be attributed to a given key.
+#[derive(Debug)]
+pub(crate) struct MultiPool<K> {
+ buffer_list: Vec<BufferSlot<K>>,
+ pub(crate) inner: RawPool,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) struct BufferSlot<K> {
+ free: Arc<AtomicBool>,
+ size: usize,
+ used: usize,
+ offset: usize,
+ buffer: Option<wl_buffer::WlBuffer>,
+ key: K,
+}
+
+impl<K> Drop for BufferSlot<K> {
+ fn drop(&mut self) {
+ self.destroy().ok();
+ }
+}
+
+impl<K> BufferSlot<K> {
+ pub(crate) fn destroy(&self) -> Result<(), PoolError> {
+ self.buffer
+ .as_ref()
+ .ok_or(PoolError::NotFound)
+ .and_then(|buffer| {
+ self.free
+ .load(Ordering::Relaxed)
+ .then(|| buffer.destroy())
+ .ok_or(PoolError::InUse)
+ })
+ }
+}
+
+impl<K> MultiPool<K> {
+ pub(crate) fn new(shm: &wl_shm::WlShm) -> Result<Self, CreatePoolError> {
+ Ok(Self {
+ inner: RawPool::new(4096, shm)?,
+ buffer_list: Vec::new(),
+ })
+ }
+
+ /// Resizes the memory pool, notifying the server the pool has changed in size.
+ ///
+ /// The [`wl_shm`] protocol only allows the pool to be made bigger. If the new size is smaller than the
+ /// current size of the pool, this function will do nothing.
+ pub(crate) fn resize(&mut self, size: usize) -> io::Result<()> {
+ self.inner.resize(size)
+ }
+
+ /// Removes the buffer with the given key from the pool and rearranges the others.
+ pub(crate) fn remove<Q>(&mut self, key: &Q) -> Option<BufferSlot<K>>
+ where
+ Q: PartialEq,
+ K: Borrow<Q>,
+ {
+ self.buffer_list
+ .iter()
+ .enumerate()
+ .find(|(_, slot)| slot.key.borrow().eq(key))
+ .map(|(i, _)| i)
+ .map(|i| self.buffer_list.remove(i))
+ }
+
+ /// Insert a buffer into the pool.
+ ///
+ /// The parameters are:
+ ///
+ /// - `width`: the width of this buffer (in pixels)
+ /// - `height`: the height of this buffer (in pixels)
+ /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+ /// - `key`: a borrowed form of the stored key type
+ /// - `format`: the encoding format of the pixels.
+ pub(crate) fn insert<Q>(
+ &mut self,
+ width: i32,
+ stride: i32,
+ height: i32,
+ key: &Q,
+ format: wl_shm::Format,
+ ) -> Result<usize, PoolError>
+ where
+ K: Borrow<Q>,
+ Q: PartialEq + ToOwned<Owned = K>,
+ {
+ let mut offset = 0;
+ let mut found_key = false;
+ let size = (stride * height) as usize;
+ let mut index = Err(PoolError::NotFound);
+
+ for (i, buf_slot) in self.buffer_list.iter_mut().enumerate() {
+ if buf_slot.key.borrow().eq(key) {
+ found_key = true;
+ if buf_slot.free.load(Ordering::Relaxed) {
+ // Destroys the buffer if it's resized
+ if size != buf_slot.used {
+ if let Some(buffer) = buf_slot.buffer.take() {
+ buffer.destroy();
+ }
+ }
+ // Increases the size of the Buffer if it's too small and add 5% padding.
+ // It is possible this buffer overlaps the following but the else if
+ // statement prevents this buffer from being returned if that's the case.
+ buf_slot.size = buf_slot.size.max(size + size / 20);
+ index = Ok(i);
+ } else {
+ index = Err(PoolError::InUse);
+ }
+ // If a buffer is resized, it is likely that the followings might overlap
+ } else if offset > buf_slot.offset {
+ // When the buffer is free, it's safe to shift it because we know the compositor won't try to read it.
+ if buf_slot.free.load(Ordering::Relaxed) {
+ if offset != buf_slot.offset {
+ if let Some(buffer) = buf_slot.buffer.take() {
+ buffer.destroy();
+ }
+ }
+ buf_slot.offset = offset;
+ } else {
+ // If one of the overlapping buffers is busy, then no buffer can be returned because it could result in a data race.
+ index = Err(PoolError::InUse);
+ }
+ } else if found_key {
+ break;
+ }
+ let size = (buf_slot.size + 63) & !63;
+ offset += size;
+ }
+
+ if !found_key {
+ if let Err(err) = index {
+ return self
+ .dyn_resize(offset, width, stride, height, key.to_owned(), format)
+ .map(|()| self.buffer_list.len() - 1)
+ .ok_or(err);
+ }
+ }
+
+ index
+ }
+
+ /// Retreives the buffer associated with the given key.
+ ///
+ /// The parameters are:
+ ///
+ /// - `width`: the width of this buffer (in pixels)
+ /// - `height`: the height of this buffer (in pixels)
+ /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+ /// - `key`: a borrowed form of the stored key type
+ /// - `format`: the encoding format of the pixels.
+ pub(crate) fn get<Q>(
+ &mut self,
+ width: i32,
+ stride: i32,
+ height: i32,
+ key: &Q,
+ format: wl_shm::Format,
+ ) -> Option<(usize, &wl_buffer::WlBuffer, &mut [u8])>
+ where
+ Q: PartialEq,
+ K: Borrow<Q>,
+ {
+ let len = self.inner.len();
+ let size = (stride * height) as usize;
+ let buf_slot = self
+ .buffer_list
+ .iter_mut()
+ .find(|buf_slot| buf_slot.key.borrow().eq(key))?;
+
+ if buf_slot.size >= size {
+ return None;
+ }
+
+ buf_slot.used = size;
+ let offset = buf_slot.offset;
+ if buf_slot.buffer.is_none() {
+ if offset + size > len {
+ self.inner.resize(offset + size + size / 20).ok()?;
+ }
+ let free = Arc::new(AtomicBool::new(true));
+ let data = BufferObjectData { free: free.clone() };
+ let buffer = self.inner.create_buffer_raw(
+ offset as i32,
+ width,
+ height,
+ stride,
+ format,
+ Arc::new(data),
+ );
+ buf_slot.free = free;
+ buf_slot.buffer = Some(buffer);
+ }
+ let buf = buf_slot.buffer.as_ref()?;
+ buf_slot.free.store(false, Ordering::Relaxed);
+ Some((offset, buf, &mut self.inner.mmap()[offset..][..size]))
+ }
+
+ /// Returns the buffer associated with the given key and its offset (usize) in the mempool.
+ ///
+ /// The parameters are:
+ ///
+ /// - `width`: the width of this buffer (in pixels)
+ /// - `height`: the height of this buffer (in pixels)
+ /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+ /// - `key`: a borrowed form of the stored key type
+ /// - `format`: the encoding format of the pixels.
+ ///
+ /// The offset can be used to determine whether or not a buffer was moved in the mempool
+ /// and by consequence if it should be damaged partially or fully.
+ pub(crate) fn create_buffer<Q>(
+ &mut self,
+ width: i32,
+ stride: i32,
+ height: i32,
+ key: &Q,
+ format: wl_shm::Format,
+ ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError>
+ where
+ K: Borrow<Q>,
+ Q: PartialEq + ToOwned<Owned = K>,
+ {
+ let index = self.insert(width, stride, height, key, format)?;
+ self.get_at(index, width, stride, height, format)
+ }
+
+ /// Retreives the buffer at the given index.
+ fn get_at(
+ &mut self,
+ index: usize,
+ width: i32,
+ stride: i32,
+ height: i32,
+ format: wl_shm::Format,
+ ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError> {
+ let len = self.inner.len();
+ let size = (stride * height) as usize;
+ let buf_slot = self.buffer_list.get_mut(index).ok_or(PoolError::NotFound)?;
+
+ if size > buf_slot.size {
+ return Err(PoolError::Overlap);
+ }
+
+ buf_slot.used = size;
+ let offset = buf_slot.offset;
+ if buf_slot.buffer.is_none() {
+ if offset + size > len {
+ self.inner
+ .resize(offset + size + size / 20)
+ .map_err(|_| PoolError::Overlap)?;
+ }
+ let free = Arc::new(AtomicBool::new(true));
+ let data = BufferObjectData { free: free.clone() };
+ let buffer = self.inner.create_buffer_raw(
+ offset as i32,
+ width,
+ height,
+ stride,
+ format,
+ Arc::new(data),
+ );
+ buf_slot.free = free;
+ buf_slot.buffer = Some(buffer);
+ }
+ buf_slot.free.store(false, Ordering::Relaxed);
+ let buf = buf_slot.buffer.as_ref().unwrap();
+ Ok((offset, buf, &mut self.inner.mmap()[offset..][..size]))
+ }
+
+ /// Calcule the offet and size of a buffer based on its stride.
+ fn offset(mut offset: i32, stride: i32, height: i32) -> (usize, usize) {
+ // bytes per pixel
+ let size = stride * height;
+ // 5% padding.
+ offset += offset / 20;
+ offset = (offset + 63) & !63;
+ (offset as usize, size as usize)
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ /// Resizes the pool and appends a new buffer.
+ fn dyn_resize(
+ &mut self,
+ offset: usize,
+ width: i32,
+ stride: i32,
+ height: i32,
+ key: K,
+ format: wl_shm::Format,
+ ) -> Option<()> {
+ let (offset, size) = Self::offset(offset as i32, stride, height);
+ if self.inner.len() < offset + size {
+ self.resize(offset + size + size / 20).ok()?;
+ }
+ let free = Arc::new(AtomicBool::new(true));
+ let data = BufferObjectData { free: free.clone() };
+ let buffer = self.inner.create_buffer_raw(
+ offset as i32,
+ width,
+ height,
+ stride,
+ format,
+ Arc::new(data),
+ );
+ self.buffer_list.push(BufferSlot {
+ offset,
+ used: 0,
+ free,
+ buffer: Some(buffer),
+ size,
+ key,
+ });
+ Some(())
+ }
+}
+
+struct BufferObjectData {
+ free: Arc<AtomicBool>,
+}
+
+impl ObjectData for BufferObjectData {
+ fn event(
+ self: Arc<Self>,
+ _backend: &Backend,
+ msg: Message<ObjectId, OwnedFd>,
+ ) -> Option<Arc<dyn ObjectData>> {
+ debug_assert!(wayland_client::backend::protocol::same_interface(
+ msg.sender_id.interface(),
+ wl_buffer::WlBuffer::interface()
+ ));
+ debug_assert!(msg.opcode == 0);
+
+ // wl_buffer only has a single event: wl_buffer.release
+ self.free.store(true, Ordering::Relaxed);
+
+ None
+ }
+
+ fn destroyed(&self, _: ObjectId) {}
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs
new file mode 100644
index 00000000..a12afaa0
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs
@@ -0,0 +1,290 @@
+//! A raw shared memory pool handler.
+//!
+//! This is intended as a safe building block for higher level shared memory pool abstractions and is not
+//! encouraged for most library users.
+
+use rustix::{
+ io::Errno,
+ shm::{Mode, OFlags},
+};
+use std::{
+ fs::File,
+ io,
+ ops::Deref,
+ os::unix::prelude::{AsFd, BorrowedFd, OwnedFd},
+ sync::Arc,
+ time::{SystemTime, UNIX_EPOCH},
+};
+
+use memmap2::MmapMut;
+use wayland_client::{
+ backend::ObjectData,
+ protocol::{wl_buffer, wl_shm, wl_shm_pool},
+ Dispatch, Proxy, QueueHandle, WEnum,
+};
+
+use super::CreatePoolError;
+
+/// A raw handler for file backed shared memory pools.
+///
+/// This type of pool will create the SHM memory pool and provide a way to resize the pool.
+///
+/// This pool does not release buffers. If you need this, use one of the higher level pools.
+#[derive(Debug)]
+pub struct RawPool {
+ pool: DestroyOnDropPool,
+ len: usize,
+ mem_file: File,
+ mmap: MmapMut,
+}
+
+impl RawPool {
+ pub fn new(len: usize, shm: &wl_shm::WlShm) -> Result<RawPool, CreatePoolError> {
+ let shm_fd = RawPool::create_shm_fd()?;
+ let mem_file = File::from(shm_fd);
+ mem_file.set_len(len as u64)?;
+
+ let pool = shm
+ .send_constructor(
+ wl_shm::Request::CreatePool {
+ fd: mem_file.as_fd(),
+ size: len as i32,
+ },
+ Arc::new(ShmPoolData),
+ )
+ .unwrap_or_else(|_| Proxy::inert(shm.backend().clone()));
+ let mmap = unsafe { MmapMut::map_mut(&mem_file)? };
+
+ Ok(RawPool {
+ pool: DestroyOnDropPool(pool),
+ len,
+ mem_file,
+ mmap,
+ })
+ }
+
+ /// Resizes the memory pool, notifying the server the pool has changed in size.
+ ///
+ /// The [`wl_shm`] protocol only allows the pool to be made bigger. If the new size is smaller than the
+ /// current size of the pool, this function will do nothing.
+ pub fn resize(&mut self, size: usize) -> io::Result<()> {
+ if size > self.len {
+ self.len = size;
+ self.mem_file.set_len(size as u64)?;
+ self.pool.resize(size as i32);
+ self.mmap = unsafe { MmapMut::map_mut(&self.mem_file) }?;
+ }
+
+ Ok(())
+ }
+
+ /// Returns a reference to the underlying shared memory file using the memmap2 crate.
+ pub fn mmap(&mut self) -> &mut MmapMut {
+ &mut self.mmap
+ }
+
+ /// Returns the size of the mempool
+ #[allow(clippy::len_without_is_empty)]
+ pub fn len(&self) -> usize {
+ self.len
+ }
+
+ /// Create a new buffer to this pool.
+ ///
+ /// ## Parameters
+ /// - `offset`: the offset (in bytes) from the beginning of the pool at which this buffer starts.
+ /// - `width` and `height`: the width and height of the buffer in pixels.
+ /// - `stride`: distance (in bytes) between the beginning of a row and the next one.
+ /// - `format`: the encoding format of the pixels.
+ ///
+ /// The encoding format of the pixels must be supported by the compositor or else a protocol error is
+ /// risen. You can ensure the format is supported by listening to [`Shm::formats`](crate::shm::Shm::formats).
+ ///
+ /// Note this function only creates the [`wl_buffer`] object, you will need to write to the pixels using the
+ /// [`io::Write`] implementation or [`RawPool::mmap`].
+ #[allow(clippy::too_many_arguments)]
+ pub fn create_buffer<D, U>(
+ &mut self,
+ offset: i32,
+ width: i32,
+ height: i32,
+ stride: i32,
+ format: wl_shm::Format,
+ udata: U,
+ qh: &QueueHandle<D>,
+ ) -> wl_buffer::WlBuffer
+ where
+ D: Dispatch<wl_buffer::WlBuffer, U> + 'static,
+ U: Send + Sync + 'static,
+ {
+ self.pool
+ .create_buffer(offset, width, height, stride, format, qh, udata)
+ }
+
+ /// Create a new buffer to this pool.
+ ///
+ /// This is identical to [`Self::create_buffer`], but allows using a custom [`ObjectData`]
+ /// implementation instead of relying on the [Dispatch] interface.
+ #[allow(clippy::too_many_arguments)]
+ pub fn create_buffer_raw(
+ &mut self,
+ offset: i32,
+ width: i32,
+ height: i32,
+ stride: i32,
+ format: wl_shm::Format,
+ data: Arc<dyn ObjectData + 'static>,
+ ) -> wl_buffer::WlBuffer {
+ self.pool
+ .send_constructor(
+ wl_shm_pool::Request::CreateBuffer {
+ offset,
+ width,
+ height,
+ stride,
+ format: WEnum::Value(format),
+ },
+ data,
+ )
+ .unwrap_or_else(|_| Proxy::inert(self.pool.backend().clone()))
+ }
+
+ /// Returns the pool object used to communicate with the server.
+ pub fn pool(&self) -> &wl_shm_pool::WlShmPool {
+ &self.pool
+ }
+}
+
+impl AsFd for RawPool {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.mem_file.as_fd()
+ }
+}
+
+impl From<RawPool> for OwnedFd {
+ fn from(pool: RawPool) -> Self {
+ pool.mem_file.into()
+ }
+}
+
+impl io::Write for RawPool {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ io::Write::write(&mut self.mem_file, buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ io::Write::flush(&mut self.mem_file)
+ }
+}
+
+impl io::Seek for RawPool {
+ fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
+ io::Seek::seek(&mut self.mem_file, pos)
+ }
+}
+
+impl RawPool {
+ fn create_shm_fd() -> io::Result<OwnedFd> {
+ #[cfg(target_os = "linux")]
+ {
+ match RawPool::create_memfd() {
+ Ok(fd) => return Ok(fd),
+
+ // Not supported, use fallback.
+ Err(Errno::NOSYS) => (),
+
+ Err(err) => return Err(Into::<io::Error>::into(err)),
+ }
+ }
+
+ let time = SystemTime::now();
+ let mut mem_file_handle = format!(
+ "/smithay-client-toolkit-{}",
+ time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
+ );
+
+ loop {
+ let flags = OFlags::CREATE | OFlags::EXCL | OFlags::RDWR;
+
+ let mode = Mode::RUSR | Mode::WUSR;
+
+ match rustix::shm::open(mem_file_handle.as_str(), flags, mode) {
+ Ok(fd) => match rustix::shm::unlink(mem_file_handle.as_str()) {
+ Ok(()) => return Ok(fd),
+
+ Err(errno) => {
+ return Err(errno.into());
+ }
+ },
+
+ Err(Errno::EXIST) => {
+ // Change the handle if we happen to be duplicate.
+ let time = SystemTime::now();
+
+ mem_file_handle = format!(
+ "/smithay-client-toolkit-{}",
+ time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
+ );
+ }
+
+ Err(Errno::INTR) => (),
+
+ Err(err) => return Err(err.into()),
+ }
+ }
+ }
+
+ #[cfg(target_os = "linux")]
+ fn create_memfd() -> rustix::io::Result<OwnedFd> {
+ use rustix::fs::{MemfdFlags, SealFlags};
+
+ loop {
+ let name = c"smithay-client-toolkit";
+ let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC;
+
+ match rustix::fs::memfd_create(name, flags) {
+ Ok(fd) => {
+ // We only need to seal for the purposes of optimization, ignore the errors.
+ let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL);
+ return Ok(fd);
+ }
+
+ Err(Errno::INTR) => (),
+
+ Err(err) => return Err(err),
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+struct DestroyOnDropPool(wl_shm_pool::WlShmPool);
+
+impl Deref for DestroyOnDropPool {
+ type Target = wl_shm_pool::WlShmPool;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl Drop for DestroyOnDropPool {
+ fn drop(&mut self) {
+ self.0.destroy();
+ }
+}
+
+#[derive(Debug)]
+struct ShmPoolData;
+
+impl ObjectData for ShmPoolData {
+ fn event(
+ self: Arc<Self>,
+ _: &wayland_client::backend::Backend,
+ _: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>,
+ ) -> Option<Arc<(dyn ObjectData + 'static)>> {
+ unreachable!("wl_shm_pool has no events")
+ }
+
+ fn destroyed(&self, _: wayland_client::backend::ObjectId) {}
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs
new file mode 100644
index 00000000..ab52c5f6
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs
@@ -0,0 +1,596 @@
+//! A pool implementation based on buffer slots
+
+use std::io;
+use std::{
+ os::unix::io::{AsRawFd, OwnedFd},
+ sync::{
+ atomic::{AtomicU8, AtomicUsize, Ordering},
+ Arc, Mutex, Weak,
+ },
+};
+
+use wayland_client::backend::protocol::Message;
+use wayland_client::backend::{ObjectData, ObjectId};
+use wayland_client::{
+ protocol::{wl_buffer, wl_shm, wl_surface},
+ Proxy,
+};
+
+use crate::wayland::shm::raw::RawPool;
+use crate::wayland::shm::CreatePoolError;
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum CreateBufferError {
+ /// Slot creation error.
+ #[error(transparent)]
+ Io(#[from] io::Error),
+
+ /// Pool mismatch.
+ #[error("Incorrect pool for slot")]
+ PoolMismatch,
+
+ /// Slot size mismatch
+ #[error("Requested buffer size is too large for slot")]
+ SlotTooSmall,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum ActivateSlotError {
+ /// Buffer was already active
+ #[error("Buffer was already active")]
+ AlreadyActive,
+}
+
+#[derive(Debug)]
+pub(crate) struct SlotPool {
+ pub(crate) inner: RawPool,
+ free_list: Arc<Mutex<Vec<FreelistEntry>>>,
+}
+
+#[derive(Debug)]
+struct FreelistEntry {
+ offset: usize,
+ len: usize,
+}
+
+/// A chunk of memory allocated from a [`SlotPool`]
+///
+/// Retaining this object is only required if you wish to resize or change the buffer's format
+/// without changing the contents of the backing memory.
+#[derive(Debug)]
+pub(crate) struct Slot {
+ inner: Arc<SlotInner>,
+}
+
+#[derive(Debug)]
+struct SlotInner {
+ free_list: Weak<Mutex<Vec<FreelistEntry>>>,
+ offset: usize,
+ len: usize,
+ active_buffers: AtomicUsize,
+ /// Count of all "real" references to this slot. This includes all Slot objects and any
+ /// [`BufferData`] object that is not in the DEAD state. When this reaches zero, the memory for
+ /// this slot will return to the [`free_list`]. It is not possible for it to reach zero and have a
+ /// Slot or Buffer referring to it.
+ all_refs: AtomicUsize,
+}
+
+/// A wrapper around a [`wl_buffer::WlBuffer`] which has been allocated via a [`SlotPool`].
+///
+/// When this object is dropped, the buffer will be destroyed immediately if it is not active, or
+/// upon the server's release if it is.
+#[derive(Debug)]
+pub(crate) struct Buffer {
+ inner: wl_buffer::WlBuffer,
+ height: i32,
+ stride: i32,
+ slot: Slot,
+}
+
+/// [`ObjectData`] for the [`WlBuffer`]
+#[derive(Debug)]
+struct BufferData {
+ inner: Arc<SlotInner>,
+ state: AtomicU8,
+}
+
+// These constants define the value of BufferData::state, since AtomicEnum does not exist.
+impl BufferData {
+ /// Buffer is counted in [`active_buffers`] list; will return to INACTIVE on Release.
+ const ACTIVE: u8 = 0;
+
+ /// Buffer is not counted in [`active_buffers`] list, but also has not been destroyed.
+ const INACTIVE: u8 = 1;
+
+ /// Buffer is counted in [`active_buffers`] list; will move to DEAD on Release
+ const DESTROY_ON_RELEASE: u8 = 2;
+
+ /// Buffer has been destroyed
+ const DEAD: u8 = 3;
+
+ /// Value that is [`ORed`] on buffer release to transition to the next state
+ const RELEASE_SET: u8 = 1;
+
+ /// Value that is [`ORed`] on buffer destroy to transition to the next state
+ const DESTROY_SET: u8 = 2;
+
+ /// Call after successfully transitioning the state to DEAD
+ fn record_death(&self) {
+ drop(Slot {
+ inner: self.inner.clone(),
+ });
+ }
+}
+
+impl SlotPool {
+ pub(crate) fn new(len: usize, shm: &wl_shm::WlShm) -> Result<Self, CreatePoolError> {
+ let inner = RawPool::new(len, shm)?;
+ let free_list = Arc::new(Mutex::new(vec![FreelistEntry {
+ offset: 0,
+ len: inner.len(),
+ }]));
+ Ok(SlotPool { inner, free_list })
+ }
+
+ /// Create a new buffer in a new slot.
+ ///
+ /// This returns the buffer and the canvas. The parameters are:
+ ///
+ /// - `width`: the width of this buffer (in pixels)
+ /// - `height`: the height of this buffer (in pixels)
+ /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+ /// - `format`: the encoding format of the pixels. Using a format that was not
+ /// advertised to the `wl_shm` global by the server is a protocol error and will
+ /// terminate your connection.
+ ///
+ /// The [Slot] for this buffer will have exactly the size required for the data. It can be
+ /// accessed via [`Buffer::slot`] to create additional buffers that point to the same data. This
+ /// is required if you wish to change formats, buffer dimensions, or attach a canvas to
+ /// multiple surfaces.
+ ///
+ /// For more control over sizing, use [`Self::new_slot`] and [`Self::create_buffer_in`].
+ pub(crate) fn create_buffer(
+ &mut self,
+ width: i32,
+ height: i32,
+ stride: i32,
+ format: wl_shm::Format,
+ ) -> Result<(Buffer, &mut [u8]), CreateBufferError> {
+ let len = (height as usize) * (stride as usize);
+ let slot = self.new_slot(len)?;
+ let buffer = self.create_buffer_in(&slot, width, height, stride, format)?;
+ let canvas = self.raw_data_mut(&slot);
+ Ok((buffer, canvas))
+ }
+
+ /// Get the bytes corresponding to a given slot or buffer if drawing to the slot is permitted.
+ ///
+ /// Returns `None` if there are active buffers in the slot or if the slot does not correspond
+ /// to this pool.
+ pub(crate) fn canvas(&mut self, key: &impl CanvasKey) -> Option<&mut [u8]> {
+ key.canvas(self)
+ }
+
+ /// Returns the size, in bytes, of this pool.
+ #[allow(clippy::len_without_is_empty)]
+ pub(crate) fn len(&self) -> usize {
+ self.inner.len()
+ }
+
+ /// Resizes the memory pool, notifying the server the pool has changed in size.
+ ///
+ /// This is an optimization; the pool automatically resizes when you allocate new slots.
+ pub(crate) fn resize(&mut self, size: usize) -> io::Result<()> {
+ let old_len = self.inner.len();
+ self.inner.resize(size)?;
+ let new_len = self.inner.len();
+ if old_len == new_len {
+ return Ok(());
+ }
+ // add the new memory to the freelist
+ let mut free = self.free_list.lock().unwrap();
+ if let Some(FreelistEntry { offset, len }) = free.last_mut() {
+ if *offset + *len == old_len {
+ *len += new_len - old_len;
+ return Ok(());
+ }
+ }
+ free.push(FreelistEntry {
+ offset: old_len,
+ len: new_len - old_len,
+ });
+ Ok(())
+ }
+
+ fn alloc(&mut self, size: usize) -> io::Result<usize> {
+ let mut free = self.free_list.lock().unwrap();
+ for FreelistEntry { offset, len } in free.iter_mut() {
+ if *len >= size {
+ let rv = *offset;
+ *len -= size;
+ *offset += size;
+ return Ok(rv);
+ }
+ }
+ let mut rv = self.inner.len();
+ let mut pop_tail = false;
+ if let Some(FreelistEntry { offset, len }) = free.last() {
+ if offset + len == self.inner.len() {
+ rv -= len;
+ pop_tail = true;
+ }
+ }
+ // resize like Vec::reserve, always at least doubling
+ let target = std::cmp::max(rv + size, self.inner.len() * 2);
+ self.inner.resize(target)?;
+ // adjust the end of the freelist here
+ if pop_tail {
+ free.pop();
+ }
+ if target > rv + size {
+ free.push(FreelistEntry {
+ offset: rv + size,
+ len: target - rv - size,
+ });
+ }
+ Ok(rv)
+ }
+
+ fn free(free_list: &Mutex<Vec<FreelistEntry>>, mut offset: usize, mut len: usize) {
+ let mut free = free_list.lock().unwrap();
+ let mut nf = Vec::with_capacity(free.len() + 1);
+ for &FreelistEntry {
+ offset: ioff,
+ len: ilen,
+ } in free.iter()
+ {
+ if ioff + ilen == offset {
+ offset = ioff;
+ len += ilen;
+ continue;
+ }
+ if ioff == offset + len {
+ len += ilen;
+ continue;
+ }
+ if ioff > offset + len && len != 0 {
+ nf.push(FreelistEntry { offset, len });
+ len = 0;
+ }
+ if ilen != 0 {
+ nf.push(FreelistEntry {
+ offset: ioff,
+ len: ilen,
+ });
+ }
+ }
+ if len != 0 {
+ nf.push(FreelistEntry { offset, len });
+ }
+ *free = nf;
+ }
+
+ /// Create a new slot with the given size in bytes.
+ pub(crate) fn new_slot(&mut self, mut len: usize) -> io::Result<Slot> {
+ len = (len + 63) & !63;
+ let offset = self.alloc(len)?;
+
+ Ok(Slot {
+ inner: Arc::new(SlotInner {
+ free_list: Arc::downgrade(&self.free_list),
+ offset,
+ len,
+ active_buffers: AtomicUsize::new(0),
+ all_refs: AtomicUsize::new(1),
+ }),
+ })
+ }
+
+ /// Get the bytes corresponding to a given slot.
+ ///
+ /// Note: prefer using [`Self::canvas`], which will prevent drawing to a buffer that has not been
+ /// released by the server.
+ ///
+ /// Returns an empty buffer if the slot does not belong to this pool.
+ pub(crate) fn raw_data_mut(&mut self, slot: &Slot) -> &mut [u8] {
+ if slot.inner.free_list.as_ptr() == Arc::as_ptr(&self.free_list) {
+ &mut self.inner.mmap()[slot.inner.offset..][..slot.inner.len]
+ } else {
+ &mut []
+ }
+ }
+
+ /// Create a new buffer corresponding to a slot.
+ ///
+ /// The parameters are:
+ ///
+ /// - `width`: the width of this buffer (in pixels)
+ /// - `height`: the height of this buffer (in pixels)
+ /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+ /// - `format`: the encoding format of the pixels. Using a format that was not
+ /// advertised to the `wl_shm` global by the server is a protocol error and will
+ /// terminate your connection
+ pub(crate) fn create_buffer_in(
+ &mut self,
+ slot: &Slot,
+ width: i32,
+ height: i32,
+ stride: i32,
+ format: wl_shm::Format,
+ ) -> Result<Buffer, CreateBufferError> {
+ let offset = slot.inner.offset as i32;
+ let len = (height as usize) * (stride as usize);
+ if len > slot.inner.len {
+ return Err(CreateBufferError::SlotTooSmall);
+ }
+
+ if slot.inner.free_list.as_ptr() != Arc::as_ptr(&self.free_list) {
+ return Err(CreateBufferError::PoolMismatch);
+ }
+
+ let slot = slot.clone();
+ // take a ref for the BufferData, which will be destroyed by BufferData::record_death
+ slot.inner.all_refs.fetch_add(1, Ordering::Relaxed);
+ let data = Arc::new(BufferData {
+ inner: slot.inner.clone(),
+ state: AtomicU8::new(BufferData::INACTIVE),
+ });
+ let buffer = self
+ .inner
+ .create_buffer_raw(offset, width, height, stride, format, data);
+ Ok(Buffer {
+ inner: buffer,
+ height,
+ stride,
+ slot,
+ })
+ }
+}
+
+impl Clone for Slot {
+ fn clone(&self) -> Self {
+ let inner = self.inner.clone();
+ inner.all_refs.fetch_add(1, Ordering::Relaxed);
+ Slot { inner }
+ }
+}
+
+impl Drop for Slot {
+ fn drop(&mut self) {
+ if self.inner.all_refs.fetch_sub(1, Ordering::Relaxed) == 1 {
+ if let Some(free_list) = self.inner.free_list.upgrade() {
+ SlotPool::free(&free_list, self.inner.offset, self.inner.len);
+ }
+ }
+ }
+}
+
+impl Drop for SlotInner {
+ fn drop(&mut self) {
+ debug_assert_eq!(*self.all_refs.get_mut(), 0);
+ }
+}
+
+/// A helper trait for [`SlotPool::canvas`].
+pub(crate) trait CanvasKey {
+ fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]>;
+}
+
+impl Slot {
+ /// Return true if there are buffers referencing this slot whose contents are being accessed
+ /// by the server.
+ pub(crate) fn has_active_buffers(&self) -> bool {
+ self.inner.active_buffers.load(Ordering::Relaxed) != 0
+ }
+
+ /// Returns the size, in bytes, of this slot.
+ #[allow(clippy::len_without_is_empty)]
+ pub(crate) fn len(&self) -> usize {
+ self.inner.len
+ }
+
+ /// Get the bytes corresponding to a given slot if drawing to the slot is permitted.
+ ///
+ /// Returns `None` if there are active buffers in the slot or if the slot does not correspond
+ /// to this pool.
+ pub(crate) fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+ if self.has_active_buffers() {
+ return None;
+ }
+ if self.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) {
+ Some(&mut pool.inner.mmap()[self.inner.offset..][..self.inner.len])
+ } else {
+ None
+ }
+ }
+}
+
+impl CanvasKey for Slot {
+ fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+ self.canvas(pool)
+ }
+}
+
+impl Buffer {
+ /// Attach a buffer to a surface.
+ ///
+ /// This marks the slot as active until the server releases the buffer, which will happen
+ /// automatically assuming the surface is committed without attaching a different buffer.
+ ///
+ /// Note: if you need to ensure that [`canvas()`](Buffer::canvas) calls never return data that
+ /// could be attached to a surface in a multi-threaded client, make this call while you have
+ /// exclusive access to the corresponding [`SlotPool`].
+ pub(crate) fn attach_to(&self, surface: &wl_surface::WlSurface) -> Result<(), ActivateSlotError> {
+ self.activate()?;
+ surface.attach(Some(&self.inner), 0, 0);
+ Ok(())
+ }
+
+ /// Get the inner buffer.
+ pub(crate) fn wl_buffer(&self) -> &wl_buffer::WlBuffer {
+ &self.inner
+ }
+
+ pub(crate) fn height(&self) -> i32 {
+ self.height
+ }
+
+ pub(crate) fn stride(&self) -> i32 {
+ self.stride
+ }
+
+ fn data(&self) -> Option<&BufferData> {
+ self.inner.object_data()?.downcast_ref()
+ }
+
+ /// Get the bytes corresponding to this buffer if drawing is permitted.
+ ///
+ /// This may be smaller than the canvas associated with the slot.
+ pub(crate) fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+ let len = (self.height as usize) * (self.stride as usize);
+ if self.slot.inner.active_buffers.load(Ordering::Relaxed) != 0 {
+ return None;
+ }
+ if self.slot.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) {
+ Some(&mut pool.inner.mmap()[self.slot.inner.offset..][..len])
+ } else {
+ None
+ }
+ }
+
+ /// Get the slot corresponding to this buffer.
+ pub(crate) fn slot(&self) -> Slot {
+ self.slot.clone()
+ }
+
+ /// Manually mark a buffer as active.
+ ///
+ /// An active buffer prevents drawing on its slot until a Release event is received or until
+ /// manually deactivated.
+ pub(crate) fn activate(&self) -> Result<(), ActivateSlotError> {
+ let data = self.data().expect("UserData type mismatch");
+
+ // This bitwise AND will transition INACTIVE -> ACTIVE, or do nothing if the buffer was
+ // already ACTIVE. No other ordering is required, as the server will not send a Release
+ // until we send our attach after returning Ok.
+ match data
+ .state
+ .fetch_and(!BufferData::RELEASE_SET, Ordering::Relaxed)
+ {
+ BufferData::INACTIVE => {
+ data.inner.active_buffers.fetch_add(1, Ordering::Relaxed);
+ Ok(())
+ }
+ BufferData::ACTIVE => Err(ActivateSlotError::AlreadyActive),
+ _ => unreachable!("Invalid state in BufferData"),
+ }
+ }
+
+ /// Manually mark a buffer as inactive.
+ ///
+ /// This should be used when the buffer was manually marked as active or when a buffer was
+ /// attached to a surface but not committed. Calling this function on a buffer that was
+ /// committed to a surface risks making the surface contents undefined.
+ pub(crate) fn deactivate(&self) -> Result<(), ActivateSlotError> {
+ let data = self.data().expect("UserData type mismatch");
+
+ // Same operation as the Release event, but we know the Buffer was not dropped.
+ match data
+ .state
+ .fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed)
+ {
+ BufferData::ACTIVE => {
+ data.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+ Ok(())
+ }
+ BufferData::INACTIVE => Err(ActivateSlotError::AlreadyActive),
+ _ => unreachable!("Invalid state in BufferData"),
+ }
+ }
+}
+
+impl CanvasKey for Buffer {
+ fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+ self.canvas(pool)
+ }
+}
+
+impl Drop for Buffer {
+ fn drop(&mut self) {
+ if let Some(data) = self.data() {
+ match data
+ .state
+ .fetch_or(BufferData::DESTROY_SET, Ordering::Relaxed)
+ {
+ BufferData::ACTIVE => {
+ // server is using the buffer, let ObjectData handle the destroy
+ }
+ BufferData::INACTIVE => {
+ data.record_death();
+ self.inner.destroy();
+ }
+ _ => unreachable!("Invalid state in BufferData"),
+ }
+ }
+ }
+}
+
+impl ObjectData for BufferData {
+ fn event(
+ self: Arc<Self>,
+ handle: &wayland_client::backend::Backend,
+ msg: Message<ObjectId, OwnedFd>,
+ ) -> Option<Arc<dyn ObjectData>> {
+ debug_assert!(wayland_client::backend::protocol::same_interface(
+ msg.sender_id.interface(),
+ wl_buffer::WlBuffer::interface()
+ ));
+ debug_assert!(msg.opcode == 0);
+
+ match self
+ .state
+ .fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed)
+ {
+ BufferData::ACTIVE => {
+ self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+ }
+ BufferData::INACTIVE => {
+ // possible spurious release, or someone called deactivate incorrectly
+ eprintln!("Unexpected WlBuffer::Release on an inactive buffer");
+ }
+ BufferData::DESTROY_ON_RELEASE => {
+ self.record_death();
+ self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+
+ // The Destroy message is identical to Release message (no args, same ID), so just reply
+ handle
+ .send_request(msg.map_fd(|x| x.as_raw_fd()), None, None)
+ .expect("Unexpected invalid ID");
+ }
+ BufferData::DEAD => {
+ // no-op, this object is already unusable
+ }
+ _ => unreachable!("Invalid state in BufferData"),
+ }
+
+ None
+ }
+
+ fn destroyed(&self, _: ObjectId) {}
+}
+
+impl Drop for BufferData {
+ fn drop(&mut self) {
+ let state = *self.state.get_mut();
+ if state == BufferData::ACTIVE || state == BufferData::DESTROY_ON_RELEASE {
+ // Release the active-buffer count
+ self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+ }
+
+ if state != BufferData::DEAD {
+ // nobody has ever transitioned state to DEAD, so we are responsible for freeing the
+ // extra reference
+ self.record_death();
+ }
+ }
+}