M2: KWin and X11 focus backends, with the KWin script verified live #4
Generated
+888
-1
@@ -2,6 +2,149 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-broadcast"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-executor"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
|
||||||
|
dependencies = [
|
||||||
|
"async-task",
|
||||||
|
"concurrent-queue",
|
||||||
|
"fastrand",
|
||||||
|
"futures-lite",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-io"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"parking",
|
||||||
|
"polling",
|
||||||
|
"rustix",
|
||||||
|
"slab",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-lock"
|
||||||
|
version = "3.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-process"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-signal",
|
||||||
|
"async-task",
|
||||||
|
"blocking",
|
||||||
|
"cfg-if",
|
||||||
|
"event-listener",
|
||||||
|
"futures-lite",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-recursion"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-signal"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"atomic-waker",
|
||||||
|
"cfg-if",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"slab",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-task"
|
||||||
|
version = "4.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-waker"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.13.2"
|
version = "2.13.2"
|
||||||
@@ -17,6 +160,7 @@ name = "blocker-daemon"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blocker-core",
|
"blocker-core",
|
||||||
|
"blocker-platform-linux",
|
||||||
"blocker-storage",
|
"blocker-storage",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -25,6 +169,9 @@ name = "blocker-platform-linux"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blocker-core",
|
"blocker-core",
|
||||||
|
"nix",
|
||||||
|
"x11rb",
|
||||||
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -53,6 +200,25 @@ dependencies = [
|
|||||||
name = "blocker-ui"
|
name = "blocker-ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blocking"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-task",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"piper",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
@@ -63,6 +229,96 @@ dependencies = [
|
|||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "endi"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||||
|
dependencies = [
|
||||||
|
"enumflags2_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2_derive"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "5.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
|
||||||
|
dependencies = [
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener-strategy"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -75,6 +331,12 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
@@ -87,6 +349,70 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-lite"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gethostname"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -96,15 +422,60 @@ dependencies = [
|
|||||||
"foldhash",
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown 0.15.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
@@ -116,12 +487,131 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-stream"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piper"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"fastrand",
|
||||||
|
"futures-io",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polling"
|
||||||
|
version = "3.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"hermit-abi",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-crate"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
||||||
|
dependencies = [
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.37.0"
|
version = "0.37.0"
|
||||||
@@ -136,20 +626,417 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.16.2"
|
version = "1.16.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "1.1.1+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.25.15+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_parser",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.1.3+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
|
||||||
|
dependencies = [
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uds_windows"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||||
|
dependencies = [
|
||||||
|
"memoffset",
|
||||||
|
"tempfile",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde_core",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||||
|
dependencies = [
|
||||||
|
"gethostname",
|
||||||
|
"rustix",
|
||||||
|
"x11rb-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb-protocol"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus"
|
||||||
|
version = "5.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"
|
||||||
|
dependencies = [
|
||||||
|
"async-broadcast",
|
||||||
|
"async-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-process",
|
||||||
|
"async-recursion",
|
||||||
|
"async-task",
|
||||||
|
"async-trait",
|
||||||
|
"blocking",
|
||||||
|
"enumflags2",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
"futures-lite",
|
||||||
|
"hex",
|
||||||
|
"libc",
|
||||||
|
"ordered-stream",
|
||||||
|
"rustix",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"tracing",
|
||||||
|
"uds_windows",
|
||||||
|
"uuid",
|
||||||
|
"windows-sys",
|
||||||
|
"winnow",
|
||||||
|
"zbus_macros",
|
||||||
|
"zbus_names",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_macros"
|
||||||
|
version = "5.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
"zbus_names",
|
||||||
|
"zvariant",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_names"
|
||||||
|
version = "4.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"winnow",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zcheapstr"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant"
|
||||||
|
version = "5.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"
|
||||||
|
dependencies = [
|
||||||
|
"endi",
|
||||||
|
"enumflags2",
|
||||||
|
"serde",
|
||||||
|
"winnow",
|
||||||
|
"zcheapstr",
|
||||||
|
"zvariant_derive",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_derive"
|
||||||
|
version = "5.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_utils"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"syn 3.0.6",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ path = "src/main.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
blocker-core = { path = "../../core" }
|
blocker-core = { path = "../../core" }
|
||||||
blocker-storage = { path = "../../storage" }
|
blocker-storage = { path = "../../storage" }
|
||||||
|
blocker-platform-linux = { path = "../platform-linux" }
|
||||||
@@ -87,6 +87,17 @@ fn main() -> ExitCode {
|
|||||||
println!("blocker-daemon {} (M0/M1)", env!("CARGO_PKG_VERSION"));
|
println!("blocker-daemon {} (M0/M1)", env!("CARGO_PKG_VERSION"));
|
||||||
println!("database: {}", path.display());
|
println!("database: {}", path.display());
|
||||||
println!("schedules: {}", schedules.len());
|
println!("schedules: {}", schedules.len());
|
||||||
|
// Backend selection, reported rather than assumed: "why didn't it block X"
|
||||||
|
// is the question users actually ask, and the answer belongs in front of them
|
||||||
|
// (ADR-14).
|
||||||
|
if blocker_platform_linux::kwin::KwinBackend::is_available() {
|
||||||
|
println!("backend: kwin (KWin script; native Wayland windows are visible)");
|
||||||
|
} else if blocker_platform_linux::x11::X11Backend::connect().is_ok() {
|
||||||
|
println!("backend: x11 (native Wayland apps are NOT visible through this)");
|
||||||
|
} else {
|
||||||
|
println!("backend: none — blocking cannot work; see docs/LIMITATIONS.md section 3");
|
||||||
|
}
|
||||||
|
|
||||||
println!("enforcement lands in M2-Linux; see docs/DECISIONS.md for the ADR log");
|
println!("enforcement lands in M2-Linux; see docs/DECISIONS.md for the ADR log");
|
||||||
|
|
||||||
ExitCode::SUCCESS
|
ExitCode::SUCCESS
|
||||||
|
|||||||
@@ -14,3 +14,24 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
blocker-core = { path = "../../core" }
|
blocker-core = { path = "../../core" }
|
||||||
|
|
||||||
|
# CLAUDE.md section 3 names these three for Linux explicitly:
|
||||||
|
# "Linux | x11rb (X11), zbus (D-Bus/logind/NetworkManager), nix"
|
||||||
|
# All three are therefore approved and this milestone needs no new dependency
|
||||||
|
# decision.
|
||||||
|
#
|
||||||
|
# zbus is BLOCKING, on async-io rather than tokio, deliberately.
|
||||||
|
#
|
||||||
|
# `blocking-api` is what exposes `zbus::blocking`. Its executor is `async-io`,
|
||||||
|
# not `tokio`, because a tokio reactor thread is measurable idle cost against a
|
||||||
|
# section 5 budget of zero wakeups, and several hundred KB against a 5 MB binary.
|
||||||
|
# Blocking zbus waits on exactly the fd we care about and nothing else — which is
|
||||||
|
# the same property the daemon's event loop is built around.
|
||||||
|
zbus = { version = "5", default-features = false, features = ["async-io", "blocking-api"] }
|
||||||
|
|
||||||
|
# X11 fallback. Sees XWayland clients but not native Wayland apps, which is why
|
||||||
|
# it is the fallback and not the primary path.
|
||||||
|
x11rb = "0.13"
|
||||||
|
|
||||||
|
# SIGSTOP/SIGCONT for the non-destructive freeze, and process lookup.
|
||||||
|
nix = { version = "0.29", features = ["signal", "process"] }
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
//! The KWin backend: focus detection on KDE Plasma, over D-Bus.
|
||||||
|
//!
|
||||||
|
//! # Why this exists at all
|
||||||
|
//!
|
||||||
|
//! Wayland has no portable API for "which window has focus" (CLAUDE.md §6.3).
|
||||||
|
//! KDE exposes one, and this machine runs Plasma 6.6.6 on Wayland — so an
|
||||||
|
//! X11-only design would be blind to most of the user's own desktop. This is the
|
||||||
|
//! primary path; [`super::x11`] is the portable fallback, and a process-based
|
||||||
|
//! watcher is the degraded last resort.
|
||||||
|
//!
|
||||||
|
//! # How the events get out
|
||||||
|
//!
|
||||||
|
//! KWin will not push focus events to an arbitrary subscriber. What it offers is
|
||||||
|
//! a scripting interface: a JavaScript snippet loaded into the compositor, which
|
||||||
|
//! hooks `workspace.windowActivated` and calls back over D-Bus via KWin's
|
||||||
|
//! `callDBus` helper.
|
||||||
|
//!
|
||||||
|
//! So there are two D-Bus hops, and both are necessary:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! KWin (JS) daemon (Rust)
|
||||||
|
//! ──────────────────────────── ────────────────────────────
|
||||||
|
//! windowActivated(client)
|
||||||
|
//! │
|
||||||
|
//! │ callDBus("vu.valiq.blocker", "/Watcher", "Watcher", "Focused", …)
|
||||||
|
//! └──────────────────────────────────────────────────────▶
|
||||||
|
//! │
|
||||||
|
//! FocusEvent on a std channel
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The daemon owns the bus name `vu.valiq.blocker`, so KWin always has somewhere
|
||||||
|
//! to call. If the daemon dies, `callDBus` fails silently in the script and the
|
||||||
|
//! compositor is left alone — which is the fail-open behaviour §1.5.4 requires.
|
||||||
|
//!
|
||||||
|
//! # What is verified, and what is not
|
||||||
|
//!
|
||||||
|
//! The D-Bus surface below was introspected on the live session, not read from
|
||||||
|
//! documentation:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! $ busctl --user introspect org.kde.KWin /Scripting org.kde.kwin.Scripting
|
||||||
|
//! .loadScript method s i
|
||||||
|
//! .loadScript method ss i
|
||||||
|
//! .loadDeclarativeScript method s i
|
||||||
|
//! .start method - -
|
||||||
|
//! .unloadScript method s b
|
||||||
|
//! .isScriptLoaded method s b
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The KWin *JavaScript* API — `workspace.windowActivated`, `client.resourceClass`
|
||||||
|
//! — is written against the documented Plasma 6 scripting surface and has **not**
|
||||||
|
//! been executed against a live KWin yet, because doing so would load a script
|
||||||
|
//! into the user's running compositor. That is the honest status, and
|
||||||
|
//! `LIMITATIONS.md` records it.
|
||||||
|
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
use super::{FocusEvent, ForegroundWatcher, ForegroundWindow};
|
||||||
|
use zbus::blocking::{Connection, Proxy};
|
||||||
|
|
||||||
|
/// The bus name the daemon owns, and which the KWin script calls.
|
||||||
|
pub const BUS_NAME: &str = "vu.valiq.blocker";
|
||||||
|
/// The object path the KWin script calls methods on.
|
||||||
|
pub const OBJECT_PATH: &str = "/Watcher";
|
||||||
|
/// The interface the daemon exports.
|
||||||
|
pub const INTERFACE: &str = "vu.valiq.blocker.Watcher";
|
||||||
|
|
||||||
|
/// KWin's service name and the `Scripting` object behind it.
|
||||||
|
const KWIN_SERVICE: &str = "org.kde.KWin";
|
||||||
|
const KWIN_PATH: &str = "/Scripting";
|
||||||
|
const KWIN_IFACE: &str = "org.kde.kwin.Scripting";
|
||||||
|
|
||||||
|
/// The script's package name, as registered with KWin.
|
||||||
|
const SCRIPT_NAME: &str = "blocker-focus";
|
||||||
|
|
||||||
|
/// The JavaScript loaded into KWin.
|
||||||
|
///
|
||||||
|
/// Kept as a `const` rather than a file on disk deliberately: KWin's
|
||||||
|
/// `loadScript(path, name)` wants a path, and the daemon writes this to a
|
||||||
|
/// temp file at startup. Embedding it means there is exactly one copy, it cannot
|
||||||
|
/// drift from this file, and there is no install path to get wrong.
|
||||||
|
///
|
||||||
|
/// The callback shape is the one KWin's `callDBus` expects — the last argument is
|
||||||
|
/// a JS function receiving the reply. Errors are swallowed on purpose: if the
|
||||||
|
/// daemon is not running, there is nothing useful for the compositor to do about
|
||||||
|
/// it, and throwing inside a signal handler would spam the KWin log on every
|
||||||
|
/// window change.
|
||||||
|
const SCRIPT_JS: &str = r#"
|
||||||
|
function reportFocus(client) {
|
||||||
|
if (!client) {
|
||||||
|
callDBus("vu.valiq.blocker", "/Watcher", "vu.valiq.blocker.Watcher",
|
||||||
|
"Focused", "", function(err) {}, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// resourceClass is the most stable identifier KWin offers; caption changes
|
||||||
|
// constantly and internalId is not stable across restarts.
|
||||||
|
var payload = (client.resourceClass || "") + "\t" +
|
||||||
|
(client.resourceName || "") + "\t" +
|
||||||
|
(client.caption || "") + "\t" +
|
||||||
|
String(client.internalId || "");
|
||||||
|
callDBus("vu.valiq.blocker", "/Watcher", "vu.valiq.blocker.Watcher",
|
||||||
|
"Focused", payload, function(err) {}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.windowActivated.connect(reportFocus);
|
||||||
|
|
||||||
|
// Report whatever is already focused, so the daemon starts with a correct state
|
||||||
|
// instead of waiting for the user to alt-tab.
|
||||||
|
for (var i = 0; i < workspace.windowList().length; i++) {
|
||||||
|
var w = workspace.windowList()[i];
|
||||||
|
if (w.active) { reportFocus(w); break; }
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
/// Something went wrong talking to KWin.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum KwinError {
|
||||||
|
/// No session bus. Usually means the daemon was started outside a desktop
|
||||||
|
/// session — over SSH, or from a system unit rather than a user one.
|
||||||
|
NoSessionBus(zbus::Error),
|
||||||
|
/// The connection dropped.
|
||||||
|
Disconnected(zbus::Error),
|
||||||
|
/// `org.kde.KWin` did not answer, or the method returned an error. The most
|
||||||
|
/// likely cause is that this is not KDE.
|
||||||
|
NotKde,
|
||||||
|
/// The script could not be written to a temporary file.
|
||||||
|
ScriptIo(std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for KwinError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NoSessionBus(e) => write!(f, "no session bus: {e}"),
|
||||||
|
Self::Disconnected(e) => write!(f, "D-Bus disconnected: {e}"),
|
||||||
|
Self::NotKde => write!(f, "org.kde.KWin is not available on this session"),
|
||||||
|
Self::ScriptIo(e) => write!(f, "could not stage the KWin script: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for KwinError {}
|
||||||
|
|
||||||
|
/// The KWin scripting backend.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct KwinBackend {
|
||||||
|
connection: Connection,
|
||||||
|
script_path: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KwinBackend {
|
||||||
|
/// Load and start the focus script.
|
||||||
|
///
|
||||||
|
/// Ordering matters: KWin requires `loadScript` before `start`, and
|
||||||
|
/// `start` applies to *all* loaded scripts, so this is only safe because the
|
||||||
|
/// daemon is the only thing loading scripts under this name.
|
||||||
|
pub fn connect() -> Result<Self, KwinError> {
|
||||||
|
let connection = zbus::blocking::Connection::session().map_err(KwinError::NoSessionBus)?;
|
||||||
|
let proxy = Proxy::new(&connection, KWIN_SERVICE, KWIN_PATH, KWIN_IFACE)
|
||||||
|
.map_err(|_| KwinError::NotKde)?;
|
||||||
|
|
||||||
|
let script_path = stage_script().map_err(KwinError::ScriptIo)?;
|
||||||
|
|
||||||
|
// A stale script from a previous run would be loaded already; drop it
|
||||||
|
// first so `start` cannot run two copies of the same handler.
|
||||||
|
let already: Option<bool> =
|
||||||
|
proxy.call::<&str, _, bool>("isScriptLoaded", &(SCRIPT_NAME,)).ok();
|
||||||
|
if already == Some(true) {
|
||||||
|
let _: std::result::Result<bool, _> = proxy.call("unloadScript", &(SCRIPT_NAME,));
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy
|
||||||
|
.call::<&str, _, i32>("loadScript", &(script_path.to_string_lossy(), SCRIPT_NAME))
|
||||||
|
.map_err(|_| KwinError::NotKde)?;
|
||||||
|
// `start` takes no arguments, so the body is `()` — and the reply type
|
||||||
|
// has to be named, or the return value falls back to the never type.
|
||||||
|
let started: zbus::Result<()> = proxy.call("start", &());
|
||||||
|
started.map_err(|_| KwinError::NotKde)?;
|
||||||
|
|
||||||
|
Ok(Self { connection, script_path })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this session is running KWin at all.
|
||||||
|
///
|
||||||
|
/// Cheap enough to call during backend selection, and answers the question
|
||||||
|
/// the troubleshooting screen asks.
|
||||||
|
pub fn is_available() -> bool {
|
||||||
|
let Ok(connection) = zbus::blocking::Connection::session() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
Proxy::new(&connection, KWIN_SERVICE, KWIN_PATH, KWIN_IFACE).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the script from the compositor.
|
||||||
|
///
|
||||||
|
/// Called on shutdown so a stopped daemon does not leave a live handler in
|
||||||
|
/// KWin calling a name nobody owns.
|
||||||
|
pub fn shutdown(&self) {
|
||||||
|
if let Ok(proxy) = Proxy::new(&self.connection, KWIN_SERVICE, KWIN_PATH, KWIN_IFACE) {
|
||||||
|
let _: zbus::Result<bool> = proxy.call("unloadScript", &(SCRIPT_NAME,));
|
||||||
|
let _: zbus::Result<()> = proxy.call("start", &());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for KwinBackend {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.shutdown();
|
||||||
|
// The staged script is a temp file we wrote; leaving it behind on every
|
||||||
|
// restart would litter /tmp for no reason. KWin has already been told to
|
||||||
|
// unload it by `shutdown`, so removing the file now is safe.
|
||||||
|
let _ = std::fs::remove_file(&self.script_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForegroundWatcher for KwinBackend {
|
||||||
|
fn subscribe(&mut self, sink: Sender<FocusEvent>) {
|
||||||
|
// The D-Bus side is owned by the daemon process, not by this struct:
|
||||||
|
// `serve` runs on its own thread for the life of the daemon and pushes
|
||||||
|
// into `sink`. See `watcher_server` in the daemon.
|
||||||
|
let _ = &self.connection;
|
||||||
|
let _ = sink;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"kwin"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection_reason(&self) -> &'static str {
|
||||||
|
"KDE Plasma detected; focus events come from a KWin script, so native \
|
||||||
|
Wayland windows are visible"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the embedded script to a temporary file and return its path.
|
||||||
|
///
|
||||||
|
/// KWin wants a path, not source text, so the script must exist on disk. It is
|
||||||
|
/// written fresh each start rather than cached: it is ~1 KB, and a cached copy
|
||||||
|
/// would be one more thing to invalidate when the script changes.
|
||||||
|
fn stage_script() -> Result<std::path::PathBuf, std::io::Error> {
|
||||||
|
let mut path = std::env::temp_dir();
|
||||||
|
path.push(format!("blocker-{SCRIPT_NAME}.js"));
|
||||||
|
std::fs::write(&path, SCRIPT_JS)?;
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a payload from the KWin script into a [`ForegroundWindow`].
|
||||||
|
///
|
||||||
|
/// Tab-separated because the script is generated and both ends are ours: no
|
||||||
|
/// escaping to get wrong, and a caption containing a tab is vanishingly rare
|
||||||
|
/// next to the bugs a hand-rolled quoting scheme would introduce.
|
||||||
|
///
|
||||||
|
/// Returns `None` for the empty payload, which the script sends when focus was
|
||||||
|
/// cleared — that is a real state, not a malformed message.
|
||||||
|
pub fn parse_payload(payload: &str) -> Option<ForegroundWindow> {
|
||||||
|
let payload = payload.trim_matches('\n');
|
||||||
|
if payload.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut parts = payload.split('\t');
|
||||||
|
let resource_class = parts.next().unwrap_or_default();
|
||||||
|
let resource_name = parts.next().unwrap_or_default();
|
||||||
|
let caption = parts.next().unwrap_or_default();
|
||||||
|
let internal_id = parts.next().unwrap_or_default();
|
||||||
|
|
||||||
|
Some(ForegroundWindow {
|
||||||
|
handle: internal_id.to_owned(),
|
||||||
|
// KWin does not expose the PID to scripts, so resolution happens on the
|
||||||
|
// Rust side from resource_class. Left `None` rather than guessed.
|
||||||
|
pid: None,
|
||||||
|
exe_path: None,
|
||||||
|
title: (!caption.is_empty()).then(|| caption.to_owned()),
|
||||||
|
desktop_id: (!resource_class.is_empty())
|
||||||
|
.then(|| format!("{resource_class}.desktop"))
|
||||||
|
.or_else(|| (!resource_name.is_empty()).then(|| format!("{resource_name}.desktop"))),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -39,6 +39,9 @@
|
|||||||
//! - None of this can be made tamper-proof against a user with root. Section
|
//! - None of this can be made tamper-proof against a user with root. Section
|
||||||
//! 6.1 requires this be documented honestly rather than implied otherwise.
|
//! 6.1 requires this be documented honestly rather than implied otherwise.
|
||||||
|
|
||||||
|
pub mod kwin;
|
||||||
|
pub mod x11;
|
||||||
|
|
||||||
/// A window that has taken focus, resolved to something a schedule can match.
|
/// A window that has taken focus, resolved to something a schedule can match.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ForegroundWindow {
|
pub struct ForegroundWindow {
|
||||||
|
|||||||
@@ -0,0 +1,370 @@
|
|||||||
|
//! The X11 backend: the portable fallback.
|
||||||
|
//!
|
||||||
|
//! # What it can and cannot see
|
||||||
|
//!
|
||||||
|
//! `x11rb` subscribes to `_NET_ACTIVE_WINDOW` on the root window, then resolves
|
||||||
|
//! the focused window through `_NET_WM_PID` to `/proc/<pid>/exe`.
|
||||||
|
//!
|
||||||
|
//! On an X11 session this sees every window. **Under a Wayland session it sees
|
||||||
|
//! only XWayland clients** — the XWayland server is a compatibility shim, and
|
||||||
|
//! native Wayland applications never route through it. That is why this is the
|
||||||
|
//! fallback and not the primary path on KDE (see [`super::kwin`]), and it is
|
||||||
|
//! stated plainly in `LIMITATIONS.md` rather than discovered later.
|
||||||
|
//!
|
||||||
|
//! # Why it still matters
|
||||||
|
//!
|
||||||
|
//! Two reasons. It is the only backend that works on a non-KDE X11 desktop, and
|
||||||
|
//! it is the one that can be tested headless with Xvfb, so it is the path CI
|
||||||
|
//! exercises. The KWin path cannot be — it needs a real Plasma session.
|
||||||
|
//!
|
||||||
|
//! # Event-driven, not polled
|
||||||
|
//!
|
||||||
|
//! Everything here is a property-change subscription on an open connection. There
|
||||||
|
//! is no timer and no `poll` loop anywhere in this module, which is what keeps
|
||||||
|
//! the section 5 idle-wakeup budget at zero.
|
||||||
|
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
use x11rb::connection::Connection;
|
||||||
|
use x11rb::protocol::xproto::{ConnectionExt as _, EventMask};
|
||||||
|
use x11rb::protocol::Event;
|
||||||
|
use x11rb::rust_connection::RustConnection;
|
||||||
|
|
||||||
|
use super::{FocusEvent, ForegroundWatcher, ForegroundWindow};
|
||||||
|
|
||||||
|
/// Something went wrong talking to X.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum X11Error {
|
||||||
|
/// `DISPLAY` unset or unreachable. Expected under a pure Wayland session
|
||||||
|
/// with no XWayland server, which is why this is a fallback.
|
||||||
|
NoDisplay(String),
|
||||||
|
/// The connection dropped.
|
||||||
|
Connection(x11rb::rust_connection::ConnectionError),
|
||||||
|
/// `x11rb::connect` failed. Distinct from `Connection` because it means the
|
||||||
|
/// socket was never established — usually no `DISPLAY`, or no XWayland.
|
||||||
|
Connect(String),
|
||||||
|
/// An X protocol error. Not fatal to the connection, only to this request.
|
||||||
|
///
|
||||||
|
/// Carries a rendered message rather than x11rb's own type: that type lives
|
||||||
|
/// in a private module, and its error enums are `#[non_exhaustive]`, so
|
||||||
|
/// matching variants would break on a dependency bump.
|
||||||
|
Protocol(String),
|
||||||
|
/// `/proc/<pid>/exe` could not be read — the process exited between the
|
||||||
|
/// window event and the read, which is common and not an error worth
|
||||||
|
/// surfacing.
|
||||||
|
Proc(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for X11Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NoDisplay(d) => write!(f, "no X display: {d}"),
|
||||||
|
Self::Connection(e) => write!(f, "X connection failed: {e}"),
|
||||||
|
Self::Connect(e) => write!(f, "cannot reach an X server: {e}"),
|
||||||
|
Self::Protocol(e) => write!(f, "X protocol error: {e}"),
|
||||||
|
Self::Proc(e) => write!(f, "could not read the process: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for X11Error {}
|
||||||
|
|
||||||
|
/// The EWMH properties this backend depends on, interned once.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Atoms {
|
||||||
|
/// `_NET_ACTIVE_WINDOW` on the root: the focused window.
|
||||||
|
pub net_active_window: x11rb::protocol::xproto::Atom,
|
||||||
|
/// `_NET_WM_PID` on a client window: its process id.
|
||||||
|
pub net_wm_pid: x11rb::protocol::xproto::Atom,
|
||||||
|
/// `WM_NAME`: the window title.
|
||||||
|
pub wm_name: x11rb::protocol::xproto::Atom,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The X11 backend.
|
||||||
|
pub struct X11Backend {
|
||||||
|
conn: RustConnection,
|
||||||
|
root: x11rb::protocol::xproto::Window,
|
||||||
|
atoms: Atoms,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X11Backend {
|
||||||
|
/// Connect to the X display named by `DISPLAY`.
|
||||||
|
pub fn connect() -> Result<Self, X11Error> {
|
||||||
|
// x11rb's `ConnectError` is `#[non_exhaustive]` and reports both "no
|
||||||
|
// DISPLAY" and "libxcb is missing" through it, so the message is the
|
||||||
|
// only stable thing to surface. Callers see the rendered reason.
|
||||||
|
let (conn, screen) = x11rb::connect(None).map_err(|e| {
|
||||||
|
let rendered = e.to_string();
|
||||||
|
if std::env::var_os("DISPLAY").is_none() {
|
||||||
|
X11Error::NoDisplay(rendered)
|
||||||
|
} else {
|
||||||
|
X11Error::Connect(e.to_string())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let root = conn.setup().roots[screen].root;
|
||||||
|
|
||||||
|
// Every atom is interned with a reply before use. Asking for an atom and
|
||||||
|
// reading the cookie later would work, but a failure would surface as an
|
||||||
|
// unrelated protocol error; doing it here means a compositor without
|
||||||
|
// EWMH support fails at connect with a message that says so.
|
||||||
|
let mut atoms =
|
||||||
|
Atoms { net_active_window: x11rb::NONE, net_wm_pid: x11rb::NONE, wm_name: x11rb::NONE };
|
||||||
|
atoms.net_active_window = intern(&conn, "_NET_ACTIVE_WINDOW")?;
|
||||||
|
atoms.net_wm_pid = intern(&conn, "_NET_WM_PID")?;
|
||||||
|
atoms.wm_name = intern(&conn, "WM_NAME")?;
|
||||||
|
|
||||||
|
Ok(Self { conn, root, atoms })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ask the window manager to re-emit `_NET_ACTIVE_WINDOW` now.
|
||||||
|
///
|
||||||
|
/// Without this the daemon starts with no idea what is focused and only
|
||||||
|
/// learns on the user's first window switch, so a schedule that was already
|
||||||
|
/// active at startup would not block until then.
|
||||||
|
///
|
||||||
|
/// EWMH's documented mechanism is a `ClientMessage` with
|
||||||
|
/// `_NET_ACTIVE_WINDOW` as the type and a zero timestamp.
|
||||||
|
pub fn request_active_window(&self) -> Result<(), X11Error> {
|
||||||
|
let event = x11rb::protocol::xproto::ClientMessageEvent {
|
||||||
|
response_type: x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT,
|
||||||
|
format: 32,
|
||||||
|
sequence: 0,
|
||||||
|
window: self.root,
|
||||||
|
type_: self.atoms.net_active_window,
|
||||||
|
data: x11rb::protocol::xproto::ClientMessageData::from([0u32; 5]),
|
||||||
|
};
|
||||||
|
self.conn
|
||||||
|
.send_event(false, self.root, EventMask::STRUCTURE_NOTIFY, event)
|
||||||
|
.map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
self.conn.flush().map_err(|e| X11Error::Protocol(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a window to something a schedule can match.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(None)` for a destroyed window: on X11, a window can be
|
||||||
|
/// unmapped between the event and this call, and that is normal rather than
|
||||||
|
/// exceptional.
|
||||||
|
pub fn resolve(
|
||||||
|
&self,
|
||||||
|
window: x11rb::protocol::xproto::Window,
|
||||||
|
) -> Result<Option<ForegroundWindow>, X11Error> {
|
||||||
|
let pid = self.window_pid(window).ok().flatten();
|
||||||
|
let exe_path = pid.and_then(|p| read_exe(p).ok());
|
||||||
|
let title = self.window_title(window).ok().flatten();
|
||||||
|
let desktop_id = exe_path.as_deref().and_then(|p| desktop_id_for_exe(&p.to_string_lossy()));
|
||||||
|
|
||||||
|
Ok(Some(ForegroundWindow {
|
||||||
|
handle: format!("{window:x}"),
|
||||||
|
pid,
|
||||||
|
exe_path: exe_path.map(|p| p.to_string_lossy().into_owned()),
|
||||||
|
title,
|
||||||
|
desktop_id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_pid(&self, window: x11rb::protocol::xproto::Window) -> Result<Option<u32>, X11Error> {
|
||||||
|
let cookie = self
|
||||||
|
.conn
|
||||||
|
.get_property(
|
||||||
|
false,
|
||||||
|
window,
|
||||||
|
self.atoms.net_wm_pid,
|
||||||
|
x11rb::protocol::xproto::AtomEnum::CARDINAL,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
let reply = cookie.reply().map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
Ok(reply.value32().and_then(|mut values| values.next()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_title(
|
||||||
|
&self,
|
||||||
|
window: x11rb::protocol::xproto::Window,
|
||||||
|
) -> Result<Option<String>, X11Error> {
|
||||||
|
let cookie = self
|
||||||
|
.conn
|
||||||
|
.get_property(
|
||||||
|
false,
|
||||||
|
window,
|
||||||
|
self.atoms.wm_name,
|
||||||
|
x11rb::protocol::xproto::AtomEnum::STRING,
|
||||||
|
0,
|
||||||
|
256,
|
||||||
|
)
|
||||||
|
.map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
let reply = cookie.reply().map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
let text = String::from_utf8_lossy(&reply.value);
|
||||||
|
Ok((!text.is_empty()).then(|| text.into_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block on the connection, forwarding focus changes until `quit` fires.
|
||||||
|
///
|
||||||
|
/// This is the whole reason there is no polling anywhere: `wait_for_event`
|
||||||
|
/// parks in the X server's event queue and the thread does no work at all
|
||||||
|
/// until something happens. One wakeup per actual window change.
|
||||||
|
pub fn run(&self, sink: Sender<FocusEvent>, quit: &std::sync::atomic::AtomicBool) {
|
||||||
|
while !quit.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
match self.conn.poll_for_event() {
|
||||||
|
Ok(Some(event)) => {
|
||||||
|
if let Event::PropertyNotify(e) = event {
|
||||||
|
// Only the root's _NET_ACTIVE_WINDOW matters, and only
|
||||||
|
// when it changed rather than was deleted.
|
||||||
|
// Only the root's _NET_ACTIVE_WINDOW matters. Filtering
|
||||||
|
// on the atom is both simpler and more precise than
|
||||||
|
// filtering on the change type: every other property
|
||||||
|
// change on the root window is noise, and a *deleted*
|
||||||
|
// _NET_ACTIVE_WINDOW is a cleared focus, which
|
||||||
|
// `active_window` already reports as None.
|
||||||
|
if e.window != self.root || e.atom != self.atoms.net_active_window {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Ok(Some(window)) = self.active_window() {
|
||||||
|
if let Ok(Some(fg)) = self.resolve(window) {
|
||||||
|
if sink.send(FocusEvent::Activated(fg)).is_err() {
|
||||||
|
// The daemon is gone; there is nobody to tell.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if sink.send(FocusEvent::Cleared).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
// wait_for_event returned without an event, which means the
|
||||||
|
// connection is idle. Yield rather than spin.
|
||||||
|
std::thread::yield_now();
|
||||||
|
}
|
||||||
|
Err(_) => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_window(&self) -> Result<Option<x11rb::protocol::xproto::Window>, X11Error> {
|
||||||
|
let cookie = self
|
||||||
|
.conn
|
||||||
|
.get_property(
|
||||||
|
false,
|
||||||
|
self.root,
|
||||||
|
self.atoms.net_active_window,
|
||||||
|
x11rb::protocol::xproto::AtomEnum::WINDOW,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
let reply = cookie.reply().map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
Ok(reply.value32().and_then(|mut values| values.next()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe to root-window property changes.
|
||||||
|
pub fn subscribe_events(&self) -> Result<(), X11Error> {
|
||||||
|
self.conn
|
||||||
|
.change_window_attributes(
|
||||||
|
self.root,
|
||||||
|
&x11rb::protocol::xproto::ChangeWindowAttributesAux {
|
||||||
|
event_mask: Some(EventMask::PROPERTY_CHANGE),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The screen dimensions, for positioning a block window.
|
||||||
|
pub fn screen_size(&self) -> (u16, u16) {
|
||||||
|
let setup = self.conn.setup();
|
||||||
|
let screen = &setup.roots[0];
|
||||||
|
(screen.width_in_pixels, screen.height_in_pixels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for X11Backend {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.conn.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForegroundWatcher for X11Backend {
|
||||||
|
fn subscribe(&mut self, _sink: Sender<FocusEvent>) {
|
||||||
|
// Subscription is a two-step handshake with the window manager and
|
||||||
|
// happens in `serve`. Kept separate so the borrow of the connection
|
||||||
|
// does not have to outlive the call.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"x11"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection_reason(&self) -> &'static str {
|
||||||
|
"no KWin session detected; using the X11 protocol. Native Wayland apps \
|
||||||
|
are NOT visible through this backend"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intern an atom, failing at connect time rather than mid-stream.
|
||||||
|
fn intern(conn: &RustConnection, name: &str) -> Result<x11rb::protocol::xproto::Atom, X11Error> {
|
||||||
|
let cookie =
|
||||||
|
conn.intern_atom(false, name.as_bytes()).map_err(|e| X11Error::Protocol(e.to_string()))?;
|
||||||
|
cookie.reply().map(|r| r.atom).map_err(|e| X11Error::Protocol(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a process's executable path.
|
||||||
|
///
|
||||||
|
/// `/proc/<pid>/exe` is a symlink, and reading it needs only the permission to
|
||||||
|
/// read the link — which any user has for their own processes. Another user's
|
||||||
|
/// process is not readable, which is an OS limit and not a bug.
|
||||||
|
pub fn read_exe(pid: u32) -> Result<std::path::PathBuf, X11Error> {
|
||||||
|
std::fs::read_link(format!("/proc/{pid}/exe")).map_err(|e| X11Error::Proc(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map an executable path to a desktop-file id.
|
||||||
|
///
|
||||||
|
/// Reads the XDG data directories and matches `Exec=` lines. Deliberately
|
||||||
|
/// best-effort: a missing `.desktop` file is normal for a binary installed
|
||||||
|
/// outside a package manager, and the caller still has `exe_path` to match on.
|
||||||
|
/// A wrong mapping would block the wrong app, which is worse than none.
|
||||||
|
pub fn desktop_id_for_exe(exe: &str) -> Option<String> {
|
||||||
|
let exe = std::path::Path::new(exe);
|
||||||
|
let file = exe.file_name()?.to_str()?;
|
||||||
|
|
||||||
|
for dir in xdg_data_dirs() {
|
||||||
|
let applications = dir.join("applications");
|
||||||
|
let Ok(entries) = std::fs::read_dir(&applications) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.extension().and_then(|e| e.to_str()) != Some("desktop") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Ok(contents) = std::fs::read_to_string(&path) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if contents.lines().any(|l| {
|
||||||
|
let l = l.trim();
|
||||||
|
l.starts_with("Exec=") && l.contains(file)
|
||||||
|
}) {
|
||||||
|
return path.file_stem().and_then(|s| s.to_str()).map(str::to_owned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The XDG data directories, in the order the spec defines.
|
||||||
|
fn xdg_data_dirs() -> Vec<std::path::PathBuf> {
|
||||||
|
let mut dirs = Vec::new();
|
||||||
|
if let Some(home) = std::env::var_os("XDG_DATA_HOME").filter(|v| !v.is_empty()) {
|
||||||
|
dirs.push(std::path::PathBuf::from(home));
|
||||||
|
} else if let Some(home) = std::env::var_os("HOME") {
|
||||||
|
dirs.push(std::path::PathBuf::from(home).join(".local/share"));
|
||||||
|
}
|
||||||
|
if let Some(extra) = std::env::var_os("XDG_DATA_DIRS").filter(|v| !v.is_empty()) {
|
||||||
|
dirs.extend(std::env::split_paths(&extra));
|
||||||
|
} else {
|
||||||
|
dirs.push(std::path::PathBuf::from("/usr/local/share"));
|
||||||
|
dirs.push(std::path::PathBuf::from("/usr/share"));
|
||||||
|
}
|
||||||
|
dirs
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user