Compare commits
1 Commits
v0.12.0
...
refactor/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c73585324c |
147
Cargo.lock
generated
147
Cargo.lock
generated
@@ -251,12 +251,6 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
@@ -1518,7 +1512,7 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.12.0"
|
version = "0.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async_once",
|
"async_once",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -1555,7 +1549,6 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
"wayland-protocols-wlr",
|
|
||||||
"zbus 3.11.1",
|
"zbus 3.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1825,6 +1818,18 @@ dependencies = [
|
|||||||
"memoffset 0.6.5",
|
"memoffset 0.6.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.24.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"memoffset 0.6.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
@@ -2211,15 +2216,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quick-xml"
|
|
||||||
version = "0.23.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
@@ -2348,7 +2344,7 @@ version = "0.11.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.0",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -2379,17 +2375,6 @@ dependencies = [
|
|||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ron"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.13.1",
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.22"
|
version = "0.1.22"
|
||||||
@@ -2449,12 +2434,6 @@ dependencies = [
|
|||||||
"windows-sys 0.42.0",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scoped-tls"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -2653,9 +2632,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smithay-client-toolkit"
|
name = "smithay-client-toolkit"
|
||||||
version = "0.17.0"
|
version = "0.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1476c3d89bb67079264b88aaf4f14358353318397e083b7c4e8c14517f55de7"
|
checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"calloop",
|
"calloop",
|
||||||
@@ -2663,14 +2642,11 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"nix 0.26.2",
|
"nix 0.24.3",
|
||||||
"thiserror",
|
"pkg-config",
|
||||||
"wayland-backend",
|
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-cursor",
|
"wayland-cursor",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
"wayland-protocols-wlr",
|
|
||||||
"wayland-scanner",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3210,13 +3186,12 @@ checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-config"
|
name = "universal-config"
|
||||||
version = "0.4.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "923efc70bfb900a20e1c86fa462fad94c336ef212de25589ef9924fb5e6cd2b6"
|
checksum = "5dae262d0546bed6c0955faad12c07ec989768c2550643609410178fddb39909"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 5.0.0",
|
"dirs 5.0.0",
|
||||||
"libcorn",
|
"libcorn",
|
||||||
"ron",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
@@ -3415,88 +3390,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-backend"
|
name = "wayland-client"
|
||||||
version = "0.1.2"
|
version = "0.29.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8"
|
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"bitflags 1.3.2",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"io-lifetimes",
|
"libc",
|
||||||
"nix 0.26.2",
|
"nix 0.24.3",
|
||||||
"scoped-tls",
|
"wayland-commons",
|
||||||
|
"wayland-scanner",
|
||||||
|
"wayland-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-commons"
|
||||||
|
version = "0.29.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
|
||||||
|
dependencies = [
|
||||||
|
"nix 0.24.3",
|
||||||
|
"once_cell",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"wayland-sys",
|
"wayland-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wayland-client"
|
|
||||||
version = "0.30.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85bde68449abab1a808e5227b6e295f4ae3680911eb7711b4a2cb90141edb780"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"calloop",
|
|
||||||
"nix 0.26.2",
|
|
||||||
"wayland-backend",
|
|
||||||
"wayland-scanner",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-cursor"
|
name = "wayland-cursor"
|
||||||
version = "0.30.0"
|
version = "0.29.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d0c3a0d5b4b688b07b0442362d3ed6bf04724fcc16cd69ab6285b90dbc487aa"
|
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix 0.26.2",
|
"nix 0.24.3",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"xcursor",
|
"xcursor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-protocols"
|
name = "wayland-protocols"
|
||||||
version = "0.30.0"
|
version = "0.29.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fefbeb8a360abe67ab7c2efe1d297a1a50ee011f5460791bc18870c26bb84e2"
|
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"wayland-backend",
|
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-scanner",
|
"wayland-commons",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wayland-protocols-wlr"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"wayland-backend",
|
|
||||||
"wayland-client",
|
|
||||||
"wayland-protocols",
|
|
||||||
"wayland-scanner",
|
"wayland-scanner",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-scanner"
|
name = "wayland-scanner"
|
||||||
version = "0.30.0"
|
version = "0.29.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4834c14b3edf1d9986c83ca79b1e7e3afbe9874c7c144702f6467063259ce45d"
|
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quick-xml",
|
|
||||||
"quote 1.0.26",
|
"quote 1.0.26",
|
||||||
|
"xml-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-sys"
|
name = "wayland-sys"
|
||||||
version = "0.30.1"
|
version = "0.29.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06"
|
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dlib",
|
|
||||||
"log",
|
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3724,6 +3683,12 @@ dependencies = [
|
|||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|||||||
22
Cargo.toml
22
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.12.0"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Customisable GTK Layer Shell wlroots/sway bar"
|
description = "Customisable GTK Layer Shell wlroots/sway bar"
|
||||||
@@ -20,12 +20,11 @@ default = [
|
|||||||
http = ["dep:reqwest"]
|
http = ["dep:reqwest"]
|
||||||
upower = ["upower_dbus", "zbus", "futures-lite"]
|
upower = ["upower_dbus", "zbus", "futures-lite"]
|
||||||
|
|
||||||
"config+all" = ["config+json", "config+yaml", "config+toml", "config+corn", "config+ron"]
|
"config+all" = ["config+json", "config+yaml", "config+toml", "config+corn"]
|
||||||
"config+json" = ["universal-config/json"]
|
"config+json" = ["universal-config/json"]
|
||||||
"config+yaml" = ["universal-config/yaml"]
|
"config+yaml" = ["universal-config/yaml"]
|
||||||
"config+toml" = ["universal-config/toml"]
|
"config+toml" = ["universal-config/toml"]
|
||||||
"config+corn" = ["universal-config/corn"]
|
"config+corn" = ["universal-config/corn"]
|
||||||
"config+ron" = ["universal-config/ron"]
|
|
||||||
|
|
||||||
clipboard = ["nix"]
|
clipboard = ["nix"]
|
||||||
|
|
||||||
@@ -47,9 +46,9 @@ workspaces = ["futures-util"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# core
|
# core
|
||||||
gtk = "0.17.0"
|
gtk = { package = "gtk4", version = "0.6.6" }
|
||||||
gtk-layer-shell = "0.6.0"
|
gtk-layer-shell = { package = "gtk4-layer-shell", version = "0.0.3" }
|
||||||
glib = "0.17.5"
|
glib = "0.17.9"
|
||||||
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time", "process", "sync", "io-util", "net"] }
|
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time", "process", "sync", "io-util", "net"] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||||
@@ -62,11 +61,10 @@ indexmap = "1.9.1"
|
|||||||
dirs = "5.0.0"
|
dirs = "5.0.0"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
notify = { version = "5.0.0", default-features = false }
|
notify = { version = "5.0.0", default-features = false }
|
||||||
wayland-client = "0.30.0"
|
wayland-client = "0.29.5"
|
||||||
wayland-protocols = { version = "0.30.0", features = ["unstable", "client"] }
|
wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] }
|
||||||
wayland-protocols-wlr = { version = "0.1.0", features = ["client"] }
|
smithay-client-toolkit = { version = "0.16.0", default-features = false, features = ["calloop"] }
|
||||||
smithay-client-toolkit = { version = "0.17.0", default-features = false, features = ["calloop"] }
|
universal-config = { version = "0.3.0", default_features = false }
|
||||||
universal-config = { version = "0.4.0", default_features = false }
|
|
||||||
|
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
async_once = "0.2.6"
|
async_once = "0.2.6"
|
||||||
@@ -76,7 +74,7 @@ cfg-if = "1.0.0"
|
|||||||
reqwest = { version = "0.11.14", optional = true }
|
reqwest = { version = "0.11.14", optional = true }
|
||||||
|
|
||||||
# clipboard
|
# clipboard
|
||||||
nix = { version = "0.26.2", optional = true, features = ["event"] }
|
nix = { version = "0.26.2", optional = true }
|
||||||
|
|
||||||
# clock
|
# clock
|
||||||
chrono = { version = "0.4.19", optional = true }
|
chrono = { version = "0.4.19", optional = true }
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ install target/release/ironbar ~/.local/bin/ironbar
|
|||||||
## Build requirements
|
## Build requirements
|
||||||
|
|
||||||
To build from source, you must have GTK (>= 3.22) and GTK Layer Shell installed.
|
To build from source, you must have GTK (>= 3.22) and GTK Layer Shell installed.
|
||||||
You also need rust; only the latest stable version is supported.
|
|
||||||
|
|
||||||
### Arch
|
### Arch
|
||||||
|
|
||||||
@@ -23,9 +22,7 @@ pacman -S gtk3 gtk-layer-shell
|
|||||||
### Ubuntu/Debian
|
### Ubuntu/Debian
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
apt install build-essential libgtk-3-dev libgtk-layer-shell-dev
|
apt install libgtk-3-dev libgtk-layer-shell-dev
|
||||||
# for http support
|
|
||||||
apt install libssl-dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fedora
|
### Fedora
|
||||||
@@ -62,8 +59,7 @@ cargo build --release --no-default-features \
|
|||||||
| config+json | Enables configuration support for JSON. |
|
| config+json | Enables configuration support for JSON. |
|
||||||
| config+yaml | Enables configuration support for YAML. |
|
| config+yaml | Enables configuration support for YAML. |
|
||||||
| config+toml | Enables configuration support for TOML. |
|
| config+toml | Enables configuration support for TOML. |
|
||||||
| config+corn | Enables configuration support for [Corn](https://github.com/jakestanger/corn). |
|
| config+corn | Enables configuration support for [Corn](https://github.com/jakestanger.corn). |
|
||||||
| config+ron | Enables configuration support for [Ron](https://github.com/ron-rs/ron). |
|
|
||||||
| **Modules** | |
|
| **Modules** | |
|
||||||
| clipboard | Enables the `clipboard` module. |
|
| clipboard | Enables the `clipboard` module. |
|
||||||
| clock | Enables the `clock` module. |
|
| clock | Enables the `clock` module. |
|
||||||
@@ -72,7 +68,6 @@ cargo build --release --no-default-features \
|
|||||||
| music+mpd | Enables the `music` module with MPD support. |
|
| music+mpd | Enables the `music` module with MPD support. |
|
||||||
| sys_info | Enables the `sys_info` module. |
|
| sys_info | Enables the `sys_info` module. |
|
||||||
| tray | Enables the `tray` module. |
|
| tray | Enables the `tray` module. |
|
||||||
| upower | Enables the `upower` module. |
|
|
||||||
| workspaces+all | Enables the `workspaces` module with support for all compositors. |
|
| workspaces+all | Enables the `workspaces` module with support for all compositors. |
|
||||||
| workspaces+sway | Enables the `workspaces` module with support for Sway. |
|
| workspaces+sway | Enables the `workspaces` module with support for Sway. |
|
||||||
| workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. |
|
| workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. |
|
||||||
|
|||||||
@@ -310,12 +310,9 @@ For information on the `Script` type, and embedding scripts in strings, see [her
|
|||||||
| `transition_type` | `slide_start` or `slide_end` or `crossfade` or `none` | `slide_start` | The transition animation to use when showing/hiding the widget. |
|
| `transition_type` | `slide_start` or `slide_end` or `crossfade` or `none` | `slide_start` | The transition animation to use when showing/hiding the widget. |
|
||||||
| `transition_duration` | `Integer` | `250` | The length of the transition animation to use when showing/hiding the widget. |
|
| `transition_duration` | `Integer` | `250` | The length of the transition animation to use when showing/hiding the widget. |
|
||||||
|
|
||||||
#### Appearance
|
#### Other
|
||||||
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|-----------|--------------------|---------|-----------------------------------------------------------------------------------|
|
|-------------------|--------------------|---------|--------------------------------------------------------------------------------------------------------------------|
|
||||||
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
|
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
|
||||||
| `name` | `string` | `null` | Sets the unique widget name, allowing you to style it using `#name`. |
|
|
||||||
| `class` | `string` | `null` | Sets one or more CSS classes, allowing you to style it using `.class`. |
|
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -4,37 +4,17 @@ To style the bar, create a file at `~/.config/ironbar/style.css`.
|
|||||||
|
|
||||||
Style changes are hot-loaded so there is no need to reload the bar.
|
Style changes are hot-loaded so there is no need to reload the bar.
|
||||||
|
|
||||||
Since the bar is GTK-based, it uses [GTK's implementation of CSS](https://docs.gtk.org/gtk3/css-overview.html),
|
A reminder: since the bar is GTK-based, it uses GTK's implementation of CSS,
|
||||||
which only includes a subset of the full web spec (plus a few non-standard properties).
|
which only includes a subset of the full web spec (plus a few non-standard properties).
|
||||||
|
|
||||||
The below table describes the selectors provided by the bar itself.
|
The below table describes the selectors provided by the bar itself.
|
||||||
Information on styling individual modules can be found on their pages in the sidebar.
|
Information on styling individual modules can be found on their pages in the sidebar.
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|----------------|--------------------------------------------|
|
|----------------|-------------------------------------------|
|
||||||
| `.background` | Top-level window. |
|
| `.background` | Top-level window |
|
||||||
| `#bar` | Bar root box. |
|
| `#bar` | Bar root box |
|
||||||
| `#bar #start` | Bar left or top modules container box. |
|
| `#bar #start` | Bar left or top modules container box |
|
||||||
| `#bar #center` | Bar center modules container box. |
|
| `#bar #center` | Bar center modules container box |
|
||||||
| `#bar #end` | Bar right or bottom modules container box. |
|
| `#bar #end` | Bar right or bottom modules container box |
|
||||||
| `.container` | All of the above. |
|
| `.container` | All of the above |
|
||||||
| `.popup` | Any popup box. |
|
|
||||||
|
|
||||||
Every widget can be selected using a `kebab-case` class name matching its name.
|
|
||||||
You can also target popups by prefixing `popup-` to the name. For example, you can use `.clock` and `.popup-clock` respectively.
|
|
||||||
|
|
||||||
Setting the `name` option on a widget allows you to target that specific instance using `#name`.
|
|
||||||
You can also add additional classes to re-use styles. In both cases, `popup-` is automatically prefixed to the popup (`#popup-name` or `.popup-my-class`).
|
|
||||||
|
|
||||||
You can also target all GTK widgets of a certain type directly using their name. For example, `button:hover` will select the hover state on *all* buttons.
|
|
||||||
These names are all lower case with no separator, so `MenuBar` -> `menubar`.
|
|
||||||
|
|
||||||
GTK CSS does not support custom properties, but it does have its own custom `@define-color` syntax which you can use for re-using colours:
|
|
||||||
|
|
||||||
```css
|
|
||||||
@define-color color_bg #2d2d2d;
|
|
||||||
|
|
||||||
box, menubar {
|
|
||||||
background-color: @color_bg;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -27,5 +27,4 @@
|
|||||||
- [Script](script)
|
- [Script](script)
|
||||||
- [Sys_Info](sys-info)
|
- [Sys_Info](sys-info)
|
||||||
- [Tray](tray)
|
- [Tray](tray)
|
||||||
- [Upower](upower)
|
|
||||||
- [Workspaces](workspaces)
|
- [Workspaces](workspaces)
|
||||||
@@ -84,13 +84,11 @@ end:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|--------------------------------------|------------------------------------------------------|
|
|--------------------------------------|------------------------------------------------------|
|
||||||
| `.clipboard` | Clipboard widget. |
|
| `#clipboard` | Clipboard widget. |
|
||||||
| `.clipboard .btn` | Clipboard widget button. |
|
| `#clipboard .btn` | Clipboard widget button. |
|
||||||
| `.popup-clipboard` | Clipboard popup box. |
|
| `#popup-clipboard` | Clipboard popup box. |
|
||||||
| `.popup-clipboard .item` | Clipboard row item inside the popup. |
|
| `#popup-clipboard .item` | Clipboard row item inside the popup. |
|
||||||
| `.popup-clipboard .item .btn` | Clipboard row item radio button. |
|
| `#popup-clipboard .item .btn` | Clipboard row item radio button. |
|
||||||
| `.popup-clipboard .item .btn.text` | Clipboard row item radio button (text values only). |
|
| `#popup-clipboard .item .btn.text` | Clipboard row item radio button (text values only). |
|
||||||
| `.popup-clipboard .item .btn.image` | Clipboard row item radio button (image values only). |
|
| `#popup-clipboard .item .btn.image` | Clipboard row item radio button (image values only). |
|
||||||
| `.popup-clipboard .item .btn-remove` | Clipboard row item remove button. |
|
| `#popup-clipboard .item .btn-remove` | Clipboard row item remove button. |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -71,9 +71,7 @@ end:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|--------------------------------|------------------------------------------------------------------------------------|
|
|--------------------------------|------------------------------------------------------------------------------------|
|
||||||
| `.clock` | Clock widget button |
|
| `#clock` | Clock widget button |
|
||||||
| `.popup-clock` | Clock popup box |
|
| `#popup-clock` | Clock popup box |
|
||||||
| `.popup-clock .calendar-clock` | Clock inside the popup |
|
| `#popup-clock #calendar-clock` | Clock inside the popup |
|
||||||
| `.popup-clock .calendar` | Calendar widget inside the popup. GTK provides some OOTB styling options for this. |
|
| `#popup-clock #calendar` | Calendar widget inside the popup. GTK provides some OOTB styling options for this. |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -392,13 +392,10 @@ let {
|
|||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
Since the widgets are all custom, you can use their `name` and `class` attributes, then target them using `#name` and `.class`.
|
Since the widgets are all custom, you can use the `name` and `class` attributes, then target them using `#name` and `.class`.
|
||||||
|
|
||||||
The following top-level selectors are always available:
|
The following top-level selector is always available:
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|-----------------|--------------------------------|
|
|-----------|-------------------------|
|
||||||
| `.custom` | Custom widget container. |
|
| `#custom` | Custom widget container |
|
||||||
| `.popup-custom` | Custom widget popup container. |
|
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -87,9 +87,7 @@ end:
|
|||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|-------------------|--------------------|
|
|--------------------------|--------------------|
|
||||||
| `.focused` | Focused widget box |
|
| `#focused` | Focused widget box |
|
||||||
| `.focused .icon` | App icon |
|
| `#focused #icon` | App icon |
|
||||||
| `.focused .label` | App name |
|
| `#focused #label` | App name |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -66,7 +66,5 @@ end:
|
|||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|----------|------------------------------------------------------------------------------------|
|
|--------------------------------|------------------------------------------------------------------------------------|
|
||||||
| `.label` | Label widget |
|
| `#label` | Label widget |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -90,12 +90,10 @@ start:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|-------------------------------|--------------------------|
|
|-------------------------------|--------------------------|
|
||||||
| `.launcher` | Launcher widget box |
|
| `#launcher` | Launcher widget box |
|
||||||
| `.launcher .item` | App button |
|
| `#launcher .item` | App button |
|
||||||
| `.launcher .item.open` | App button (open app) |
|
| `#launcher .item.open` | App button (open app) |
|
||||||
| `.launcher .item.focused` | App button (focused app) |
|
| `#launcher .item.focused` | App button (focused app) |
|
||||||
| `.launcher .item.urgent` | App button (urgent app) |
|
| `#launcher .item.urgent` | App button (urgent app) |
|
||||||
| `.popup-launcher` | Popup container |
|
| `#launcher-popup` | Popup container |
|
||||||
| `.popup-launcher .popup-item` | Window button in popup |
|
| `#launcher-popup .popup-item` | Window button in popup |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
|
|||||||
@@ -135,26 +135,23 @@ and will be replaced with values from the currently playing track:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|-------------------------------------|------------------------------------------|
|
|-------------------------------------|------------------------------------------|
|
||||||
| `.music` | Tray widget button |
|
| `#music` | Tray widget button |
|
||||||
| `.music .contents` | Tray widget button contents box |
|
| `#popup-music` | Popup box |
|
||||||
| `.popup-music` | Popup box |
|
| `#popup-music #album-art` | Album art image inside popup box |
|
||||||
| `.popup-music .album-art` | Album art image inside popup box |
|
| `#popup-music #title` | Track title container inside popup box |
|
||||||
| `.popup-music .title` | Track title container inside popup box |
|
| `#popup-music #title .icon` | Track title icon label inside popup box |
|
||||||
| `.popup-music .title .icon` | Track title icon label inside popup box |
|
| `#popup-music #title .label` | Track title label inside popup box |
|
||||||
| `.popup-music .title .label` | Track title label inside popup box |
|
| `#popup-music #album` | Track album container inside popup box |
|
||||||
| `.popup-music .album` | Track album container inside popup box |
|
| `#popup-music #album .icon` | Track album icon label inside popup box |
|
||||||
| `.popup-music .album .icon` | Track album icon label inside popup box |
|
| `#popup-music #album .label` | Track album label inside popup box |
|
||||||
| `.popup-music .album .label` | Track album label inside popup box |
|
| `#popup-music #artist` | Track artist container inside popup box |
|
||||||
| `.popup-music .artist` | Track artist container inside popup box |
|
| `#popup-music #artist .icon` | Track artist icon label inside popup box |
|
||||||
| `.popup-music .artist .icon` | Track artist icon label inside popup box |
|
| `#popup-music #artist .label` | Track artist label inside popup box |
|
||||||
| `.popup-music .artist .label` | Track artist label inside popup box |
|
| `#popup-music #controls` | Controls container inside popup box |
|
||||||
| `.popup-music .controls` | Controls container inside popup box |
|
| `#popup-music #controls #btn-prev` | Previous button inside popup box |
|
||||||
| `.popup-music .controls .btn-prev` | Previous button inside popup box |
|
| `#popup-music #controls #btn-play` | Play button inside popup box |
|
||||||
| `.popup-music .controls .btn-play` | Play button inside popup box |
|
| `#popup-music #controls #btn-pause` | Pause button inside popup box |
|
||||||
| `.popup-music .controls .btn-pause` | Pause button inside popup box |
|
| `#popup-music #controls #btn-next` | Next button inside popup box |
|
||||||
| `.popup-music .controls .btn-next` | Next button inside popup box |
|
| `#popup-music #volume` | Volume container inside popup box |
|
||||||
| `.popup-music .volume` | Volume container inside popup box |
|
| `#popup-music #volume #slider` | Volume slider popup box |
|
||||||
| `.popup-music .volume .slider` | Volume slider popup box |
|
| `#popup-music #volume .icon` | Volume icon label inside popup box |
|
||||||
| `.popup-music .volume .icon` | Volume icon label inside popup box |
|
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -83,7 +83,5 @@ end:
|
|||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|-----------|---------------------|
|
|---------------|---------------------|
|
||||||
| `.script` | Script widget label |
|
| `#script` | Script widget label |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -172,7 +172,5 @@ The following tokens can be used in the `format` configuration option:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|------------------|------------------------------|
|
|------------------|------------------------------|
|
||||||
| `.sysinfo` | Sysinfo widget box |
|
| `#sysinfo` | Sysinfo widget box |
|
||||||
| `.sysinfo .item` | Individual information label |
|
| `#sysinfo #item` | Individual information label |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -60,7 +60,5 @@ end:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|---------------|------------------|
|
|---------------|------------------|
|
||||||
| `.tray` | Tray widget box |
|
| `#tray` | Tray widget box |
|
||||||
| `.tray .item` | Tray icon button |
|
| `#tray .item` | Tray icon button |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
|
|||||||
@@ -72,11 +72,9 @@ end:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|---------------------------------|-----------------------------|
|
|---------------------------------|-----------------------------|
|
||||||
| `.upower` | Upower widget container. |
|
| `#upower` | Upower widget container. |
|
||||||
| `.upower .icon` | Upower widget battery icon. |
|
| `#upower #icon` | Upower widget battery icon. |
|
||||||
| `.upower .button` | Upower widget button. |
|
| `#upower #button` | Upower widget button. |
|
||||||
| `.upower .button .label` | Upower widget button label. |
|
| `#upower #button #label` | Upower widget button label. |
|
||||||
| `.popup-upower` | Upower popup box. |
|
| `#popup-upower` | Clock popup box. |
|
||||||
| `.popup-upower .upower-details` | Label inside the popup. |
|
| `#popup-upower #upower-details` | Label inside the popup. |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ end:
|
|||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|-----------------------------|--------------------------------------|
|
|-----------------------------|--------------------------------------|
|
||||||
| `.workspaces` | Workspaces widget box |
|
| `#workspaces` | Workspaces widget box |
|
||||||
| `.workspaces .item` | Workspace button |
|
| `#workspaces .item` | Workspace button |
|
||||||
| `.workspaces .item.focused` | Workspace button (workspace focused) |
|
| `#workspaces .item.focused` | Workspace button (workspace focused) |
|
||||||
|
|
||||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
||||||
@@ -1,194 +1,240 @@
|
|||||||
@define-color color_bg #2d2d2d;
|
|
||||||
@define-color color_bg_dark #1c1c1c;
|
|
||||||
@define-color color_border #424242;
|
|
||||||
@define-color color_border_active #6699cc;
|
|
||||||
@define-color color_text #ffffff;
|
|
||||||
@define-color color_urgent #8f0a0a;
|
|
||||||
|
|
||||||
/* -- base styles -- */
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
/* `otf-font-awesome` is required to be installed for icons */
|
||||||
font-family: Noto Sans Nerd Font, sans-serif;
|
font-family: Noto Sans Nerd Font, sans-serif;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
box, menubar, button {
|
|
||||||
background-color: @color_bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
button, label {
|
|
||||||
color: @color_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: @color_bg_dark;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#bar {
|
#bar {
|
||||||
border-top: 1px solid @color_border;
|
border-top: 1px solid #424242;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup {
|
.background, .container {
|
||||||
border: 1px solid @color_border;
|
background-color: #2d2d2d;
|
||||||
padding: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#workspaces .item {
|
||||||
/* -- clipboard -- */
|
color: white;
|
||||||
|
background-color: #2d2d2d;
|
||||||
.clipboard {
|
border-radius: 0;
|
||||||
margin-left: 5px;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-clipboard .item {
|
#workspaces .item.focused {
|
||||||
padding-bottom: 0.3em;
|
box-shadow: inset 0 -3px;
|
||||||
border-bottom: 1px solid @color_border;
|
background-color: #1c1c1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#workspaces *:not(.focused):hover {
|
||||||
|
box-shadow: inset 0 -3px;
|
||||||
|
}
|
||||||
|
|
||||||
/* -- clock -- */
|
#launcher .item {
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.clock {
|
#launcher .item:not(.focused):hover {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .open {
|
||||||
|
border-bottom: 2px solid #6699cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .focused {
|
||||||
|
color: white;
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
border-bottom: 4px solid #6699cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .urgent {
|
||||||
|
color: white;
|
||||||
|
background-color: #8f0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-launcher .popup-item {
|
||||||
|
color: white;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-launcher .popup-item:hover {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-launcher .popup-item:not(:first-child) {
|
||||||
|
border-top: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clock {
|
||||||
|
color: white;
|
||||||
|
background-color: #2d2d2d;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-clock .calendar-clock {
|
#clock:hover {
|
||||||
color: @color_text;
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#script {
|
||||||
|
padding-left: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sysinfo {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sysinfo #item {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tray {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tray .item {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#music {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border: 1px solid #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-clock {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar-clock {
|
||||||
|
color: white;
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
padding-bottom: 0.1em;
|
padding-bottom: 0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-clock .calendar {
|
#calendar {
|
||||||
background-color: @color_bg;
|
background-color: #2d2d2d;
|
||||||
color: @color_text;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-clock .calendar .header {
|
#calendar .header {
|
||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
border-top: 1px solid @color_border;
|
border-top: 1px solid #424242;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-clock .calendar:selected {
|
#calendar:selected {
|
||||||
background-color: @color_border_active;
|
background-color: #6699cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#music:hover {
|
||||||
/* -- launcher -- */
|
background-color: #1c1c1c;
|
||||||
|
|
||||||
.launcher .item {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcher .item:not(.focused):hover {
|
#popup-music {
|
||||||
background-color: @color_bg_dark;
|
color: white;
|
||||||
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launcher .open {
|
#popup-music #album-art {
|
||||||
border-bottom: 1px solid @color_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.launcher .focused {
|
|
||||||
border-bottom: 2px solid @color_border_active;
|
|
||||||
}
|
|
||||||
|
|
||||||
.launcher .urgent {
|
|
||||||
border-bottom-color: @color_urgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-launcher {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-launcher .popup-item:not(:first-child) {
|
|
||||||
border-top: 1px solid @color_border;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -- music -- */
|
|
||||||
|
|
||||||
.music:hover * {
|
|
||||||
background-color: @color_bg_dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-music .album-art {
|
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-music .title .icon *, .popup-music .title .label {
|
#popup-music #title .icon *, #popup-music #title .label {
|
||||||
font-size: 1.7em;
|
font-size: 1.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-music .controls *:disabled {
|
#popup-music #controls * {
|
||||||
color: @color_border;
|
border-radius: 0;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-music .volume scale slider {
|
#popup-music #controls *:disabled {
|
||||||
border-radius: 100%;
|
color: #424242;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* volume icon */
|
#popup-music #volume > box:last-child label {
|
||||||
.popup-music .volume > box:last-child label {
|
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#focused {
|
||||||
/* -- script -- */
|
color: white;
|
||||||
|
|
||||||
.script {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.power-menu {
|
||||||
/* -- sys_info -- */
|
|
||||||
|
|
||||||
.sysinfo {
|
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sysinfo .item {
|
.power-menu #power-btn {
|
||||||
margin-left: 5px;
|
color: white;
|
||||||
|
background-color: #2d2d2d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.power-menu #power-btn:hover {
|
||||||
/* -- tray -- */
|
background-color: #1c1c1c;
|
||||||
|
|
||||||
.tray {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup-power-menu {
|
||||||
/* -- workspaces -- */
|
padding: 1em;
|
||||||
|
|
||||||
.workspaces .item.focused {
|
|
||||||
box-shadow: inset 0 -3px;
|
|
||||||
background-color: @color_bg_dark;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspaces .item:hover {
|
|
||||||
box-shadow: inset 0 -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -- custom: power menu -- */
|
|
||||||
|
|
||||||
.popup-power-menu #header {
|
.popup-power-menu #header {
|
||||||
|
color: white;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
|
border-bottom: 1px solid white;
|
||||||
padding-bottom: 0.4em;
|
padding-bottom: 0.4em;
|
||||||
margin-bottom: 0.6em;
|
margin-bottom: 0.8em;
|
||||||
border-bottom: 1px solid @color_border;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-power-menu .power-btn {
|
.popup-power-menu .power-btn {
|
||||||
border: 1px solid @color_border;
|
color: white;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border: 1px solid white;
|
||||||
padding: 0.6em 1em;
|
padding: 0.6em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-power-menu #buttons > *:nth-child(1) .power-btn {
|
.popup-power-menu .power-btn + .power-btn {
|
||||||
margin-right: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup-power-menu .power-btn:hover {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard * {
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-clipboard {
|
||||||
|
padding: 1em;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-clipboard .item {
|
||||||
|
border-bottom: 1px solid #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-clipboard .btn > *:nth-child(2) {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-clipboard .btn-remove {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: white;
|
||||||
|
font-size: 1.2em;
|
||||||
|
border-left: 1px solid #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-clipboard .btn-remove:hover {
|
||||||
|
color: #fcc;
|
||||||
|
}
|
||||||
36
flake.lock
generated
36
flake.lock
generated
@@ -1,15 +1,12 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681202837,
|
"lastModified": 1659877975,
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -20,11 +17,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1682786779,
|
"lastModified": 1680213900,
|
||||||
"narHash": "sha256-m7QFzPS/CE8hbkbIVK4UStihAQMtczr0vSpOgETOM1g=",
|
"narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "08e4dc3a907a6dfec8bb3bbf1540d8abbffea22b",
|
"rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -48,11 +45,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1682821181,
|
"lastModified": 1680229280,
|
||||||
"narHash": "sha256-7MYRqO9Ge46sULbQwJbcH/IMDNBdxCGUO9w7bEOc3CI=",
|
"narHash": "sha256-9UoyQCeKUmHcsIdpsAgcz41LAIDkWhI2PhVDjckrpg0=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "1be440e9119e69b68151cd9c84876ff3063a2e45",
|
"rev": "aa480d799023141e1b9e5d6108700de63d9ad002",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -60,21 +57,6 @@
|
|||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Migrates CSS selectors from widget names to CSS classes.
|
|
||||||
# These changed as part of the 0.12 release.
|
|
||||||
|
|
||||||
# ⚠ This script will **NOT** check for custom styles and may mangle them!
|
|
||||||
# ⚠ It is *highly recommended* that you back up your existing styles before running this!
|
|
||||||
|
|
||||||
style_path="$HOME/.config/ironbar/style.css"
|
|
||||||
|
|
||||||
# general
|
|
||||||
sed -i 's/#icon/.icon/g' "$style_path"
|
|
||||||
sed -i 's/#label/.label/g' "$style_path"
|
|
||||||
sed -i 's/#image/.image/g' "$style_path"
|
|
||||||
|
|
||||||
# clipboard
|
|
||||||
sed -i 's/#clipboard/.clipboard/g' "$style_path"
|
|
||||||
sed -i 's/#popup-clipboard/.popup-clipboard/g' "$style_path"
|
|
||||||
|
|
||||||
# clock
|
|
||||||
sed -i 's/#clock/.clock/g' "$style_path"
|
|
||||||
sed -i 's/#popup-clock/.popup-clock/g' "$style_path"
|
|
||||||
sed -i 's/#calendar-clock/.calendar-clock/g' "$style_path"
|
|
||||||
sed -i 's/#calendar/.calendar/g' "$style_path"
|
|
||||||
|
|
||||||
# custom
|
|
||||||
sed -i 's/#custom/.custom/g' "$style_path"
|
|
||||||
sed -i 's/#popup-custom/.popup-custom/g' "$style_path"
|
|
||||||
|
|
||||||
# focused
|
|
||||||
sed -i 's/#focused/.focused/g' "$style_path"
|
|
||||||
|
|
||||||
# launcher
|
|
||||||
sed -i 's/#launcher/.launcher/g' "$style_path"
|
|
||||||
sed -i 's/#popup-launcher/.popup-launcher/g' "$style_path"
|
|
||||||
sed -i 's/#launcher-popup/.popup-launcher/g' "$style_path" # was incorrect in docs
|
|
||||||
|
|
||||||
# music
|
|
||||||
sed -i 's/#music/.music/g' "$style_path"
|
|
||||||
sed -i 's/#contents/.contents/g' "$style_path"
|
|
||||||
sed -i 's/#popup-music/.popup-music/g' "$style_path"
|
|
||||||
sed -i 's/#album-art/.album-art/g' "$style_path"
|
|
||||||
sed -i 's/#title/.title/g' "$style_path"
|
|
||||||
sed -i 's/#album/.album/g' "$style_path"
|
|
||||||
sed -i 's/#artist/.artist/g' "$style_path"
|
|
||||||
sed -i 's/#controls/.controls/g' "$style_path"
|
|
||||||
sed -i 's/#btn-prev/.btn-prev/g' "$style_path"
|
|
||||||
sed -i 's/#btn-play/.btn-play/g' "$style_path"
|
|
||||||
sed -i 's/#btn-pause/.btn-pause/g' "$style_path"
|
|
||||||
sed -i 's/#btn-next/.btn-next/g' "$style_path"
|
|
||||||
sed -i 's/#volume/.volume/g' "$style_path"
|
|
||||||
sed -i 's/#slider/.slider/g' "$style_path"
|
|
||||||
|
|
||||||
# script
|
|
||||||
sed -i 's/#script/.script/g' "$style_path"
|
|
||||||
|
|
||||||
# sys_info
|
|
||||||
sed -i 's/#sysinfo/.sysinfo/g' "$style_path"
|
|
||||||
sed -i 's/#item/.item/g' "$style_path"
|
|
||||||
|
|
||||||
# tray
|
|
||||||
sed -i 's/#tray/.tray/g' "$style_path"
|
|
||||||
|
|
||||||
# upower
|
|
||||||
sed -i 's/#upower/.upower/g' "$style_path"
|
|
||||||
sed -i 's/#button/.button/g' "$style_path"
|
|
||||||
sed -i 's/#popup-upower/.popup-upower/g' "$style_path"
|
|
||||||
sed -i 's/#upower-details/.upower-details/g' "$style_path"
|
|
||||||
|
|
||||||
# workspaces
|
|
||||||
sed -i 's/#workspaces/.workspaces/g' "$style_path"
|
|
||||||
sed -i 's/#item/.item/g' "$style_path"
|
|
||||||
19
src/bar.rs
19
src/bar.rs
@@ -1,7 +1,5 @@
|
|||||||
use crate::config::{BarPosition, MarginConfig, ModuleConfig};
|
use crate::config::{BarPosition, MarginConfig, ModuleConfig};
|
||||||
use crate::modules::{
|
use crate::modules::{create_module, wrap_widget, ModuleInfo, ModuleLocation};
|
||||||
create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation,
|
|
||||||
};
|
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -9,6 +7,7 @@ use gtk::gdk::Monitor;
|
|||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, IconTheme, Orientation};
|
use gtk::{Application, ApplicationWindow, IconTheme, Orientation};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
use glib::signal::Inhibit;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
/// Creates a new window for a bar,
|
/// Creates a new window for a bar,
|
||||||
@@ -50,16 +49,16 @@ pub fn create_bar(
|
|||||||
let center = create_container("center", orientation);
|
let center = create_container("center", orientation);
|
||||||
let end = create_container("end", orientation);
|
let end = create_container("end", orientation);
|
||||||
|
|
||||||
content.add(&start);
|
content.append(&start);
|
||||||
content.set_center_widget(Some(¢er));
|
content.set_center_widget(Some(¢er));
|
||||||
content.pack_end(&end, false, false, 0);
|
content.pack_end(&end, false, false, 0);
|
||||||
|
|
||||||
load_modules(&start, ¢er, &end, app, config, monitor, monitor_name)?;
|
load_modules(&start, ¢er, &end, app, config, monitor, monitor_name)?;
|
||||||
win.add(&content);
|
win.append(&content);
|
||||||
|
|
||||||
win.connect_destroy_event(|_, _| {
|
win.connect_destroy_event(|_, _| {
|
||||||
info!("Shutting down");
|
info!("Shutting down");
|
||||||
gtk::main_quit();
|
// gtk::main_quit();
|
||||||
Inhibit(false)
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,11 +196,9 @@ fn add_modules(
|
|||||||
macro_rules! add_module {
|
macro_rules! add_module {
|
||||||
($module:expr, $id:expr) => {{
|
($module:expr, $id:expr) => {{
|
||||||
let common = $module.common.take().expect("Common config did not exist");
|
let common = $module.common.take().expect("Common config did not exist");
|
||||||
let widget_parts = create_module(*$module, $id, &info, &Arc::clone(&popup))?;
|
let widget = create_module(*$module, $id, &info, &Arc::clone(&popup))?;
|
||||||
set_widget_identifiers(&widget_parts, &common);
|
let container = wrap_widget(&widget, common, orientation);
|
||||||
|
content.append(&container);
|
||||||
let container = wrap_widget(&widget_parts.widget, common, orientation);
|
|
||||||
content.add(&container);
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use lazy_static::lazy_static;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, trace};
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClipboardEvent {
|
pub enum ClipboardEvent {
|
||||||
@@ -26,8 +26,6 @@ pub struct ClipboardClient {
|
|||||||
|
|
||||||
impl ClipboardClient {
|
impl ClipboardClient {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
trace!("Initializing clipboard client");
|
|
||||||
|
|
||||||
let senders = Arc::new(Mutex::new(Vec::<(EventSender, usize)>::new()));
|
let senders = Arc::new(Mutex::new(Vec::<(EventSender, usize)>::new()));
|
||||||
|
|
||||||
let cache = Arc::new(Mutex::new(ClipboardCache::new()));
|
let cache = Arc::new(Mutex::new(ClipboardCache::new()));
|
||||||
@@ -37,21 +35,11 @@ impl ClipboardClient {
|
|||||||
let cache = cache.clone();
|
let cache = cache.clone();
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let (mut rx, item) = {
|
let mut rx = {
|
||||||
let wl = wayland::get_client().await;
|
let wl = wayland::get_client().await;
|
||||||
wl.subscribe_clipboard()
|
wl.subscribe_clipboard()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(item) = item {
|
|
||||||
let senders = lock!(senders);
|
|
||||||
let iter = senders.iter();
|
|
||||||
for (tx, _) in iter {
|
|
||||||
try_send!(tx, ClipboardEvent::Add(item.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
lock!(cache).insert(item, senders.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Ok(item) = rx.recv().await {
|
while let Ok(item) = rx.recv().await {
|
||||||
debug!("Received clipboard item (ID: {})", item.id);
|
debug!("Received clipboard item (ID: {})", item.id);
|
||||||
|
|
||||||
@@ -71,6 +59,7 @@ impl ClipboardClient {
|
|||||||
let iter = senders.iter();
|
let iter = senders.iter();
|
||||||
for (tx, sender_cache_size) in iter {
|
for (tx, sender_cache_size) in iter {
|
||||||
if cache_size == *sender_cache_size {
|
if cache_size == *sender_cache_size {
|
||||||
|
// let mut cache = lock!(cache);
|
||||||
let removed_id = lock!(cache)
|
let removed_id = lock!(cache)
|
||||||
.remove_ref_first()
|
.remove_ref_first()
|
||||||
.expect("Clipboard cache unexpectedly empty");
|
.expect("Clipboard cache unexpectedly empty");
|
||||||
@@ -94,11 +83,18 @@ impl ClipboardClient {
|
|||||||
Self { senders, cache }
|
Self { senders, cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe(&self, cache_size: usize) -> mpsc::Receiver<ClipboardEvent> {
|
pub async fn subscribe(&self, cache_size: usize) -> mpsc::Receiver<ClipboardEvent> {
|
||||||
let (tx, rx) = mpsc::channel(16);
|
let (tx, rx) = mpsc::channel(16);
|
||||||
|
|
||||||
|
let wl = wayland::get_client().await;
|
||||||
|
wl.roundtrip();
|
||||||
|
|
||||||
{
|
{
|
||||||
let cache = lock!(self.cache);
|
let mut cache = lock!(self.cache);
|
||||||
|
|
||||||
|
if let Some(item) = wl.get_clipboard() {
|
||||||
|
cache.insert_or_inc_ref(item);
|
||||||
|
}
|
||||||
|
|
||||||
let iter = cache.iter();
|
let iter = cache.iter();
|
||||||
for (_, (item, _)) in iter {
|
for (_, (item, _)) in iter {
|
||||||
@@ -106,7 +102,10 @@ impl ClipboardClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lock!(self.senders).push((tx, cache_size));
|
{
|
||||||
|
let mut senders = lock!(self.senders);
|
||||||
|
senders.push((tx, cache_size));
|
||||||
|
}
|
||||||
|
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
@@ -172,6 +171,13 @@ impl ClipboardCache {
|
|||||||
.map(|(item, _)| item)
|
.map(|(item, _)| item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts an entry with `ref_count` initial references,
|
||||||
|
/// or increments the `ref_count` by 1 if it already exists.
|
||||||
|
fn insert_or_inc_ref(&mut self, item: Arc<ClipboardItem>) {
|
||||||
|
let mut item = self.cache.entry(item.id).or_insert((item, 0));
|
||||||
|
item.1 += 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes the entry with key `id`.
|
/// Removes the entry with key `id`.
|
||||||
/// This ignores references.
|
/// This ignores references.
|
||||||
fn remove(&mut self, id: usize) -> Option<Arc<ClipboardItem>> {
|
fn remove(&mut self, id: usize) -> Option<Arc<ClipboardItem>> {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use color_eyre::Result;
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::string;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -260,7 +259,7 @@ impl From<Metadata> for Track {
|
|||||||
.and_then(mpris::MetadataValue::as_str_array)
|
.and_then(mpris::MetadataValue::as_str_array)
|
||||||
.and_then(|arr| arr.first().map(|val| (*val).to_string())),
|
.and_then(|arr| arr.first().map(|val| (*val).to_string())),
|
||||||
track: value.track_number().map(|track| track as u64),
|
track: value.track_number().map(|track| track as u64),
|
||||||
cover_path: value.art_url().map(string::ToString::to_string),
|
cover_path: value.art_url().map(|s| s.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +1,79 @@
|
|||||||
use super::wlr_foreign_toplevel::handle::ToplevelHandle;
|
use super::wlr_foreign_toplevel::{
|
||||||
use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
handle::{ToplevelEvent, ToplevelInfo},
|
||||||
use super::wlr_foreign_toplevel::ToplevelEvent;
|
manager::listen_for_toplevels,
|
||||||
use super::Environment;
|
};
|
||||||
use crate::error::ERR_CHANNEL_RECV;
|
use super::{DData, Env, ToplevelHandler};
|
||||||
use crate::send;
|
use crate::{error as err, send};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
use indexmap::IndexMap;
|
||||||
|
use smithay_client_toolkit::environment::Environment;
|
||||||
|
use smithay_client_toolkit::output::{with_output_info, OutputInfo};
|
||||||
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
||||||
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
||||||
use smithay_client_toolkit::registry::RegistryState;
|
use smithay_client_toolkit::WaylandSource;
|
||||||
use smithay_client_toolkit::seat::SeatState;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::mpsc;
|
use std::sync::{Arc, RwLock};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::{broadcast, oneshot};
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error};
|
||||||
use wayland_client::globals::registry_queue_init;
|
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_client::{Connection, WaylandSource};
|
use wayland_client::{ConnectError, Display, EventQueue};
|
||||||
|
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
|
||||||
|
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||||
|
zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
|
||||||
|
};
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
use super::ClipboardItem;
|
use super::{ClipboardItem};
|
||||||
use super::wlr_data_control::manager::DataControlDeviceManagerState;
|
use super::wlr_data_control::manager::{listen_to_devices, DataControlDeviceHandler};
|
||||||
use crate::lock;
|
use crate::{read_lock, write_lock};
|
||||||
use std::sync::{Arc, Mutex};
|
use tokio::spawn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Request {
|
pub enum Request {
|
||||||
/// Sends a request for all the outputs.
|
|
||||||
/// These are then sent on the `output` channel.
|
|
||||||
Outputs,
|
|
||||||
/// Sends a request for all the seats.
|
|
||||||
/// These are then sent ont the `seat` channel.
|
|
||||||
Seats,
|
|
||||||
/// Sends a request for all the toplevels.
|
|
||||||
/// These are then sent on the `toplevel_init` channel.
|
|
||||||
Toplevels,
|
|
||||||
/// Sends a request for the current clipboard item.
|
|
||||||
/// This is then sent on the `clipboard_init` channel.
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
Clipboard,
|
|
||||||
/// Copies the value to the clipboard
|
/// Copies the value to the clipboard
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
CopyToClipboard(Arc<ClipboardItem>),
|
CopyToClipboard(Arc<ClipboardItem>),
|
||||||
/// Forces a dispatch, flushing any currently queued events
|
/// Forces a dispatch, flushing any currently queued events
|
||||||
Roundtrip,
|
Refresh,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WaylandClient {
|
pub struct WaylandClient {
|
||||||
// External channels
|
pub outputs: Vec<OutputInfo>,
|
||||||
|
pub seats: Vec<WlSeat>,
|
||||||
|
|
||||||
|
pub toplevels: Arc<RwLock<IndexMap<usize, (ToplevelInfo, ZwlrForeignToplevelHandleV1)>>>,
|
||||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||||
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
_clipboard_rx: broadcast::Receiver<Arc<ClipboardItem>>,
|
clipboard: Arc<RwLock<Option<Arc<ClipboardItem>>>>,
|
||||||
|
|
||||||
// Internal channels
|
|
||||||
toplevel_init_rx: mpsc::Receiver<HashMap<usize, ToplevelHandle>>,
|
|
||||||
output_rx: mpsc::Receiver<Vec<OutputInfo>>,
|
|
||||||
seat_rx: mpsc::Receiver<Vec<WlSeat>>,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
clipboard_init_rx: mpsc::Receiver<Option<Arc<ClipboardItem>>>,
|
|
||||||
|
|
||||||
request_tx: Sender<Request>,
|
request_tx: Sender<Request>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WaylandClient {
|
impl WaylandClient {
|
||||||
pub(super) async fn new() -> Self {
|
pub(super) async fn new() -> Self {
|
||||||
|
let (output_tx, output_rx) = oneshot::channel();
|
||||||
|
let (seat_tx, seat_rx) = oneshot::channel();
|
||||||
|
|
||||||
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
|
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
|
||||||
|
|
||||||
let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel();
|
let toplevels = Arc::new(RwLock::new(IndexMap::new()));
|
||||||
#[cfg(feature = "clipboard")]
|
let toplevels2 = toplevels.clone();
|
||||||
let (clipboard_init_tx, clipboard_init_rx) = mpsc::channel();
|
|
||||||
let (output_tx, output_rx) = mpsc::channel();
|
|
||||||
let (seat_tx, seat_rx) = mpsc::channel();
|
|
||||||
|
|
||||||
let toplevel_tx2 = toplevel_tx.clone();
|
let toplevel_tx2 = toplevel_tx.clone();
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
let (clipboard_tx, clipboard_rx) = broadcast::channel(32);
|
let (clipboard_tx, mut clipboard_rx) = broadcast::channel(32);
|
||||||
|
let clipboard = Arc::new(RwLock::new(None));
|
||||||
let clipboard_tx2 = clipboard_tx.clone();
|
let clipboard_tx2 = clipboard_tx.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,100 +82,85 @@ impl WaylandClient {
|
|||||||
|
|
||||||
// `queue` is not `Send` so we need to handle everything inside the task
|
// `queue` is not `Send` so we need to handle everything inside the task
|
||||||
spawn_blocking(move || {
|
spawn_blocking(move || {
|
||||||
|
let toplevels = toplevels2;
|
||||||
let toplevel_tx = toplevel_tx2;
|
let toplevel_tx = toplevel_tx2;
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
let clipboard_tx = clipboard_tx2;
|
|
||||||
|
|
||||||
let conn =
|
let (env, _display, queue) =
|
||||||
Connection::connect_to_env().expect("Failed to connect to Wayland compositor");
|
Self::new_environment().expect("Failed to connect to Wayland compositor");
|
||||||
let (globals, queue) =
|
|
||||||
registry_queue_init(&conn).expect("Failed to retrieve Wayland globals");
|
|
||||||
|
|
||||||
let qh = queue.handle();
|
|
||||||
let mut event_loop =
|
let mut event_loop =
|
||||||
EventLoop::<Environment>::try_new().expect("Failed to create new event loop");
|
EventLoop::<DData>::try_new().expect("Failed to create new event loop");
|
||||||
|
|
||||||
WaylandSource::new(queue)
|
WaylandSource::new(queue)
|
||||||
.expect("Failed to create Wayland source from queue")
|
.quick_insert(event_loop.handle())
|
||||||
.insert(event_loop.handle())
|
|
||||||
.expect("Failed to insert Wayland event queue into event loop");
|
.expect("Failed to insert Wayland event queue into event loop");
|
||||||
|
|
||||||
let loop_handle = event_loop.handle();
|
let outputs = Self::get_outputs(&env);
|
||||||
|
send!(output_tx, outputs);
|
||||||
|
|
||||||
// Initialize the registry handling
|
let seats = env.get_all_seats();
|
||||||
// so other parts of Smithay's client toolkit may bind globals.
|
|
||||||
let registry_state = RegistryState::new(&globals);
|
|
||||||
|
|
||||||
let output_delegate = OutputState::new(&globals, &qh);
|
// TODO: Actually handle seats properly
|
||||||
let seat_delegate = SeatState::new(&globals, &qh);
|
#[cfg(feature = "clipboard")]
|
||||||
|
let default_seat = seats[0].detach();
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
send!(
|
||||||
let data_control_device_manager_delegate =
|
seat_tx,
|
||||||
DataControlDeviceManagerState::bind(&globals, &qh)
|
seats
|
||||||
.expect("data device manager is not available");
|
.into_iter()
|
||||||
|
.map(|seat| seat.detach())
|
||||||
|
.collect::<Vec<WlSeat>>()
|
||||||
|
);
|
||||||
|
|
||||||
let foreign_toplevel_manager_delegate = ToplevelManagerState::bind(&globals, &qh)
|
let handle = event_loop.handle();
|
||||||
.expect("foreign toplevel manager is not available");
|
handle
|
||||||
|
.insert_source(ev_rx, move |event, _metadata, ddata| {
|
||||||
let mut env = Environment {
|
// let env = &ddata.env;
|
||||||
registry_state,
|
|
||||||
output_state: output_delegate,
|
|
||||||
seat_state: seat_delegate,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
data_control_device_manager_state: data_control_device_manager_delegate,
|
|
||||||
foreign_toplevel_manager_state: foreign_toplevel_manager_delegate,
|
|
||||||
seats: vec![],
|
|
||||||
handles: HashMap::new(),
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
clipboard: Arc::new(Mutex::new(None)),
|
|
||||||
toplevel_tx,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
clipboard_tx,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
data_control_devices: vec![],
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
selection_offers: vec![],
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
copy_paste_sources: vec![],
|
|
||||||
loop_handle: event_loop.handle(),
|
|
||||||
};
|
|
||||||
|
|
||||||
loop_handle
|
|
||||||
.insert_source(ev_rx, move |event, _metadata, env| {
|
|
||||||
trace!("{event:?}");
|
|
||||||
match event {
|
match event {
|
||||||
Event::Msg(Request::Roundtrip) => debug!("Received refresh event"),
|
Event::Msg(Request::Refresh) => debug!("Received refresh event"),
|
||||||
Event::Msg(Request::Outputs) => {
|
|
||||||
trace!("Received get outputs request");
|
|
||||||
|
|
||||||
send!(output_tx, env.output_info());
|
|
||||||
}
|
|
||||||
Event::Msg(Request::Seats) => {
|
|
||||||
trace!("Receive get seats request");
|
|
||||||
send!(seat_tx, env.seats.clone());
|
|
||||||
}
|
|
||||||
Event::Msg(Request::Toplevels) => {
|
|
||||||
trace!("Receive get toplevels request");
|
|
||||||
send!(toplevel_init_tx, env.handles.clone());
|
|
||||||
}
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
Event::Msg(Request::Clipboard) => {
|
|
||||||
trace!("Receive get clipboard requests");
|
|
||||||
let clipboard = lock!(env.clipboard).clone();
|
|
||||||
send!(clipboard_init_tx, clipboard);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
Event::Msg(Request::CopyToClipboard(value)) => {
|
Event::Msg(Request::CopyToClipboard(value)) => {
|
||||||
env.copy_to_clipboard(value, &qh);
|
super::wlr_data_control::copy_to_clipboard(
|
||||||
|
&ddata.env,
|
||||||
|
&default_seat,
|
||||||
|
&value,
|
||||||
|
)
|
||||||
|
.expect("Failed to copy to clipboard");
|
||||||
}
|
}
|
||||||
Event::Closed => panic!("Channel unexpectedly closed"),
|
Event::Closed => panic!("Channel unexpectedly closed"),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.expect("Failed to insert channel into event queue");
|
.expect("Failed to insert channel into event queue");
|
||||||
|
|
||||||
|
let _toplevel_manager = env.require_global::<ZwlrForeignToplevelManagerV1>();
|
||||||
|
|
||||||
|
let _toplevel_listener = listen_for_toplevels(&env, move |handle, event, _ddata| {
|
||||||
|
super::wlr_foreign_toplevel::update_toplevels(
|
||||||
|
&toplevels,
|
||||||
|
handle,
|
||||||
|
event,
|
||||||
|
&toplevel_tx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(feature = "clipboard")] {
|
||||||
|
let clipboard_tx = clipboard_tx2;
|
||||||
|
let handle = event_loop.handle();
|
||||||
|
|
||||||
|
let _offer_listener = listen_to_devices(&env, move |_seat, event, ddata| {
|
||||||
|
debug!("Received clipboard event");
|
||||||
|
super::wlr_data_control::receive_offer(event, &handle, clipboard_tx.clone(), ddata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = DData {
|
||||||
|
env,
|
||||||
|
offer_tokens: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
trace!("Dispatching event loop");
|
if let Err(err) = event_loop.dispatch(None, &mut data) {
|
||||||
if let Err(err) = event_loop.dispatch(None, &mut env) {
|
|
||||||
error!(
|
error!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
|
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
|
||||||
@@ -195,76 +169,119 @@ impl WaylandClient {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// keep track of current clipboard item
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
{
|
||||||
|
let clipboard = clipboard.clone();
|
||||||
|
spawn(async move {
|
||||||
|
while let Ok(item) = clipboard_rx.recv().await {
|
||||||
|
let mut clipboard = write_lock!(clipboard);
|
||||||
|
clipboard.replace(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputs = output_rx.await.expect(err::ERR_CHANNEL_RECV);
|
||||||
|
|
||||||
|
let seats = seat_rx.await.expect(err::ERR_CHANNEL_RECV);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
outputs,
|
||||||
|
seats,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
clipboard,
|
||||||
|
toplevels,
|
||||||
toplevel_tx,
|
toplevel_tx,
|
||||||
_toplevel_rx: toplevel_rx,
|
_toplevel_rx: toplevel_rx,
|
||||||
toplevel_init_rx,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
clipboard_init_rx,
|
|
||||||
output_rx,
|
|
||||||
seat_rx,
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx,
|
clipboard_tx,
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
_clipboard_rx: clipboard_rx,
|
|
||||||
request_tx: ev_tx,
|
request_tx: ev_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe_toplevels(
|
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
|
||||||
&self,
|
self.toplevel_tx.subscribe()
|
||||||
) -> (
|
|
||||||
broadcast::Receiver<ToplevelEvent>,
|
|
||||||
HashMap<usize, ToplevelHandle>,
|
|
||||||
) {
|
|
||||||
let rx = self.toplevel_tx.subscribe();
|
|
||||||
|
|
||||||
let receiver = &self.toplevel_init_rx;
|
|
||||||
send!(self.request_tx, Request::Toplevels);
|
|
||||||
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
|
|
||||||
|
|
||||||
(rx, data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
pub fn subscribe_clipboard(
|
pub fn subscribe_clipboard(&self) -> broadcast::Receiver<Arc<ClipboardItem>> {
|
||||||
&self,
|
self.clipboard_tx.subscribe()
|
||||||
) -> (
|
|
||||||
broadcast::Receiver<Arc<ClipboardItem>>,
|
|
||||||
Option<Arc<ClipboardItem>>,
|
|
||||||
) {
|
|
||||||
let rx = self.clipboard_tx.subscribe();
|
|
||||||
|
|
||||||
let receiver = &self.clipboard_init_rx;
|
|
||||||
send!(self.request_tx, Request::Clipboard);
|
|
||||||
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
|
|
||||||
|
|
||||||
(rx, data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force a roundtrip on the wayland connection,
|
|
||||||
/// flushing any queued events and immediately receiving any new ones.
|
|
||||||
pub fn roundtrip(&self) {
|
pub fn roundtrip(&self) {
|
||||||
trace!("Sending roundtrip request");
|
send!(self.request_tx, Request::Refresh);
|
||||||
send!(self.request_tx, Request::Roundtrip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_outputs(&self) -> Vec<OutputInfo> {
|
#[cfg(feature = "clipboard")]
|
||||||
trace!("Sending get outputs request");
|
pub fn get_clipboard(&self) -> Option<Arc<ClipboardItem>> {
|
||||||
|
let clipboard = read_lock!(self.clipboard);
|
||||||
send!(self.request_tx, Request::Outputs);
|
clipboard.as_ref().cloned()
|
||||||
self.output_rx.recv().expect(ERR_CHANNEL_RECV)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_seats(&self) -> Vec<WlSeat> {
|
|
||||||
trace!("Sending get seats request");
|
|
||||||
|
|
||||||
send!(self.request_tx, Request::Seats);
|
|
||||||
self.seat_rx.recv().expect(ERR_CHANNEL_RECV)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
pub fn copy_to_clipboard(&self, item: Arc<ClipboardItem>) {
|
pub fn copy_to_clipboard(&self, item: Arc<ClipboardItem>) {
|
||||||
send!(self.request_tx, Request::CopyToClipboard(item));
|
send!(self.request_tx, Request::CopyToClipboard(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_outputs(env: &Environment<Env>) -> Vec<OutputInfo> {
|
||||||
|
let outputs = env.get_all_outputs();
|
||||||
|
|
||||||
|
outputs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|output| with_output_info(output, Clone::clone))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_environment() -> Result<(Environment<Env>, Display, EventQueue), ConnectError> {
|
||||||
|
Display::connect_to_env().and_then(|display| {
|
||||||
|
let mut queue = display.create_event_queue();
|
||||||
|
let ret = {
|
||||||
|
let mut sctk_seats = smithay_client_toolkit::seat::SeatHandler::new();
|
||||||
|
let sctk_data_device_manager =
|
||||||
|
smithay_client_toolkit::data_device::DataDeviceHandler::init(&mut sctk_seats);
|
||||||
|
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
let data_control_device = DataControlDeviceHandler::init(&mut sctk_seats);
|
||||||
|
|
||||||
|
let sctk_primary_selection_manager =
|
||||||
|
smithay_client_toolkit::primary_selection::PrimarySelectionHandler::init(
|
||||||
|
&mut sctk_seats,
|
||||||
|
);
|
||||||
|
|
||||||
|
let display = ::smithay_client_toolkit::reexports::client::Proxy::clone(&display);
|
||||||
|
let env = Environment::new(
|
||||||
|
&display.attach(queue.token()),
|
||||||
|
&mut queue,
|
||||||
|
Env {
|
||||||
|
sctk_compositor: smithay_client_toolkit::environment::SimpleGlobal::new(),
|
||||||
|
sctk_subcompositor: smithay_client_toolkit::environment::SimpleGlobal::new(
|
||||||
|
),
|
||||||
|
sctk_shm: smithay_client_toolkit::shm::ShmHandler::new(),
|
||||||
|
sctk_outputs: smithay_client_toolkit::output::OutputHandler::new(),
|
||||||
|
sctk_seats,
|
||||||
|
sctk_data_device_manager,
|
||||||
|
sctk_primary_selection_manager,
|
||||||
|
toplevel: ToplevelHandler::init(),
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
data_control_device,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Ok(env) = env.as_ref() {
|
||||||
|
let _psm = env.get_primary_selection_manager();
|
||||||
|
}
|
||||||
|
|
||||||
|
env
|
||||||
|
};
|
||||||
|
match ret {
|
||||||
|
Ok(env) => Ok((env, display, queue)),
|
||||||
|
Err(_e) => display.protocol_error().map_or_else(
|
||||||
|
|| Err(ConnectError::NoCompositorListening),
|
||||||
|
|perr| {
|
||||||
|
panic!("[SCTK] A protocol error occured during initial setup: {perr}");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
/// It is necessary to store macros in a separate file due to a compilation error.
|
|
||||||
/// I believe this stems from the feature flags.
|
|
||||||
/// Related issue: https://github.com/rust-lang/rust/issues/81066
|
|
||||||
|
|
||||||
// --- Data Control Device --- \\
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! delegate_data_control_device_manager {
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1: smithay_client_toolkit::globals::GlobalData
|
|
||||||
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! delegate_data_control_device {
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1: $udata,
|
|
||||||
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1: $crate::clients::wayland::wlr_data_control::device::DataControlDeviceData
|
|
||||||
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! delegate_data_control_offer {
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $udata,
|
|
||||||
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $crate::clients::wayland::wlr_data_control::offer::DataControlOfferData
|
|
||||||
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! delegate_data_control_source {
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1: $udata,
|
|
||||||
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1: $crate::clients::wayland::wlr_data_control::source::DataControlSourceData
|
|
||||||
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Foreign Toplevel --- \\
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! delegate_foreign_toplevel_manager {
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1: smithay_client_toolkit::globals::GlobalData
|
|
||||||
] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! delegate_foreign_toplevel_handle {
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: $udata,
|
|
||||||
] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
|
||||||
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
|
||||||
[
|
|
||||||
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: $crate::clients::wayland::wlr_foreign_toplevel::handle::ToplevelHandleData
|
|
||||||
] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,110 +1,70 @@
|
|||||||
mod client;
|
mod client;
|
||||||
|
|
||||||
mod macros;
|
|
||||||
mod wl_output;
|
|
||||||
mod wl_seat;
|
|
||||||
mod wlr_foreign_toplevel;
|
mod wlr_foreign_toplevel;
|
||||||
|
|
||||||
use self::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
|
||||||
use crate::{delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
|
|
||||||
use async_once::AsyncOnce;
|
|
||||||
use cfg_if::cfg_if;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use smithay_client_toolkit::output::OutputState;
|
|
||||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
|
||||||
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
|
||||||
use smithay_client_toolkit::seat::SeatState;
|
|
||||||
use smithay_client_toolkit::{
|
|
||||||
delegate_output, delegate_registry, delegate_seat, registry_handlers,
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tokio::sync::broadcast;
|
use async_once::AsyncOnce;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use lazy_static::lazy_static;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use smithay_client_toolkit::default_environment;
|
||||||
|
use smithay_client_toolkit::environment::Environment;
|
||||||
|
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
||||||
|
use wayland_client::{Attached, Interface};
|
||||||
|
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
|
||||||
|
pub use wlr_foreign_toplevel::handle::{ToplevelChange, ToplevelEvent, ToplevelInfo};
|
||||||
|
use wlr_foreign_toplevel::manager::{ToplevelHandler};
|
||||||
|
|
||||||
pub use self::client::WaylandClient;
|
pub use client::WaylandClient;
|
||||||
pub use self::wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
|
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
mod wlr_data_control;
|
mod wlr_data_control;
|
||||||
|
|
||||||
use crate::{delegate_data_control_device, delegate_data_control_device_manager, delegate_data_control_offer, delegate_data_control_source};
|
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||||
use self::wlr_data_control::device::DataControlDevice;
|
use wlr_data_control::manager::DataControlDeviceHandler;
|
||||||
use self::wlr_data_control::manager::DataControlDeviceManagerState;
|
|
||||||
use self::wlr_data_control::source::CopyPasteSource;
|
|
||||||
use self::wlr_data_control::SelectionOfferItem;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
||||||
|
|
||||||
pub struct DataControlDeviceEntry {
|
|
||||||
seat: WlSeat,
|
|
||||||
device: DataControlDevice,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Environment {
|
/// A utility for lazy-loading globals.
|
||||||
pub registry_state: RegistryState,
|
/// Taken from `smithay_client_toolkit` where it's not exposed
|
||||||
pub output_state: OutputState,
|
#[derive(Debug)]
|
||||||
pub seat_state: SeatState,
|
enum LazyGlobal<I: Interface> {
|
||||||
pub foreign_toplevel_manager_state: ToplevelManagerState,
|
Unknown,
|
||||||
#[cfg(feature = "clipboard")]
|
Seen { id: u32, version: u32 },
|
||||||
pub data_control_device_manager_state: DataControlDeviceManagerState,
|
Bound(Attached<I>),
|
||||||
pub loop_handle: LoopHandle<'static, Self>,
|
|
||||||
|
|
||||||
pub seats: Vec<WlSeat>,
|
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
pub data_control_devices: Vec<DataControlDeviceEntry>,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
pub selection_offers: Vec<SelectionOfferItem>,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
pub copy_paste_sources: Vec<CopyPasteSource>,
|
|
||||||
|
|
||||||
pub handles: HashMap<usize, ToplevelHandle>,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
clipboard: Arc<Mutex<Option<Arc<ClipboardItem>>>>,
|
|
||||||
|
|
||||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we need to say we are delegating the responsibility of output related events for our application data
|
pub struct DData {
|
||||||
// type to the requisite delegate.
|
env: Environment<Env>,
|
||||||
delegate_output!(Environment);
|
offer_tokens: HashMap<u128, RegistrationToken>,
|
||||||
delegate_seat!(Environment);
|
}
|
||||||
|
|
||||||
delegate_foreign_toplevel_manager!(Environment);
|
|
||||||
delegate_foreign_toplevel_handle!(Environment);
|
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
delegate_data_control_device_manager!(Environment);
|
default_environment!(Env,
|
||||||
delegate_data_control_device!(Environment);
|
fields = [
|
||||||
delegate_data_control_source!(Environment);
|
toplevel: ToplevelHandler,
|
||||||
delegate_data_control_offer!(Environment);
|
data_control_device: DataControlDeviceHandler
|
||||||
|
],
|
||||||
|
singles = [
|
||||||
|
ZwlrForeignToplevelManagerV1 => toplevel,
|
||||||
|
ZwlrDataControlManagerV1 => data_control_device
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
default_environment!(Env,
|
||||||
|
fields = [
|
||||||
|
toplevel: ToplevelHandler,
|
||||||
|
],
|
||||||
|
singles = [
|
||||||
|
ZwlrForeignToplevelManagerV1 => toplevel,
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In order for our delegate to know of the existence of globals, we need to implement registry
|
|
||||||
// handling for the program. This trait will forward events to the RegistryHandler trait
|
|
||||||
// implementations.
|
|
||||||
delegate_registry!(Environment);
|
|
||||||
|
|
||||||
// In order for delegate_registry to work, our application data type needs to provide a way for the
|
|
||||||
// implementation to access the registry state.
|
|
||||||
//
|
|
||||||
// We also need to indicate which delegates will get told about globals being created. We specify
|
|
||||||
// the types of the delegates inside the array.
|
|
||||||
impl ProvidesRegistryState for Environment {
|
|
||||||
fn registry(&mut self) -> &mut RegistryState {
|
|
||||||
&mut self.registry_state
|
|
||||||
}
|
|
||||||
registry_handlers![OutputState, SeatState];
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CLIENT: AsyncOnce<WaylandClient> =
|
static ref CLIENT: AsyncOnce<WaylandClient> =
|
||||||
AsyncOnce::new(async { WaylandClient::new().await });
|
AsyncOnce::new(async { WaylandClient::new().await });
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
use super::Environment;
|
|
||||||
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
|
|
||||||
use tracing::debug;
|
|
||||||
use wayland_client::protocol::wl_output;
|
|
||||||
use wayland_client::{Connection, QueueHandle};
|
|
||||||
|
|
||||||
impl Environment {
|
|
||||||
pub fn output_info(&mut self) -> Vec<OutputInfo> {
|
|
||||||
self.output_state
|
|
||||||
.outputs()
|
|
||||||
.filter_map(|output| self.output_state.info(&output))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In order to use OutputDelegate, we must implement this trait to indicate when something has happened to an
|
|
||||||
// output and to provide an instance of the output state to the delegate when dispatching events.
|
|
||||||
impl OutputHandler for Environment {
|
|
||||||
// First we need to provide a way to access the delegate.
|
|
||||||
//
|
|
||||||
// This is needed because delegate implementations for handling events use the application data type in
|
|
||||||
// their function signatures. This allows the implementation to access an instance of the type.
|
|
||||||
fn output_state(&mut self) -> &mut OutputState {
|
|
||||||
&mut self.output_state
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then there exist these functions that indicate the lifecycle of an output.
|
|
||||||
// These will be called as appropriate by the delegate implementation.
|
|
||||||
|
|
||||||
fn new_output(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_output: wl_output::WlOutput,
|
|
||||||
) {
|
|
||||||
debug!("Handler received new output");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_output(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_output: wl_output::WlOutput,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn output_destroyed(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_output: wl_output::WlOutput,
|
|
||||||
) {
|
|
||||||
debug!("Handle received output destruction");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
use super::Environment;
|
|
||||||
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
|
|
||||||
use tracing::debug;
|
|
||||||
use wayland_client::protocol::wl_seat;
|
|
||||||
use wayland_client::{Connection, QueueHandle};
|
|
||||||
|
|
||||||
impl SeatHandler for Environment {
|
|
||||||
fn seat_state(&mut self) -> &mut SeatState {
|
|
||||||
&mut self.seat_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
|
|
||||||
debug!("Handler received new seat");
|
|
||||||
self.seats.push(seat);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_capability(
|
|
||||||
&mut self,
|
|
||||||
_: &Connection,
|
|
||||||
qh: &QueueHandle<Self>,
|
|
||||||
seat: wl_seat::WlSeat,
|
|
||||||
_: Capability,
|
|
||||||
) {
|
|
||||||
debug!("Handler received new capability");
|
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
if !self
|
|
||||||
.data_control_devices
|
|
||||||
.iter_mut()
|
|
||||||
.any(|entry| entry.seat == seat)
|
|
||||||
{
|
|
||||||
debug!("Adding new data control device");
|
|
||||||
// create the data device here for this seat
|
|
||||||
let data_control_device_manager = &self.data_control_device_manager_state;
|
|
||||||
let data_control_device = data_control_device_manager.get_data_device(qh, &seat);
|
|
||||||
self.data_control_devices
|
|
||||||
.push(super::DataControlDeviceEntry {
|
|
||||||
seat: seat.clone(),
|
|
||||||
device: data_control_device,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.seats.iter().any(|s| s == &seat) {
|
|
||||||
self.seats.push(seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_capability(
|
|
||||||
&mut self,
|
|
||||||
_: &Connection,
|
|
||||||
_: &QueueHandle<Self>,
|
|
||||||
_: wl_seat::WlSeat,
|
|
||||||
_: Capability,
|
|
||||||
) {
|
|
||||||
debug!("Handler received capability removal");
|
|
||||||
// Not applicable
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
|
|
||||||
debug!("Handler received seat removal");
|
|
||||||
self.seats.retain(|s| s != &seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +1,88 @@
|
|||||||
use super::manager::DataControlDeviceManagerState;
|
use super::offer::DataControlOffer;
|
||||||
use super::offer::{
|
use super::source::DataControlSource;
|
||||||
DataControlOfferData, DataControlOfferDataExt, DataControlOfferHandler, SelectionOffer,
|
|
||||||
};
|
|
||||||
use crate::error::ERR_WAYLAND_DATA;
|
|
||||||
use crate::lock;
|
use crate::lock;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::warn;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_client::{event_created_child, Connection, Dispatch, Proxy, QueueHandle};
|
use wayland_client::{Attached, DispatchData, Main};
|
||||||
use wayland_protocols_wlr::data_control::v1::client::{
|
use wayland_protocols::wlr::unstable::data_control::v1::client::{
|
||||||
zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1},
|
zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1},
|
||||||
|
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
||||||
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct DataControlDevice {
|
#[derive(Debug)]
|
||||||
pub device: ZwlrDataControlDeviceV1,
|
struct Inner {
|
||||||
|
offer: Option<Arc<DataControlOffer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
impl Inner {
|
||||||
pub struct DataControlDeviceInner {
|
fn new_offer(&mut self, offer: &Main<ZwlrDataControlOfferV1>) {
|
||||||
/// the active selection offer and its data
|
self.offer.replace(Arc::new(DataControlOffer::new(offer)));
|
||||||
selection_offer: Arc<Mutex<Option<ZwlrDataControlOfferV1>>>,
|
|
||||||
/// the active undetermined offers and their data
|
|
||||||
pub undetermined_offers: Arc<Mutex<Vec<ZwlrDataControlOfferV1>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct DataControlDeviceData {
|
|
||||||
pub(super) inner: Arc<Mutex<DataControlDeviceInner>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DataControlDeviceDataExt: Send + Sync {
|
|
||||||
type DataControlOfferInner: DataControlOfferDataExt + Send + Sync + 'static;
|
|
||||||
|
|
||||||
fn data_control_device_data(&self) -> &DataControlDeviceData;
|
|
||||||
|
|
||||||
fn selection_mime_types(&self) -> Vec<String> {
|
|
||||||
let inner = self.data_control_device_data();
|
|
||||||
lock!(lock!(inner.inner).selection_offer)
|
|
||||||
.as_ref()
|
|
||||||
.map(|offer| {
|
|
||||||
let data = offer
|
|
||||||
.data::<Self::DataControlOfferInner>()
|
|
||||||
.expect(ERR_WAYLAND_DATA);
|
|
||||||
data.mime_types()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the active selection offer if it exists.
|
|
||||||
fn selection_offer(&self) -> Option<SelectionOffer> {
|
|
||||||
let inner = self.data_control_device_data();
|
|
||||||
lock!(lock!(inner.inner).selection_offer)
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|offer| {
|
|
||||||
let data = offer
|
|
||||||
.data::<Self::DataControlOfferInner>()
|
|
||||||
.expect(ERR_WAYLAND_DATA);
|
|
||||||
data.as_selection_offer()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataControlDeviceDataExt for DataControlDevice {
|
#[derive(Debug, Clone)]
|
||||||
type DataControlOfferInner = DataControlOfferData;
|
pub struct DataControlDeviceEvent(pub Arc<DataControlOffer>);
|
||||||
fn data_control_device_data(&self) -> &DataControlDeviceData {
|
|
||||||
self.device.data().expect(ERR_WAYLAND_DATA)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlDeviceDataExt for DataControlDeviceData {
|
fn data_control_device_implem<F>(
|
||||||
type DataControlOfferInner = DataControlOfferData;
|
|
||||||
fn data_control_device_data(&self) -> &DataControlDeviceData {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler trait for `DataDevice` events.
|
|
||||||
///
|
|
||||||
/// The functions defined in this trait are called as `DataDevice` events are received from the compositor.
|
|
||||||
pub trait DataControlDeviceHandler: Sized {
|
|
||||||
/// Advertises a new selection.
|
|
||||||
fn selection(
|
|
||||||
&mut self,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<Self>,
|
|
||||||
data_device: DataControlDevice,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, U, V> Dispatch<ZwlrDataControlDeviceV1, U, D> for DataControlDeviceManagerState<V>
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlDeviceV1, U>
|
|
||||||
+ Dispatch<ZwlrDataControlOfferV1, V>
|
|
||||||
+ DataControlDeviceHandler
|
|
||||||
+ DataControlOfferHandler
|
|
||||||
+ 'static,
|
|
||||||
U: DataControlDeviceDataExt,
|
|
||||||
V: DataControlOfferDataExt + Default + 'static + Send + Sync,
|
|
||||||
{
|
|
||||||
event_created_child!(D, ZwlrDataControlDeviceV1, [
|
|
||||||
0 => (ZwlrDataControlOfferV1, V::default())
|
|
||||||
]);
|
|
||||||
|
|
||||||
fn event(
|
|
||||||
state: &mut D,
|
|
||||||
data_device: &ZwlrDataControlDeviceV1,
|
|
||||||
event: Event,
|
event: Event,
|
||||||
data: &U,
|
inner: &mut Inner,
|
||||||
conn: &Connection,
|
implem: &mut F,
|
||||||
qh: &QueueHandle<D>,
|
ddata: DispatchData,
|
||||||
) {
|
) where
|
||||||
let data = data.data_control_device_data();
|
F: FnMut(DataControlDeviceEvent, DispatchData),
|
||||||
let inner = lock!(data.inner);
|
{
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::DataOffer { id } => {
|
Event::DataOffer { id } => {
|
||||||
// XXX Drop done here to prevent Mutex deadlocks.S
|
inner.new_offer(&id);
|
||||||
|
|
||||||
lock!(inner.undetermined_offers).push(id.clone());
|
|
||||||
let data = id
|
|
||||||
.data::<V>()
|
|
||||||
.expect(ERR_WAYLAND_DATA)
|
|
||||||
.data_control_offer_data();
|
|
||||||
data.init_undetermined_offer(&id);
|
|
||||||
|
|
||||||
// Append the data offer to our list of offers.
|
|
||||||
drop(inner);
|
|
||||||
}
|
}
|
||||||
Event::Selection { id } => {
|
Event::Selection { id: Some(offer) } => {
|
||||||
let mut selection_offer = lock!(inner.selection_offer);
|
let inner_offer = inner
|
||||||
|
.offer
|
||||||
if let Some(offer) = id {
|
.clone()
|
||||||
let mut undetermined = lock!(inner.undetermined_offers);
|
.expect("Offer should exist at this stage");
|
||||||
if let Some(i) = undetermined.iter().position(|o| o == &offer) {
|
if offer == inner_offer.offer {
|
||||||
undetermined.remove(i);
|
implem(DataControlDeviceEvent(inner_offer), ddata);
|
||||||
}
|
}
|
||||||
drop(undetermined);
|
|
||||||
|
|
||||||
let data = offer
|
|
||||||
.data::<V>()
|
|
||||||
.expect(ERR_WAYLAND_DATA)
|
|
||||||
.data_control_offer_data();
|
|
||||||
data.to_selection_offer();
|
|
||||||
// XXX Drop done here to prevent Mutex deadlocks.
|
|
||||||
*selection_offer = Some(offer.clone());
|
|
||||||
drop(selection_offer);
|
|
||||||
drop(inner);
|
|
||||||
state.selection(
|
|
||||||
conn,
|
|
||||||
qh,
|
|
||||||
DataControlDevice {
|
|
||||||
device: data_device.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
*selection_offer = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Finished => {
|
|
||||||
warn!("Data control offer is no longer valid, but has not been dropped by client. This could cause clipboard issues.");
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DataControlDevice {
|
||||||
|
device: ZwlrDataControlDeviceV1,
|
||||||
|
_inner: Arc<Mutex<Inner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlDevice {
|
||||||
|
pub fn init_for_seat<F>(
|
||||||
|
manager: &Attached<ZwlrDataControlManagerV1>,
|
||||||
|
seat: &WlSeat,
|
||||||
|
mut callback: F,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(DataControlDeviceEvent, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
let inner = Arc::new(Mutex::new(Inner { offer: None }));
|
||||||
|
|
||||||
|
let device = manager.get_data_device(seat);
|
||||||
|
|
||||||
|
{
|
||||||
|
let inner = inner.clone();
|
||||||
|
device.quick_assign(move |_handle, event, ddata| {
|
||||||
|
let mut inner = lock!(inner);
|
||||||
|
data_control_device_implem(event, &mut inner, &mut callback, ddata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
device: device.detach(),
|
||||||
|
_inner: inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selection(&self, source: &Option<DataControlSource>) {
|
||||||
|
self.device
|
||||||
|
.set_selection(source.as_ref().map(|s| &s.source));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +1,253 @@
|
|||||||
use super::device::{DataControlDevice, DataControlDeviceData, DataControlDeviceDataExt};
|
use super::device::{DataControlDevice, DataControlDeviceEvent};
|
||||||
use super::offer::DataControlOfferData;
|
use super::source::DataControlSource;
|
||||||
use super::source::{CopyPasteSource, DataControlSourceData, DataControlSourceDataExt};
|
use smithay_client_toolkit::data_device::WritePipe;
|
||||||
use smithay_client_toolkit::error::GlobalError;
|
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
|
||||||
use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal};
|
use smithay_client_toolkit::seat::{SeatHandling, SeatListener};
|
||||||
use std::marker::PhantomData;
|
use smithay_client_toolkit::MissingGlobal;
|
||||||
use tracing::debug;
|
use std::cell::RefCell;
|
||||||
use wayland_client::globals::{BindError, GlobalList};
|
use std::rc::{self, Rc};
|
||||||
|
use tracing::warn;
|
||||||
|
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
use wayland_client::{Attached, DispatchData};
|
||||||
use wayland_protocols_wlr::data_control::v1::client::{
|
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||||
zwlr_data_control_device_v1::ZwlrDataControlDeviceV1,
|
|
||||||
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
|
||||||
zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct DataControlDeviceManagerState<V = DataControlOfferData> {
|
enum DataControlDeviceHandlerInner {
|
||||||
manager: ZwlrDataControlManagerV1,
|
Ready {
|
||||||
_phantom: PhantomData<V>,
|
manager: Attached<ZwlrDataControlManagerV1>,
|
||||||
|
devices: Vec<(WlSeat, DataControlDevice)>,
|
||||||
|
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>>,
|
||||||
|
},
|
||||||
|
Pending {
|
||||||
|
seats: Vec<WlSeat>,
|
||||||
|
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataControlDeviceManagerState {
|
impl DataControlDeviceHandlerInner {
|
||||||
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
|
fn init_manager(&mut self, manager: Attached<ZwlrDataControlManagerV1>) {
|
||||||
where
|
let (seats, status_listeners) = if let Self::Pending {
|
||||||
State: Dispatch<ZwlrDataControlManagerV1, GlobalData, State> + 'static,
|
seats,
|
||||||
|
status_listeners,
|
||||||
|
} = self
|
||||||
{
|
{
|
||||||
let manager = globals.bind(qh, 1..=2, GlobalData)?;
|
(std::mem::take(seats), status_listeners.clone())
|
||||||
debug!("Bound to ZwlDataControlManagerV1 global");
|
} else {
|
||||||
Ok(Self {
|
warn!("Ignoring second zwlr_data_control_manager_v1");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut devices = Vec::new();
|
||||||
|
|
||||||
|
for seat in seats {
|
||||||
|
let my_seat = seat.clone();
|
||||||
|
let status_listeners = status_listeners.clone();
|
||||||
|
let device =
|
||||||
|
DataControlDevice::init_for_seat(&manager, &seat, move |event, dispatch_data| {
|
||||||
|
notify_status_listeners(&my_seat, &event, dispatch_data, &status_listeners);
|
||||||
|
});
|
||||||
|
devices.push((seat.clone(), device));
|
||||||
|
}
|
||||||
|
|
||||||
|
*self = Self::Ready {
|
||||||
manager,
|
manager,
|
||||||
_phantom: PhantomData,
|
devices,
|
||||||
|
status_listeners,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_manager(&self) -> Option<Attached<ZwlrDataControlManagerV1>> {
|
||||||
|
match self {
|
||||||
|
Self::Ready { manager, .. } => Some(manager.clone()),
|
||||||
|
Self::Pending { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_seat(&mut self, seat: &WlSeat) {
|
||||||
|
match self {
|
||||||
|
Self::Ready {
|
||||||
|
manager,
|
||||||
|
devices,
|
||||||
|
status_listeners,
|
||||||
|
} => {
|
||||||
|
if devices.iter().any(|(s, _)| s == seat) {
|
||||||
|
// the seat already exists, nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let my_seat = seat.clone();
|
||||||
|
let status_listeners = status_listeners.clone();
|
||||||
|
let device =
|
||||||
|
DataControlDevice::init_for_seat(manager, seat, move |event, dispatch_data| {
|
||||||
|
notify_status_listeners(&my_seat, &event, dispatch_data, &status_listeners);
|
||||||
|
});
|
||||||
|
devices.push((seat.clone(), device));
|
||||||
|
}
|
||||||
|
Self::Pending { seats, .. } => {
|
||||||
|
seats.push(seat.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_seat(&mut self, seat: &WlSeat) {
|
||||||
|
match self {
|
||||||
|
Self::Ready { devices, .. } => devices.retain(|(s, _)| s != seat),
|
||||||
|
Self::Pending { seats, .. } => seats.retain(|s| s != seat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
||||||
|
where
|
||||||
|
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Ready { manager, .. } => {
|
||||||
|
let source = DataControlSource::new(manager, mime_types, callback);
|
||||||
|
Some(source)
|
||||||
|
}
|
||||||
|
Self::Pending { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
||||||
|
where
|
||||||
|
F: FnOnce(&DataControlDevice),
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Ready { devices, .. } => {
|
||||||
|
let device = devices
|
||||||
|
.iter()
|
||||||
|
.find_map(|(s, device)| if s == seat { Some(device) } else { None });
|
||||||
|
|
||||||
|
device.map_or(Err(MissingGlobal), |device| {
|
||||||
|
f(device);
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Self::Pending { .. } => Err(MissingGlobal),
|
||||||
/// creates a data source for copy paste
|
|
||||||
pub fn create_copy_paste_source<'s, D, I>(
|
|
||||||
&self,
|
|
||||||
qh: &QueueHandle<D>,
|
|
||||||
mime_types: I,
|
|
||||||
) -> CopyPasteSource
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlSourceV1, DataControlSourceData> + 'static,
|
|
||||||
I: IntoIterator<Item = &'s str>,
|
|
||||||
{
|
|
||||||
CopyPasteSource {
|
|
||||||
inner: self.create_data_control_source(qh, mime_types),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// creates a data source
|
|
||||||
fn create_data_control_source<'s, D, I>(
|
|
||||||
&self,
|
|
||||||
qh: &QueueHandle<D>,
|
|
||||||
mime_types: I,
|
|
||||||
) -> ZwlrDataControlSourceV1
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlSourceV1, DataControlSourceData> + 'static,
|
|
||||||
I: IntoIterator<Item = &'s str>,
|
|
||||||
{
|
|
||||||
let source =
|
|
||||||
self.create_data_control_source_with_data(qh, DataControlSourceData::default());
|
|
||||||
|
|
||||||
for mime in mime_types {
|
|
||||||
source.offer(mime.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
source
|
|
||||||
}
|
|
||||||
|
|
||||||
/// create a new data source for a given seat with some user data
|
|
||||||
pub fn create_data_control_source_with_data<D, U>(
|
|
||||||
&self,
|
|
||||||
qh: &QueueHandle<D>,
|
|
||||||
data: U,
|
|
||||||
) -> ZwlrDataControlSourceV1
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlSourceV1, U> + 'static,
|
|
||||||
U: DataControlSourceDataExt + 'static,
|
|
||||||
{
|
|
||||||
self.manager.create_data_source(qh, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// create a new data device for a given seat
|
|
||||||
pub fn get_data_device<D>(&self, qh: &QueueHandle<D>, seat: &WlSeat) -> DataControlDevice
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlDeviceV1, DataControlDeviceData> + 'static,
|
|
||||||
{
|
|
||||||
DataControlDevice {
|
|
||||||
device: self.get_data_control_device_with_data(
|
|
||||||
qh,
|
|
||||||
seat,
|
|
||||||
DataControlDeviceData::default(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// create a new data device for a given seat with some user data
|
|
||||||
pub fn get_data_control_device_with_data<D, U>(
|
|
||||||
&self,
|
|
||||||
qh: &QueueHandle<D>,
|
|
||||||
seat: &WlSeat,
|
|
||||||
data: U,
|
|
||||||
) -> ZwlrDataControlDeviceV1
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlDeviceV1, U> + 'static,
|
|
||||||
U: DataControlDeviceDataExt + 'static,
|
|
||||||
{
|
|
||||||
self.manager.get_data_device(seat, qh, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProvidesBoundGlobal<ZwlrDataControlManagerV1, 2> for DataControlDeviceManagerState {
|
pub struct DataControlDeviceHandler {
|
||||||
fn bound_global(&self) -> Result<ZwlrDataControlManagerV1, GlobalError> {
|
inner: Rc<RefCell<DataControlDeviceHandlerInner>>,
|
||||||
Ok(self.manager.clone())
|
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>>,
|
||||||
|
_seat_listener: SeatListener,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlDeviceHandler {
|
||||||
|
pub fn init<S>(seat_handler: &mut S) -> Self
|
||||||
|
where
|
||||||
|
S: SeatHandling,
|
||||||
|
{
|
||||||
|
let status_listeners = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
|
||||||
|
let inner = Rc::new(RefCell::new(DataControlDeviceHandlerInner::Pending {
|
||||||
|
seats: Vec::new(),
|
||||||
|
status_listeners: status_listeners.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let seat_inner = inner.clone();
|
||||||
|
let seat_listener = seat_handler.listen(move |seat, seat_data, _| {
|
||||||
|
if seat_data.defunct {
|
||||||
|
seat_inner.borrow_mut().remove_seat(&seat);
|
||||||
|
} else {
|
||||||
|
seat_inner.borrow_mut().new_seat(&seat);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
_seat_listener: seat_listener,
|
||||||
|
status_listeners,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Dispatch<ZwlrDataControlManagerV1, GlobalData, D> for DataControlDeviceManagerState
|
impl GlobalHandler<ZwlrDataControlManagerV1> for DataControlDeviceHandler {
|
||||||
where
|
fn created(
|
||||||
D: Dispatch<ZwlrDataControlManagerV1, GlobalData>,
|
&mut self,
|
||||||
{
|
registry: Attached<WlRegistry>,
|
||||||
fn event(
|
id: u32,
|
||||||
_state: &mut D,
|
version: u32,
|
||||||
_proxy: &ZwlrDataControlManagerV1,
|
_ddata: DispatchData,
|
||||||
_event: <ZwlrDataControlManagerV1 as Proxy>::Event,
|
|
||||||
_data: &GlobalData,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qhandle: &QueueHandle<D>,
|
|
||||||
) {
|
) {
|
||||||
unreachable!()
|
// data control manager is supported until version 2
|
||||||
|
let version = std::cmp::min(version, 2);
|
||||||
|
|
||||||
|
let manager = registry.bind::<ZwlrDataControlManagerV1>(version, id);
|
||||||
|
self.inner.borrow_mut().init_manager((*manager).clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self) -> Option<Attached<ZwlrDataControlManagerV1>> {
|
||||||
|
RefCell::borrow(&self.inner).get_manager()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DataControlDeviceStatusCallback =
|
||||||
|
dyn FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static;
|
||||||
|
|
||||||
|
/// Notifies the callbacks of an event on the data device
|
||||||
|
fn notify_status_listeners(
|
||||||
|
seat: &WlSeat,
|
||||||
|
event: &DataControlDeviceEvent,
|
||||||
|
mut ddata: DispatchData,
|
||||||
|
listeners: &RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>,
|
||||||
|
) {
|
||||||
|
listeners.borrow_mut().retain(|lst| {
|
||||||
|
rc::Weak::upgrade(lst).map_or(false, |cb| {
|
||||||
|
(cb.borrow_mut())(seat.clone(), event.clone(), ddata.reborrow());
|
||||||
|
true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DataControlDeviceStatusListener {
|
||||||
|
_cb: Rc<RefCell<DataControlDeviceStatusCallback>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DataControlDeviceHandling {
|
||||||
|
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
||||||
|
where
|
||||||
|
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static;
|
||||||
|
|
||||||
|
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
||||||
|
where
|
||||||
|
F: FnOnce(&DataControlDevice);
|
||||||
|
|
||||||
|
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
||||||
|
where
|
||||||
|
F: FnMut(String, WritePipe, DispatchData) + 'static;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlDeviceHandling for DataControlDeviceHandler {
|
||||||
|
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
||||||
|
where
|
||||||
|
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
|
||||||
|
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
|
||||||
|
DataControlDeviceStatusListener { _cb: rc }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
||||||
|
where
|
||||||
|
F: FnOnce(&DataControlDevice),
|
||||||
|
{
|
||||||
|
RefCell::borrow(&self.inner).with_device(seat, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
||||||
|
where
|
||||||
|
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
RefCell::borrow(&self.inner).create_source(mime_types, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen_to_devices<E, F>(env: &Environment<E>, f: F) -> DataControlDeviceStatusListener
|
||||||
|
where
|
||||||
|
E: DataControlDeviceHandling,
|
||||||
|
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
env.with_inner(move |inner| DataControlDeviceHandling::listen(inner, f))
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,28 +3,28 @@ pub mod manager;
|
|||||||
pub mod offer;
|
pub mod offer;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
|
|
||||||
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
use super::Env;
|
||||||
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
use crate::clients::wayland::DData;
|
||||||
use self::source::DataControlSourceHandler;
|
use crate::send;
|
||||||
use crate::clients::wayland::Environment;
|
use color_eyre::Report;
|
||||||
use crate::{lock, send};
|
use device::{DataControlDevice, DataControlDeviceEvent};
|
||||||
use device::DataControlDevice;
|
|
||||||
use glib::Bytes;
|
use glib::Bytes;
|
||||||
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
|
use manager::{DataControlDeviceHandling, DataControlDeviceStatusListener};
|
||||||
use nix::sys::epoll::{epoll_create, epoll_ctl, epoll_wait, EpollEvent, EpollFlags, EpollOp};
|
use smithay_client_toolkit::data_device::WritePipe;
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use smithay_client_toolkit::environment::Environment;
|
||||||
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||||
use std::cmp::min;
|
use smithay_client_toolkit::MissingGlobal;
|
||||||
use std::fmt::{Debug, Formatter};
|
use source::DataControlSource;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{ErrorKind, Read, Write};
|
use std::io;
|
||||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
use std::io::{Read, Write};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fs, io};
|
use std::time::UNIX_EPOCH;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
use wayland_client::{Connection, QueueHandle};
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;
|
use wayland_client::DispatchData;
|
||||||
|
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
|
||||||
@@ -34,11 +34,6 @@ fn get_id() -> usize {
|
|||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SelectionOfferItem {
|
|
||||||
offer: SelectionOffer,
|
|
||||||
token: Option<RegistrationToken>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq)]
|
#[derive(Debug, Clone, Eq)]
|
||||||
pub struct ClipboardItem {
|
pub struct ClipboardItem {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
@@ -52,27 +47,77 @@ impl PartialEq<Self> for ClipboardItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ClipboardValue {
|
pub enum ClipboardValue {
|
||||||
Text(String),
|
Text(String),
|
||||||
Image(Bytes),
|
Image(Bytes),
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for ClipboardValue {
|
impl DataControlDeviceHandling for Env {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
||||||
write!(
|
where
|
||||||
f,
|
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
||||||
"{}",
|
{
|
||||||
match self {
|
self.data_control_device.listen(f)
|
||||||
Self::Text(text) => text.clone(),
|
|
||||||
Self::Image(bytes) => {
|
|
||||||
format!("[{} Bytes]", bytes.len())
|
|
||||||
}
|
}
|
||||||
Self::Other => "[Unknown]".to_string(),
|
|
||||||
|
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
||||||
|
where
|
||||||
|
F: FnOnce(&DataControlDevice),
|
||||||
|
{
|
||||||
|
self.data_control_device.with_data_control_device(seat, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
||||||
|
where
|
||||||
|
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
self.data_control_device.create_source(mime_types, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_to_clipboard<E>(
|
||||||
|
env: &Environment<E>,
|
||||||
|
seat: &WlSeat,
|
||||||
|
item: &ClipboardItem,
|
||||||
|
) -> Result<(), MissingGlobal>
|
||||||
|
where
|
||||||
|
E: DataControlDeviceHandling,
|
||||||
|
{
|
||||||
|
debug!("Copying item with id {} [{}]", item.id, item.mime_type);
|
||||||
|
trace!("Copying: {item:?}");
|
||||||
|
|
||||||
|
let item = item.clone();
|
||||||
|
|
||||||
|
env.with_inner(|env| {
|
||||||
|
let mime_types = vec![INTERNAL_MIME_TYPE.to_string(), item.mime_type];
|
||||||
|
let source = env.create_source(mime_types, move |mime_type, mut pipe, _ddata| {
|
||||||
|
debug!(
|
||||||
|
"Triggering source callback for item with id {} [{}]",
|
||||||
|
item.id, mime_type
|
||||||
|
);
|
||||||
|
|
||||||
|
// FIXME: Not working for large (buffered) values in xwayland
|
||||||
|
let bytes = match &item.value {
|
||||||
|
ClipboardValue::Text(text) => text.as_bytes(),
|
||||||
|
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
||||||
|
ClipboardValue::Other => panic!(
|
||||||
|
"{:?}",
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Attempted to copy unsupported mime type",
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = pipe.write_all(bytes) {
|
||||||
|
error!("{err:?}");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
env.with_data_control_device(seat, |device| device.set_selection(&source))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -88,8 +133,11 @@ enum MimeTypeCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MimeType {
|
impl MimeType {
|
||||||
fn parse(mime_type: &str) -> Option<Self> {
|
fn parse(mime_types: &[String]) -> Option<Self> {
|
||||||
match mime_type.to_lowercase().as_str() {
|
mime_types
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_lowercase())
|
||||||
|
.find_map(|mime_type| match mime_type.as_str() {
|
||||||
"text"
|
"text"
|
||||||
| "string"
|
| "string"
|
||||||
| "utf8_string"
|
| "utf8_string"
|
||||||
@@ -98,42 +146,94 @@ impl MimeType {
|
|||||||
| "text/plain;charset=iso-8859-1"
|
| "text/plain;charset=iso-8859-1"
|
||||||
| "text/plain;charset=us-ascii"
|
| "text/plain;charset=us-ascii"
|
||||||
| "text/plain;charset=unicode" => Some(Self {
|
| "text/plain;charset=unicode" => Some(Self {
|
||||||
value: mime_type.to_string(),
|
value: mime_type,
|
||||||
category: MimeTypeCategory::Text,
|
category: MimeTypeCategory::Text,
|
||||||
}),
|
}),
|
||||||
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
||||||
| "image/x-bmp" | "image/icon" => Some(Self {
|
| "image/x-bmp" | "image/icon" => Some(Self {
|
||||||
value: mime_type.to_string(),
|
value: mime_type,
|
||||||
category: MimeTypeCategory::Image,
|
category: MimeTypeCategory::Image,
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_multiple(mime_types: &[String]) -> Option<Self> {
|
|
||||||
mime_types.iter().find_map(|mime| Self::parse(mime))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Environment {
|
pub fn receive_offer(
|
||||||
pub fn copy_to_clipboard(&mut self, item: Arc<ClipboardItem>, qh: &QueueHandle<Self>) {
|
event: DataControlDeviceEvent,
|
||||||
debug!("Copying item to clipboard: {item:?}");
|
handle: &LoopHandle<DData>,
|
||||||
|
tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||||
|
mut ddata: DispatchData,
|
||||||
|
) {
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Could not get epoch, system time is probably very wrong")
|
||||||
|
.as_nanos();
|
||||||
|
|
||||||
// TODO: Proper device tracking
|
let offer = event.0;
|
||||||
let device = self.data_control_devices.first();
|
|
||||||
if let Some(device) = device {
|
|
||||||
let source = self
|
|
||||||
.data_control_device_manager_state
|
|
||||||
.create_copy_paste_source(qh, [INTERNAL_MIME_TYPE, item.mime_type.as_str()]);
|
|
||||||
|
|
||||||
source.set_selection(&device.device);
|
let ddata = ddata
|
||||||
self.copy_paste_sources.push(source);
|
.get::<DData>()
|
||||||
|
.expect("Expected dispatch data to exist");
|
||||||
|
|
||||||
lock!(self.clipboard).replace(item);
|
let handle2 = handle.clone();
|
||||||
|
|
||||||
|
let res = offer.with_mime_types(|mime_types| {
|
||||||
|
debug!("Offer mime types: {mime_types:?}");
|
||||||
|
|
||||||
|
if mime_types.contains(&INTERNAL_MIME_TYPE.to_string()) {
|
||||||
|
debug!("Skipping value provided by bar");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mime_type = MimeType::parse(mime_types);
|
||||||
|
debug!("Detected mime type: {mime_type:?}");
|
||||||
|
|
||||||
|
match mime_type {
|
||||||
|
Some(mime_type) => {
|
||||||
|
debug!("[{timestamp}] Sending clipboard read request ({mime_type:?})");
|
||||||
|
let read_pipe = offer.receive(mime_type.value.clone())?;
|
||||||
|
let source = handle.insert_source(read_pipe, move |(), file, ddata| {
|
||||||
|
debug!(
|
||||||
|
"[{timestamp}] Reading clipboard contents ({:?})",
|
||||||
|
&mime_type.category
|
||||||
|
);
|
||||||
|
match read_file(&mime_type, file) {
|
||||||
|
Ok(item) => {
|
||||||
|
send!(tx, Arc::new(item));
|
||||||
|
}
|
||||||
|
Err(err) => error!("{err:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(src) = ddata.offer_tokens.remove(×tamp) {
|
||||||
|
handle2.remove(src);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ddata.offer_tokens.insert(timestamp, source);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// send an event so the clipboard module is aware it's changed
|
||||||
|
send!(
|
||||||
|
tx,
|
||||||
|
Arc::new(ClipboardItem {
|
||||||
|
id: usize::MAX,
|
||||||
|
mime_type: String::new(),
|
||||||
|
value: ClipboardValue::Other
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
Ok::<(), Report>(())
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
error!("{err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
||||||
let value = match mime_type.category {
|
let value = match mime_type.category {
|
||||||
MimeTypeCategory::Text => {
|
MimeTypeCategory::Text => {
|
||||||
let mut txt = String::new();
|
let mut txt = String::new();
|
||||||
@@ -155,241 +255,4 @@ impl Environment {
|
|||||||
value,
|
value,
|
||||||
mime_type: mime_type.value.clone(),
|
mime_type: mime_type.value.clone(),
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlDeviceHandler for Environment {
|
|
||||||
fn selection(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
data_device: DataControlDevice,
|
|
||||||
) {
|
|
||||||
debug!("Handler received selection event");
|
|
||||||
|
|
||||||
let mime_types = data_device.selection_mime_types();
|
|
||||||
|
|
||||||
if mime_types.contains(&INTERNAL_MIME_TYPE.to_string()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(offer) = data_device.selection_offer() {
|
|
||||||
self.selection_offers
|
|
||||||
.push(SelectionOfferItem { offer, token: None });
|
|
||||||
|
|
||||||
let cur_offer = self
|
|
||||||
.selection_offers
|
|
||||||
.last_mut()
|
|
||||||
.expect("Failed to get current offer");
|
|
||||||
|
|
||||||
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
|
|
||||||
lock!(self.clipboard).take();
|
|
||||||
// send an event so the clipboard module is aware it's changed
|
|
||||||
send!(
|
|
||||||
self.clipboard_tx,
|
|
||||||
Arc::new(ClipboardItem {
|
|
||||||
id: usize::MAX,
|
|
||||||
mime_type: String::new(),
|
|
||||||
value: ClipboardValue::Other
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(read_pipe) = cur_offer.offer.receive(mime_type.value.clone()) {
|
|
||||||
let offer_clone = cur_offer.offer.clone();
|
|
||||||
|
|
||||||
let tx = self.clipboard_tx.clone();
|
|
||||||
let clipboard = self.clipboard.clone();
|
|
||||||
|
|
||||||
let token = self
|
|
||||||
.loop_handle
|
|
||||||
.insert_source(read_pipe, move |_, file, state| {
|
|
||||||
let item = state
|
|
||||||
.selection_offers
|
|
||||||
.iter()
|
|
||||||
.position(|o| o.offer == offer_clone)
|
|
||||||
.map(|p| state.selection_offers.remove(p))
|
|
||||||
.expect("Failed to find selection offer item");
|
|
||||||
|
|
||||||
match Self::read_file(&mime_type, file) {
|
|
||||||
Ok(item) => {
|
|
||||||
let item = Arc::new(item);
|
|
||||||
lock!(clipboard).replace(item.clone());
|
|
||||||
send!(tx, item);
|
|
||||||
}
|
|
||||||
Err(err) => error!("{err:?}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
state
|
|
||||||
.loop_handle
|
|
||||||
.remove(item.token.expect("Missing item token"));
|
|
||||||
});
|
|
||||||
|
|
||||||
match token {
|
|
||||||
Ok(token) => {
|
|
||||||
cur_offer.token.replace(token);
|
|
||||||
}
|
|
||||||
Err(err) => error!("{err:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlOfferHandler for Environment {
|
|
||||||
fn offer(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_offer: &mut DataControlDeviceOffer,
|
|
||||||
_mime_type: String,
|
|
||||||
) {
|
|
||||||
debug!("Handler received offer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlSourceHandler for Environment {
|
|
||||||
fn accept_mime(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_source: &ZwlrDataControlSourceV1,
|
|
||||||
mime: Option<String>,
|
|
||||||
) {
|
|
||||||
debug!("Accepted mime type: {mime:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_request(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
source: &ZwlrDataControlSourceV1,
|
|
||||||
mime: String,
|
|
||||||
write_pipe: WritePipe,
|
|
||||||
) {
|
|
||||||
debug!("Handler received source send request event ({mime})");
|
|
||||||
|
|
||||||
if let Some(item) = lock!(self.clipboard).clone() {
|
|
||||||
let fd = OwnedFd::from(write_pipe);
|
|
||||||
if self
|
|
||||||
.copy_paste_sources
|
|
||||||
.iter_mut()
|
|
||||||
.any(|s| s.inner() == source && MimeType::parse(&mime).is_some())
|
|
||||||
{
|
|
||||||
trace!("Source found, writing to file");
|
|
||||||
|
|
||||||
let mut bytes = match &item.value {
|
|
||||||
ClipboardValue::Text(text) => text.as_bytes(),
|
|
||||||
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
|
||||||
ClipboardValue::Other => panic!(
|
|
||||||
"{:?}",
|
|
||||||
io::Error::new(ErrorKind::Other, "Attempted to copy unsupported mime type",)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pipe_size = set_pipe_size(fd.as_raw_fd(), bytes.len())
|
|
||||||
.expect("Failed to increase pipe size");
|
|
||||||
let mut file = File::from(fd.try_clone().expect("Failed to clone fd"));
|
|
||||||
|
|
||||||
trace!("Num bytes: {}", bytes.len());
|
|
||||||
|
|
||||||
let mut events = (0..16).map(|_| EpollEvent::empty()).collect::<Vec<_>>();
|
|
||||||
let mut epoll_event = EpollEvent::new(EpollFlags::EPOLLOUT, 0);
|
|
||||||
|
|
||||||
let epoll_fd = epoll_create().unwrap();
|
|
||||||
epoll_ctl(
|
|
||||||
epoll_fd,
|
|
||||||
EpollOp::EpollCtlAdd,
|
|
||||||
fd.as_raw_fd(),
|
|
||||||
&mut epoll_event,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
while !bytes.is_empty() {
|
|
||||||
let chunk = &bytes[..min(pipe_size as usize, bytes.len())];
|
|
||||||
|
|
||||||
trace!("Writing {} bytes ({} remain)", chunk.len(), bytes.len());
|
|
||||||
|
|
||||||
epoll_wait(epoll_fd, &mut events, 100).expect("Failed to wait to epoll");
|
|
||||||
|
|
||||||
match file.write(chunk) {
|
|
||||||
Ok(_) => bytes = &bytes[chunk.len()..],
|
|
||||||
Err(err) => {
|
|
||||||
error!("{err:?}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for chunk in bytes.chunks(pipe_size as usize) {
|
|
||||||
// trace!("Writing chunk");
|
|
||||||
// file.write(chunk).expect("Failed to write chunk to buffer");
|
|
||||||
// file.flush().expect("Failed to flush to file");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// match file.write_vectored(&bytes.chunks(pipe_size as usize).map(IoSlice::new).collect::<Vec<_>>()) {
|
|
||||||
// Ok(_) => debug!("Copied item"),
|
|
||||||
// Err(err) => error!("{err:?}"),
|
|
||||||
// }
|
|
||||||
|
|
||||||
// match file.write_all(bytes) {
|
|
||||||
// Ok(_) => debug!("Copied item"),
|
|
||||||
// Err(err) => error!("{err:?}"),
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
error!("Failed to find source");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cancelled(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
source: &ZwlrDataControlSourceV1,
|
|
||||||
) {
|
|
||||||
debug!("Handler received source cancelled event");
|
|
||||||
|
|
||||||
self.copy_paste_sources
|
|
||||||
.iter()
|
|
||||||
.position(|s| s.inner() == source)
|
|
||||||
.map(|pos| self.copy_paste_sources.remove(pos));
|
|
||||||
source.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to increase the fd pipe size to the requested number of bytes.
|
|
||||||
/// The kernel will automatically round this up to the nearest page size.
|
|
||||||
/// If the requested size is larger than the kernel max (normally 1MB),
|
|
||||||
/// it will be clamped at this.
|
|
||||||
///
|
|
||||||
/// Returns the new size if succeeded
|
|
||||||
fn set_pipe_size(fd: RawFd, size: usize) -> io::Result<i32> {
|
|
||||||
// clamp size at kernel max
|
|
||||||
let max_pipe_size = fs::read_to_string("/proc/sys/fs/pipe-max-size")
|
|
||||||
.expect("Failed to find pipe-max-size virtual kernel file")
|
|
||||||
.trim()
|
|
||||||
.parse::<usize>()
|
|
||||||
.expect("Failed to parse pipe-max-size contents");
|
|
||||||
|
|
||||||
let size = min(size, max_pipe_size);
|
|
||||||
|
|
||||||
let curr_size = fcntl(fd, F_GETPIPE_SZ)? as usize;
|
|
||||||
|
|
||||||
trace!("Current pipe size: {curr_size}");
|
|
||||||
|
|
||||||
let new_size = if size > curr_size {
|
|
||||||
trace!("Requesting pipe size increase to (at least): {size}");
|
|
||||||
let res = fcntl(fd, F_SETPIPE_SZ(size as i32))?;
|
|
||||||
trace!("New pipe size: {res}");
|
|
||||||
if res < size as i32 {
|
|
||||||
return Err(io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
res
|
|
||||||
} else {
|
|
||||||
size as i32
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(new_size)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,183 +1,74 @@
|
|||||||
use super::manager::DataControlDeviceManagerState;
|
|
||||||
use crate::lock;
|
use crate::lock;
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::unistd::{close, pipe2};
|
use nix::unistd::{close, pipe2};
|
||||||
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
|
use smithay_client_toolkit::data_device::ReadPipe;
|
||||||
use smithay_client_toolkit::data_device_manager::ReadPipe;
|
use std::io;
|
||||||
use std::ops::DerefMut;
|
|
||||||
use std::os::fd::FromRawFd;
|
use std::os::fd::FromRawFd;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::{debug, warn};
|
use tracing::warn;
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
use wayland_client::Main;
|
||||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{
|
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_offer_v1::{
|
||||||
Event, ZwlrDataControlOfferV1,
|
Event, ZwlrDataControlOfferV1,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UndeterminedOffer {
|
struct Inner {
|
||||||
pub(crate) data_offer: Option<ZwlrDataControlOfferV1>,
|
mime_types: Vec<String>,
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for UndeterminedOffer {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.data_offer == other.data_offer
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SelectionOffer {
|
pub struct DataControlOffer {
|
||||||
pub data_offer: ZwlrDataControlOfferV1,
|
inner: Arc<Mutex<Inner>>,
|
||||||
|
pub(crate) offer: ZwlrDataControlOfferV1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for SelectionOffer {
|
impl DataControlOffer {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
pub(crate) fn new(offer: &Main<ZwlrDataControlOfferV1>) -> Self {
|
||||||
self.data_offer == other.data_offer
|
let inner = Arc::new(Mutex::new(Inner {
|
||||||
}
|
mime_types: Vec::new(),
|
||||||
}
|
}));
|
||||||
|
|
||||||
impl SelectionOffer {
|
{
|
||||||
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
|
let inner = inner.clone();
|
||||||
receive(&self.data_offer, mime_type).map_err(DataOfferError::Io)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum DataControlDeviceOffer {
|
|
||||||
Selection(SelectionOffer),
|
|
||||||
Undetermined(UndeterminedOffer),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DataControlDeviceOffer {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Undetermined(UndeterminedOffer { data_offer: None })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct DataControlOfferData {
|
|
||||||
pub(crate) inner: Arc<Mutex<DataControlDeviceOfferInner>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct DataControlDeviceOfferInner {
|
|
||||||
pub(crate) offer: DataControlDeviceOffer,
|
|
||||||
pub(crate) mime_types: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlOfferData {
|
|
||||||
pub(crate) fn push_mime_type(&self, mime_type: String) {
|
|
||||||
lock!(self.inner).mime_types.push(mime_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn to_selection_offer(&self) {
|
|
||||||
let mut inner = lock!(self.inner);
|
|
||||||
match &mut inner.deref_mut().offer {
|
|
||||||
DataControlDeviceOffer::Selection(_) => {}
|
|
||||||
DataControlDeviceOffer::Undetermined(o) => {
|
|
||||||
inner.offer = DataControlDeviceOffer::Selection(SelectionOffer {
|
|
||||||
data_offer: o.data_offer.clone().expect("Missing current data offer"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn init_undetermined_offer(&self, offer: &ZwlrDataControlOfferV1) {
|
|
||||||
let mut inner = lock!(self.inner);
|
|
||||||
match &mut inner.deref_mut().offer {
|
|
||||||
DataControlDeviceOffer::Selection(_) => {
|
|
||||||
inner.offer = DataControlDeviceOffer::Undetermined(UndeterminedOffer {
|
|
||||||
data_offer: Some(offer.clone()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
DataControlDeviceOffer::Undetermined(o) => {
|
|
||||||
o.data_offer = Some(offer.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DataControlOfferDataExt {
|
|
||||||
fn data_control_offer_data(&self) -> &DataControlOfferData;
|
|
||||||
fn mime_types(&self) -> Vec<String>;
|
|
||||||
fn as_selection_offer(&self) -> Option<SelectionOffer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlOfferDataExt for DataControlOfferData {
|
|
||||||
fn data_control_offer_data(&self) -> &DataControlOfferData {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mime_types(&self) -> Vec<String> {
|
|
||||||
lock!(self.inner).mime_types.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_selection_offer(&self) -> Option<SelectionOffer> {
|
|
||||||
match &lock!(self.inner).offer {
|
|
||||||
DataControlDeviceOffer::Selection(o) => Some(o.clone()),
|
|
||||||
DataControlDeviceOffer::Undetermined(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler trait for `DataOffer` events.
|
|
||||||
///
|
|
||||||
/// The functions defined in this trait are called as `DataOffer` events are received from the compositor.
|
|
||||||
pub trait DataControlOfferHandler: Sized {
|
|
||||||
// Called for each mime type the data offer advertises.
|
|
||||||
fn offer(
|
|
||||||
&mut self,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<Self>,
|
|
||||||
offer: &mut DataControlDeviceOffer,
|
|
||||||
mime_type: String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, U> Dispatch<ZwlrDataControlOfferV1, U, D> for DataControlDeviceManagerState
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlOfferV1, U> + DataControlOfferHandler,
|
|
||||||
U: DataControlOfferDataExt,
|
|
||||||
{
|
|
||||||
fn event(
|
|
||||||
state: &mut D,
|
|
||||||
_offer: &ZwlrDataControlOfferV1,
|
|
||||||
event: <ZwlrDataControlOfferV1 as Proxy>::Event,
|
|
||||||
data: &U,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<D>,
|
|
||||||
) {
|
|
||||||
let data = data.data_control_offer_data();
|
|
||||||
|
|
||||||
|
offer.quick_assign(move |_, event, _| {
|
||||||
|
let mut inner = lock!(inner);
|
||||||
if let Event::Offer { mime_type } = event {
|
if let Event::Offer { mime_type } = event {
|
||||||
debug!("Adding new offer with type '{mime_type}'");
|
inner.mime_types.push(mime_type);
|
||||||
data.push_mime_type(mime_type.clone());
|
|
||||||
state.offer(conn, qh, &mut lock!(data.inner).offer, mime_type);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Request to receive the data of a given mime type.
|
Self {
|
||||||
///
|
offer: offer.detach(),
|
||||||
/// You can do this several times, as a reaction to motion of
|
inner,
|
||||||
/// the dnd cursor, or to inspect the data in order to choose your
|
}
|
||||||
/// response.
|
}
|
||||||
///
|
|
||||||
/// Note that you should *not* read the contents right away in a
|
pub fn with_mime_types<F, T>(&self, f: F) -> T
|
||||||
/// blocking way, as you may deadlock your application doing so.
|
where
|
||||||
/// At least make sure you flush your events to the server before
|
F: FnOnce(&[String]) -> T,
|
||||||
/// doing so.
|
{
|
||||||
///
|
let inner = lock!(self.inner);
|
||||||
/// Fails if too many file descriptors were already open and a pipe
|
f(&inner.mime_types)
|
||||||
/// could not be created.
|
}
|
||||||
pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result<ReadPipe> {
|
|
||||||
|
pub fn receive(&self, mime_type: String) -> io::Result<ReadPipe> {
|
||||||
// create a pipe
|
// create a pipe
|
||||||
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
||||||
|
|
||||||
offer.receive(mime_type, writefd);
|
self.offer.receive(mime_type, writefd);
|
||||||
|
|
||||||
if let Err(err) = close(writefd) {
|
if let Err(err) = close(writefd) {
|
||||||
warn!("Failed to close write pipe: {}", err);
|
warn!("Failed to close write pipe: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
|
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DataControlOffer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.offer.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,54 @@
|
|||||||
use super::device::DataControlDevice;
|
use smithay_client_toolkit::data_device::WritePipe;
|
||||||
use super::manager::DataControlDeviceManagerState;
|
use std::os::fd::FromRawFd;
|
||||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
use wayland_client::{Attached, DispatchData};
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
use wayland_protocols::wlr::unstable::data_control::v1::client::{
|
||||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
|
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
||||||
Event, ZwlrDataControlSourceV1,
|
zwlr_data_control_source_v1::{Event, ZwlrDataControlSourceV1},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
fn data_control_source_impl<F>(
|
||||||
pub struct DataControlSourceData {}
|
|
||||||
|
|
||||||
pub trait DataControlSourceDataExt: Send + Sync {
|
|
||||||
fn data_source_data(&self) -> &DataControlSourceData;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlSourceDataExt for DataControlSourceData {
|
|
||||||
fn data_source_data(&self) -> &DataControlSourceData {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler trait for `DataSource` events.
|
|
||||||
///
|
|
||||||
/// The functions defined in this trait are called as `DataSource` events are received from the compositor.
|
|
||||||
pub trait DataControlSourceHandler: Sized {
|
|
||||||
/// This may be called multiple times, once for each accepted mime type from the destination, if any.
|
|
||||||
fn accept_mime(
|
|
||||||
&mut self,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<Self>,
|
|
||||||
source: &ZwlrDataControlSourceV1,
|
source: &ZwlrDataControlSourceV1,
|
||||||
mime: Option<String>,
|
event: Event,
|
||||||
);
|
implem: &mut F,
|
||||||
|
ddata: DispatchData,
|
||||||
/// The client has requested the data for this source to be sent.
|
) where
|
||||||
/// Send the data, then close the fd.
|
F: FnMut(String, WritePipe, DispatchData),
|
||||||
fn send_request(
|
|
||||||
&mut self,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<Self>,
|
|
||||||
source: &ZwlrDataControlSourceV1,
|
|
||||||
mime: String,
|
|
||||||
fd: WritePipe,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// The data source is no longer valid
|
|
||||||
/// Cleanup & destroy this resource
|
|
||||||
fn cancelled(
|
|
||||||
&mut self,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<Self>,
|
|
||||||
source: &ZwlrDataControlSourceV1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, U> Dispatch<ZwlrDataControlSourceV1, U, D> for DataControlDeviceManagerState
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrDataControlSourceV1, U> + DataControlSourceHandler,
|
|
||||||
U: DataControlSourceDataExt,
|
|
||||||
{
|
{
|
||||||
fn event(
|
|
||||||
state: &mut D,
|
|
||||||
source: &ZwlrDataControlSourceV1,
|
|
||||||
event: <ZwlrDataControlSourceV1 as Proxy>::Event,
|
|
||||||
_data: &U,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<D>,
|
|
||||||
) {
|
|
||||||
match event {
|
match event {
|
||||||
Event::Send { mime_type, fd } => {
|
Event::Send { mime_type, fd } => {
|
||||||
state.send_request(conn, qh, source, mime_type, fd.into());
|
let pipe = unsafe { FromRawFd::from_raw_fd(fd) };
|
||||||
}
|
implem(mime_type, pipe, ddata);
|
||||||
Event::Cancelled => {
|
|
||||||
state.cancelled(conn, qh, source);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
Event::Cancelled => source.destroy(),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
pub struct DataControlSource {
|
||||||
pub struct CopyPasteSource {
|
pub(crate) source: ZwlrDataControlSourceV1,
|
||||||
pub(crate) inner: ZwlrDataControlSourceV1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CopyPasteSource {
|
impl DataControlSource {
|
||||||
/// Set the selection of the provided data device as a response to the event with with provided serial.
|
pub fn new<F>(
|
||||||
pub fn set_selection(&self, device: &DataControlDevice) {
|
manager: &Attached<ZwlrDataControlManagerV1>,
|
||||||
device.device.set_selection(Some(&self.inner));
|
mime_types: Vec<String>,
|
||||||
|
mut callback: F,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
let source = manager.create_data_source();
|
||||||
|
|
||||||
|
source.quick_assign(move |source, evt, ddata| {
|
||||||
|
data_control_source_impl(&source, evt, &mut callback, ddata);
|
||||||
|
});
|
||||||
|
|
||||||
|
for mime_type in mime_types {
|
||||||
|
source.offer(mime_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn inner(&self) -> &ZwlrDataControlSourceV1 {
|
Self {
|
||||||
&self.inner
|
source: source.detach(),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for CopyPasteSource {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.inner.destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
use super::manager::ToplevelManagerState;
|
|
||||||
use crate::lock;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
use wayland_client::protocol::wl_output::WlOutput;
|
use wayland_client::{DispatchData, Main};
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1};
|
||||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
use crate::write_lock;
|
||||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{
|
|
||||||
Event, ZwlrForeignToplevelHandleV1,
|
const STATE_ACTIVE: u32 = 2;
|
||||||
};
|
const STATE_FULLSCREEN: u32 = 3;
|
||||||
|
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
|
||||||
@@ -17,119 +15,69 @@ fn get_id() -> usize {
|
|||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ToplevelHandle {
|
|
||||||
pub handle: ZwlrForeignToplevelHandleV1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for ToplevelHandle {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.handle == other.handle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToplevelHandle {
|
|
||||||
pub fn info(&self) -> Option<ToplevelInfo> {
|
|
||||||
trace!("Retrieving handle info");
|
|
||||||
|
|
||||||
let data = self.handle.data::<ToplevelHandleData>()?;
|
|
||||||
data.info()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus(&self, seat: &WlSeat) {
|
|
||||||
trace!("Activating handle");
|
|
||||||
self.handle.activate(seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct ToplevelHandleData {
|
|
||||||
pub inner: Arc<Mutex<ToplevelHandleDataInner>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToplevelHandleData {
|
|
||||||
fn info(&self) -> Option<ToplevelInfo> {
|
|
||||||
lock!(self.inner).current_info.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct ToplevelHandleDataInner {
|
|
||||||
initial_done: bool,
|
|
||||||
output: Option<WlOutput>,
|
|
||||||
|
|
||||||
current_info: Option<ToplevelInfo>,
|
|
||||||
pending_info: ToplevelInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ToplevelInfo {
|
pub struct ToplevelInfo {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub app_id: String,
|
pub app_id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
pub active: bool,
|
||||||
pub fullscreen: bool,
|
pub fullscreen: bool,
|
||||||
pub focused: bool,
|
|
||||||
|
ready: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ToplevelInfo {
|
impl ToplevelInfo {
|
||||||
fn default() -> Self {
|
fn new() -> Self {
|
||||||
|
let id = get_id();
|
||||||
Self {
|
Self {
|
||||||
id: get_id(),
|
id,
|
||||||
app_id: String::new(),
|
..Default::default()
|
||||||
title: String::new(),
|
|
||||||
fullscreen: false,
|
|
||||||
focused: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToplevelHandleDataExt {
|
pub struct Toplevel;
|
||||||
fn toplevel_handle_data(&self) -> &ToplevelHandleData;
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ToplevelEvent {
|
||||||
|
pub toplevel: ToplevelInfo,
|
||||||
|
pub change: ToplevelChange,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelHandleDataExt for ToplevelHandleData {
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
fn toplevel_handle_data(&self) -> &ToplevelHandleData {
|
pub enum ToplevelChange {
|
||||||
self
|
New,
|
||||||
}
|
Close,
|
||||||
|
Title(String),
|
||||||
|
Focus(bool),
|
||||||
|
Fullscreen(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToplevelHandleHandler: Sized {
|
fn toplevel_implem<F>(event: Event, info: &mut ToplevelInfo, implem: &mut F, ddata: DispatchData)
|
||||||
fn new_handle(&mut self, conn: &Connection, qh: &QueueHandle<Self>, handle: ToplevelHandle);
|
|
||||||
|
|
||||||
fn update_handle(&mut self, conn: &Connection, qh: &QueueHandle<Self>, handle: ToplevelHandle);
|
|
||||||
|
|
||||||
fn remove_handle(&mut self, conn: &Connection, qh: &QueueHandle<Self>, handle: ToplevelHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, U> Dispatch<ZwlrForeignToplevelHandleV1, U, D> for ToplevelManagerState
|
|
||||||
where
|
where
|
||||||
D: Dispatch<ZwlrForeignToplevelHandleV1, U> + ToplevelHandleHandler,
|
F: FnMut(ToplevelEvent, DispatchData),
|
||||||
U: ToplevelHandleDataExt,
|
|
||||||
{
|
{
|
||||||
fn event(
|
trace!("event: {event:?} (info: {info:?})");
|
||||||
state: &mut D,
|
|
||||||
handle: &ZwlrForeignToplevelHandleV1,
|
|
||||||
event: Event,
|
|
||||||
data: &U,
|
|
||||||
conn: &Connection,
|
|
||||||
qh: &QueueHandle<D>,
|
|
||||||
) {
|
|
||||||
const STATE_ACTIVE: u32 = 2;
|
|
||||||
const STATE_FULLSCREEN: u32 = 3;
|
|
||||||
|
|
||||||
let data = data.toplevel_handle_data();
|
let change = match event {
|
||||||
|
Event::AppId { app_id } => {
|
||||||
trace!("Processing handle event: {event:?}");
|
info.app_id = app_id;
|
||||||
|
None
|
||||||
match event {
|
}
|
||||||
Event::Title { title } => {
|
Event::Title { title } => {
|
||||||
lock!(data.inner).pending_info.title = title;
|
info.title = title.clone();
|
||||||
|
|
||||||
|
if info.ready {
|
||||||
|
Some(ToplevelChange::Title(title))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::AppId { app_id } => lock!(data.inner).pending_info.app_id = app_id,
|
|
||||||
Event::State { state } => {
|
Event::State { state } => {
|
||||||
// state is received as a `Vec<u8>` where every 4 bytes make up a `u32`
|
// state is received as a `Vec<u8>` where every 4 bytes make up a `u32`
|
||||||
// the u32 then represents a value in the `State` enum.
|
// the u32 then represents a value in the `State` enum.
|
||||||
assert_eq!(state.len() % 4, 0);
|
assert_eq!(state.len() % 4, 0);
|
||||||
|
|
||||||
let state = (0..state.len() / 4)
|
let state = (0..state.len() / 4)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let slice: [u8; 4] = state[i * 4..i * 4 + 4]
|
let slice: [u8; 4] = state[i * 4..i * 4 + 4]
|
||||||
@@ -139,46 +87,66 @@ where
|
|||||||
})
|
})
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
lock!(data.inner).pending_info.focused = state.contains(&STATE_ACTIVE);
|
let new_active = state.contains(&STATE_ACTIVE);
|
||||||
lock!(data.inner).pending_info.fullscreen = state.contains(&STATE_FULLSCREEN);
|
let new_fullscreen = state.contains(&STATE_FULLSCREEN);
|
||||||
}
|
|
||||||
Event::OutputEnter { output } => lock!(data.inner).output = Some(output),
|
|
||||||
Event::OutputLeave { output: _ } => lock!(data.inner).output = None,
|
|
||||||
Event::Closed => state.remove_handle(
|
|
||||||
conn,
|
|
||||||
qh,
|
|
||||||
ToplevelHandle {
|
|
||||||
handle: handle.clone(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Event::Done => {
|
|
||||||
{
|
|
||||||
let pending_info = lock!(data.inner).pending_info.clone();
|
|
||||||
lock!(data.inner).current_info = Some(pending_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !lock!(data.inner).initial_done {
|
let change = if info.ready && new_active != info.active {
|
||||||
lock!(data.inner).initial_done = true;
|
Some(ToplevelChange::Focus(new_active))
|
||||||
state.new_handle(
|
} else if info.ready && new_fullscreen != info.fullscreen {
|
||||||
conn,
|
Some(ToplevelChange::Fullscreen(new_fullscreen))
|
||||||
qh,
|
|
||||||
ToplevelHandle {
|
|
||||||
handle: handle.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
state.update_handle(
|
None
|
||||||
conn,
|
};
|
||||||
qh,
|
|
||||||
ToplevelHandle {
|
info.active = new_active;
|
||||||
handle: handle.clone(),
|
info.fullscreen = new_fullscreen;
|
||||||
},
|
|
||||||
);
|
change
|
||||||
|
}
|
||||||
|
Event::Closed => {
|
||||||
|
if info.ready {
|
||||||
|
Some(ToplevelChange::Close)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
Event::OutputEnter { output: _ }
|
||||||
|
| Event::OutputLeave { output: _ }
|
||||||
|
| Event::Parent { parent: _ } => None,
|
||||||
|
Event::Done => {
|
||||||
|
if info.ready || info.app_id.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
info.ready = true;
|
||||||
|
Some(ToplevelChange::New)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("Event processed");
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(change) = change {
|
||||||
|
let event = ToplevelEvent {
|
||||||
|
change,
|
||||||
|
toplevel: info.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
implem(event, ddata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Toplevel {
|
||||||
|
pub fn init<F>(handle: &Main<ZwlrForeignToplevelHandleV1>, mut callback: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(ToplevelEvent, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
let inner = Arc::new(RwLock::new(ToplevelInfo::new()));
|
||||||
|
|
||||||
|
handle.quick_assign(move |_handle, event, ddata| {
|
||||||
|
let mut inner = write_lock!(inner);
|
||||||
|
toplevel_implem(event, &mut inner, &mut callback, ddata);
|
||||||
|
});
|
||||||
|
|
||||||
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,163 @@
|
|||||||
use super::handle::{ToplevelHandleData, ToplevelHandleDataExt, ToplevelHandleHandler};
|
use super::handle::{Toplevel, ToplevelEvent};
|
||||||
use smithay_client_toolkit::error::GlobalError;
|
use crate::wayland::LazyGlobal;
|
||||||
use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal};
|
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
|
||||||
use std::marker::PhantomData;
|
use std::cell::RefCell;
|
||||||
use tracing::{debug, warn};
|
use std::rc::{self, Rc};
|
||||||
use wayland_client::globals::{BindError, GlobalList};
|
use tracing::warn;
|
||||||
use wayland_client::{event_created_child, Connection, Dispatch, QueueHandle};
|
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
use wayland_client::{Attached, DispatchData};
|
||||||
|
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
|
||||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||||
zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1},
|
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ToplevelManagerState<V = ToplevelHandleData> {
|
struct ToplevelHandlerInner {
|
||||||
manager: ZwlrForeignToplevelManagerV1,
|
manager: LazyGlobal<ZwlrForeignToplevelManagerV1>,
|
||||||
_phantom: PhantomData<V>,
|
registry: Option<Attached<WlRegistry>>,
|
||||||
|
toplevels: Vec<Toplevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelManagerState {
|
impl ToplevelHandlerInner {
|
||||||
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
|
const fn new() -> Self {
|
||||||
where
|
let toplevels = vec![];
|
||||||
State: Dispatch<ZwlrForeignToplevelManagerV1, GlobalData, State> + 'static,
|
|
||||||
{
|
Self {
|
||||||
let manager = globals.bind(qh, 1..=3, GlobalData)?;
|
registry: None,
|
||||||
debug!("Bound to ZwlForeignToplevelManagerV1 global");
|
manager: LazyGlobal::Unknown,
|
||||||
Ok(Self {
|
toplevels,
|
||||||
manager,
|
}
|
||||||
_phantom: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToplevelManagerHandler: Sized {
|
pub struct ToplevelHandler {
|
||||||
/// Advertises a new toplevel.
|
inner: Rc<RefCell<ToplevelHandlerInner>>,
|
||||||
fn toplevel(
|
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<ToplevelStatusCallback>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToplevelHandler {
|
||||||
|
pub fn init() -> Self {
|
||||||
|
let inner = Rc::new(RefCell::new(ToplevelHandlerInner::new()));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
status_listeners: Rc::new(RefCell::new(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalHandler<ZwlrForeignToplevelManagerV1> for ToplevelHandler {
|
||||||
|
fn created(
|
||||||
&mut self,
|
&mut self,
|
||||||
conn: &Connection,
|
registry: Attached<WlRegistry>,
|
||||||
qh: &QueueHandle<Self>,
|
id: u32,
|
||||||
manager: ToplevelManagerState,
|
version: u32,
|
||||||
);
|
_ddata: DispatchData,
|
||||||
}
|
|
||||||
|
|
||||||
impl ProvidesBoundGlobal<ZwlrForeignToplevelManagerV1, 3> for ToplevelManagerState {
|
|
||||||
fn bound_global(&self) -> Result<ZwlrForeignToplevelManagerV1, GlobalError> {
|
|
||||||
Ok(self.manager.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D, V> Dispatch<ZwlrForeignToplevelManagerV1, GlobalData, D> for ToplevelManagerState<V>
|
|
||||||
where
|
|
||||||
D: Dispatch<ZwlrForeignToplevelManagerV1, GlobalData>
|
|
||||||
+ Dispatch<ZwlrForeignToplevelHandleV1, V>
|
|
||||||
+ ToplevelManagerHandler
|
|
||||||
+ ToplevelHandleHandler
|
|
||||||
+ 'static,
|
|
||||||
V: ToplevelHandleDataExt + Default + 'static + Send + Sync,
|
|
||||||
{
|
|
||||||
event_created_child!(D, ZwlrForeignToplevelManagerV1, [
|
|
||||||
0 => (ZwlrForeignToplevelHandleV1, V::default())
|
|
||||||
]);
|
|
||||||
|
|
||||||
fn event(
|
|
||||||
state: &mut D,
|
|
||||||
toplevel_manager: &ZwlrForeignToplevelManagerV1,
|
|
||||||
event: Event,
|
|
||||||
_data: &GlobalData,
|
|
||||||
conn: &Connection,
|
|
||||||
qhandle: &QueueHandle<D>,
|
|
||||||
) {
|
) {
|
||||||
match event {
|
let mut inner = RefCell::borrow_mut(&self.inner);
|
||||||
Event::Toplevel { toplevel: _ } => {
|
if inner.registry.is_none() {
|
||||||
state.toplevel(
|
inner.registry = Some(registry);
|
||||||
conn,
|
}
|
||||||
qhandle,
|
if matches!(inner.manager, LazyGlobal::Unknown) {
|
||||||
ToplevelManagerState {
|
inner.manager = LazyGlobal::Seen { id, version }
|
||||||
manager: toplevel_manager.clone(),
|
} else {
|
||||||
_phantom: PhantomData,
|
warn!(
|
||||||
},
|
"Compositor advertised zwlr_foreign_toplevel_manager_v1 multiple times, ignoring."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::Finished => {
|
|
||||||
warn!("Foreign toplevel manager is no longer valid, but has not been dropped by client. This could cause window tracking issues.");
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
|
fn get(&self) -> Option<Attached<ZwlrForeignToplevelManagerV1>> {
|
||||||
|
let mut inner = RefCell::borrow_mut(&self.inner);
|
||||||
|
|
||||||
|
match inner.manager {
|
||||||
|
LazyGlobal::Bound(ref mgr) => Some(mgr.clone()),
|
||||||
|
LazyGlobal::Unknown => None,
|
||||||
|
LazyGlobal::Seen { id, version } => {
|
||||||
|
let registry = inner.registry.as_ref().expect("Failed to get registry");
|
||||||
|
// current max protocol version = 3
|
||||||
|
let version = std::cmp::min(version, 3);
|
||||||
|
let manager = registry.bind::<ZwlrForeignToplevelManagerV1>(version, id);
|
||||||
|
|
||||||
|
{
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
let status_listeners = self.status_listeners.clone();
|
||||||
|
|
||||||
|
manager.quick_assign(move |_, event, _ddata| {
|
||||||
|
let mut inner = RefCell::borrow_mut(&inner);
|
||||||
|
let status_listeners = status_listeners.clone();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
zwlr_foreign_toplevel_manager_v1::Event::Toplevel {
|
||||||
|
toplevel: handle,
|
||||||
|
} => {
|
||||||
|
let toplevel =
|
||||||
|
Toplevel::init(&handle.clone(), move |event, ddata| {
|
||||||
|
notify_status_listeners(
|
||||||
|
&handle,
|
||||||
|
&event,
|
||||||
|
ddata,
|
||||||
|
&status_listeners,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
inner.toplevels.push(toplevel);
|
||||||
|
}
|
||||||
|
zwlr_foreign_toplevel_manager_v1::Event::Finished => {}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.manager = LazyGlobal::Bound((*manager).clone());
|
||||||
|
Some((*manager).clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ToplevelStatusCallback =
|
||||||
|
dyn FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
|
||||||
|
|
||||||
|
/// Notifies the callbacks of an event on the toplevel
|
||||||
|
fn notify_status_listeners(
|
||||||
|
toplevel: &ZwlrForeignToplevelHandleV1,
|
||||||
|
event: &ToplevelEvent,
|
||||||
|
mut ddata: DispatchData,
|
||||||
|
listeners: &RefCell<Vec<rc::Weak<RefCell<ToplevelStatusCallback>>>>,
|
||||||
|
) {
|
||||||
|
listeners.borrow_mut().retain(|lst| {
|
||||||
|
rc::Weak::upgrade(lst).map_or(false, |cb| {
|
||||||
|
(cb.borrow_mut())(toplevel.clone(), event.clone(), ddata.reborrow());
|
||||||
|
true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ToplevelStatusListener {
|
||||||
|
_cb: Rc<RefCell<ToplevelStatusCallback>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ToplevelHandling {
|
||||||
|
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||||
|
where
|
||||||
|
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToplevelHandling for ToplevelHandler {
|
||||||
|
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||||
|
where
|
||||||
|
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
|
||||||
|
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
|
||||||
|
ToplevelStatusListener { _cb: rc }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen_for_toplevels<E, F>(env: &Environment<E>, f: F) -> ToplevelStatusListener
|
||||||
|
where
|
||||||
|
E: ToplevelHandling,
|
||||||
|
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||||
|
{
|
||||||
|
env.with_inner(move |inner| ToplevelHandling::listen(inner, f))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,84 +1,39 @@
|
|||||||
|
use std::sync::RwLock;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use tokio::sync::broadcast::Sender;
|
||||||
|
use tracing::trace;
|
||||||
|
use super::Env;
|
||||||
|
use handle::{ToplevelEvent, ToplevelChange, ToplevelInfo};
|
||||||
|
use manager::{ToplevelHandling, ToplevelStatusListener};
|
||||||
|
use wayland_client::DispatchData;
|
||||||
|
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1;
|
||||||
|
use crate::{send, write_lock};
|
||||||
|
|
||||||
pub mod handle;
|
pub mod handle;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
|
|
||||||
use self::handle::ToplevelHandleHandler;
|
impl ToplevelHandling for Env {
|
||||||
use self::manager::{ToplevelManagerHandler, ToplevelManagerState};
|
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||||
use crate::clients::wayland::Environment;
|
where
|
||||||
use tracing::{debug, error, trace};
|
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||||
use wayland_client::{Connection, QueueHandle};
|
{
|
||||||
|
self.toplevel.listen(f)
|
||||||
use crate::send;
|
|
||||||
pub use handle::{ToplevelHandle, ToplevelInfo};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ToplevelEvent {
|
|
||||||
New(ToplevelHandle),
|
|
||||||
Update(ToplevelHandle),
|
|
||||||
Remove(ToplevelHandle),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToplevelManagerHandler for Environment {
|
|
||||||
fn toplevel(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
_manager: ToplevelManagerState,
|
|
||||||
) {
|
|
||||||
debug!("Manager received new handle");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelHandleHandler for Environment {
|
pub fn update_toplevels(
|
||||||
fn new_handle(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, handle: ToplevelHandle) {
|
toplevels: &RwLock<IndexMap<usize, (ToplevelInfo, ZwlrForeignToplevelHandleV1)>>,
|
||||||
debug!("Handler received new handle");
|
handle: ZwlrForeignToplevelHandleV1,
|
||||||
|
event: ToplevelEvent,
|
||||||
|
tx: &Sender<ToplevelEvent>,
|
||||||
|
) {
|
||||||
|
trace!("Received toplevel event: {:?}", event);
|
||||||
|
|
||||||
match handle.info() {
|
if event.change == ToplevelChange::Close {
|
||||||
Some(info) => {
|
write_lock!(toplevels).remove(&event.toplevel.id);
|
||||||
trace!("Adding new handle: {info:?}");
|
} else {
|
||||||
self.handles.insert(info.id, handle.clone());
|
write_lock!(toplevels).insert(event.toplevel.id, (event.toplevel.clone(), handle));
|
||||||
send!(self.toplevel_tx, ToplevelEvent::New(handle));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
error!("Handle is missing information!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_handle(
|
send!(tx, event);
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
handle: ToplevelHandle,
|
|
||||||
) {
|
|
||||||
debug!("Handler received handle update");
|
|
||||||
|
|
||||||
match handle.info() {
|
|
||||||
Some(info) => {
|
|
||||||
trace!("Updating handle: {info:?}");
|
|
||||||
self.handles.insert(info.id, handle.clone());
|
|
||||||
send!(self.toplevel_tx, ToplevelEvent::Update(handle));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
error!("Handle is missing information!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_handle(
|
|
||||||
&mut self,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qh: &QueueHandle<Self>,
|
|
||||||
handle: ToplevelHandle,
|
|
||||||
) {
|
|
||||||
debug!("Handler received handle close");
|
|
||||||
match handle.info() {
|
|
||||||
Some(info) => {
|
|
||||||
self.handles.remove(&info.id);
|
|
||||||
send!(self.toplevel_tx, ToplevelEvent::Remove(handle));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
error!("Handle is missing information!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
|
use glib::signal::Inhibit;
|
||||||
use crate::dynamic_string::DynamicString;
|
use crate::dynamic_string::DynamicString;
|
||||||
use crate::script::{Script, ScriptInput};
|
use crate::script::{Script, ScriptInput};
|
||||||
use crate::send;
|
use crate::send;
|
||||||
use gtk::gdk::ScrollDirection;
|
use gtk::gdk::ScrollDirection;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
|
use gtk::{GestureClick, Orientation, Revealer, RevealerTransitionType, Widget};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
/// Common configuration options
|
/// Common configuration options
|
||||||
/// which can be set on every module.
|
/// which can be set on every module.
|
||||||
#[derive(Debug, Default, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct CommonConfig {
|
pub struct CommonConfig {
|
||||||
pub class: Option<String>,
|
|
||||||
pub name: Option<String>,
|
|
||||||
|
|
||||||
pub show_if: Option<ScriptInput>,
|
pub show_if: Option<ScriptInput>,
|
||||||
pub transition_type: Option<TransitionType>,
|
pub transition_type: Option<TransitionType>,
|
||||||
pub transition_duration: Option<u32>,
|
pub transition_duration: Option<u32>,
|
||||||
@@ -40,16 +38,17 @@ pub enum TransitionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TransitionType {
|
impl TransitionType {
|
||||||
pub const fn to_revealer_transition_type(
|
pub fn to_revealer_transition_type(&self, orientation: Orientation) -> RevealerTransitionType {
|
||||||
&self,
|
|
||||||
orientation: Orientation,
|
|
||||||
) -> RevealerTransitionType {
|
|
||||||
match (self, orientation) {
|
match (self, orientation) {
|
||||||
(Self::SlideStart, Orientation::Horizontal) => RevealerTransitionType::SlideLeft,
|
(TransitionType::SlideStart, Orientation::Horizontal) => {
|
||||||
(Self::SlideStart, Orientation::Vertical) => RevealerTransitionType::SlideUp,
|
RevealerTransitionType::SlideLeft
|
||||||
(Self::SlideEnd, Orientation::Horizontal) => RevealerTransitionType::SlideRight,
|
}
|
||||||
(Self::SlideEnd, Orientation::Vertical) => RevealerTransitionType::SlideDown,
|
(TransitionType::SlideStart, Orientation::Vertical) => RevealerTransitionType::SlideUp,
|
||||||
(Self::Crossfade, _) => RevealerTransitionType::Crossfade,
|
(TransitionType::SlideEnd, Orientation::Horizontal) => {
|
||||||
|
RevealerTransitionType::SlideRight
|
||||||
|
}
|
||||||
|
(TransitionType::SlideEnd, Orientation::Vertical) => RevealerTransitionType::SlideDown,
|
||||||
|
(TransitionType::Crossfade, _) => RevealerTransitionType::Crossfade,
|
||||||
_ => RevealerTransitionType::None,
|
_ => RevealerTransitionType::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,14 +56,16 @@ impl TransitionType {
|
|||||||
|
|
||||||
impl CommonConfig {
|
impl CommonConfig {
|
||||||
/// Configures the module's container according to the common config options.
|
/// Configures the module's container according to the common config options.
|
||||||
pub fn install_events(mut self, container: &EventBox, revealer: &Revealer) {
|
pub fn install<W: IsA<Widget>>(mut self, widget: &W, revealer: &Revealer) {
|
||||||
self.install_show_if(container, revealer);
|
self.install_show_if(widget, revealer);
|
||||||
|
|
||||||
let left_click_script = self.on_click_left.map(Script::new_polling);
|
let left_click_script = self.on_click_left.map(Script::new_polling);
|
||||||
let middle_click_script = self.on_click_middle.map(Script::new_polling);
|
let middle_click_script = self.on_click_middle.map(Script::new_polling);
|
||||||
let right_click_script = self.on_click_right.map(Script::new_polling);
|
let right_click_script = self.on_click_right.map(Script::new_polling);
|
||||||
|
|
||||||
container.connect_button_press_event(move |_, event| {
|
let gesture = GestureClick::new();
|
||||||
|
|
||||||
|
gesture.connect_pressed(move |_, event| {
|
||||||
let script = match event.button() {
|
let script = match event.button() {
|
||||||
1 => left_click_script.as_ref(),
|
1 => left_click_script.as_ref(),
|
||||||
2 => middle_click_script.as_ref(),
|
2 => middle_click_script.as_ref(),
|
||||||
@@ -76,14 +77,12 @@ impl CommonConfig {
|
|||||||
trace!("Running on-click script: {}", event.button());
|
trace!("Running on-click script: {}", event.button());
|
||||||
script.run_as_oneshot(None);
|
script.run_as_oneshot(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Inhibit(false)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let scroll_up_script = self.on_scroll_up.map(Script::new_polling);
|
let scroll_up_script = self.on_scroll_up.map(Script::new_polling);
|
||||||
let scroll_down_script = self.on_scroll_down.map(Script::new_polling);
|
let scroll_down_script = self.on_scroll_down.map(Script::new_polling);
|
||||||
|
|
||||||
container.connect_scroll_event(move |_, event| {
|
widget.connect_scroll_event(move |_, event| {
|
||||||
let script = match event.direction() {
|
let script = match event.direction() {
|
||||||
ScrollDirection::Up => scroll_up_script.as_ref(),
|
ScrollDirection::Up => scroll_up_script.as_ref(),
|
||||||
ScrollDirection::Down => scroll_down_script.as_ref(),
|
ScrollDirection::Down => scroll_down_script.as_ref(),
|
||||||
@@ -101,7 +100,7 @@ impl CommonConfig {
|
|||||||
macro_rules! install_oneshot {
|
macro_rules! install_oneshot {
|
||||||
($option:expr, $method:ident) => {
|
($option:expr, $method:ident) => {
|
||||||
$option.map(Script::new_polling).map(|script| {
|
$option.map(Script::new_polling).map(|script| {
|
||||||
container.$method(move |_, _| {
|
widget.$method(move |_, _| {
|
||||||
script.run_as_oneshot(None);
|
script.run_as_oneshot(None);
|
||||||
Inhibit(false)
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
@@ -113,7 +112,7 @@ impl CommonConfig {
|
|||||||
install_oneshot!(self.on_mouse_exit, connect_leave_notify_event);
|
install_oneshot!(self.on_mouse_exit, connect_leave_notify_event);
|
||||||
|
|
||||||
if let Some(tooltip) = self.tooltip {
|
if let Some(tooltip) = self.tooltip {
|
||||||
let container = container.clone();
|
let container = widget.clone();
|
||||||
DynamicString::new(&tooltip, move |string| {
|
DynamicString::new(&tooltip, move |string| {
|
||||||
container.set_tooltip_text(Some(&string));
|
container.set_tooltip_text(Some(&string));
|
||||||
Continue(true)
|
Continue(true)
|
||||||
@@ -121,14 +120,14 @@ impl CommonConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_show_if(&mut self, container: &EventBox, revealer: &Revealer) {
|
fn install_show_if<W: IsA<Widget>>(&mut self, widget: &W, revealer: &Revealer) {
|
||||||
self.show_if.take().map_or_else(
|
self.show_if.take().map_or_else(
|
||||||
|| {
|
|| {
|
||||||
container.show_all();
|
widget.set_visible(true)
|
||||||
},
|
},
|
||||||
|show_if| {
|
|show_if| {
|
||||||
let script = Script::new_polling(show_if);
|
let script = Script::new_polling(show_if);
|
||||||
let container = container.clone();
|
let widget = widget.clone();
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
@@ -154,7 +153,7 @@ impl CommonConfig {
|
|||||||
|
|
||||||
revealer.connect_child_revealed_notify(move |revealer| {
|
revealer.connect_child_revealed_notify(move |revealer| {
|
||||||
if !revealer.reveals_child() {
|
if !revealer.reveals_child() {
|
||||||
container.hide();
|
container.hide()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,12 +29,13 @@ pub fn find_desktop_file(app_id: &str) -> Option<PathBuf> {
|
|||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
let mut walker = WalkDir::new(dir).max_depth(5).into_iter();
|
let mut walker = WalkDir::new(dir).max_depth(5).into_iter();
|
||||||
|
|
||||||
let entry = walker.find(|entry| {
|
let entry = walker.find(|entry| match entry {
|
||||||
entry.as_ref().map_or(false, |entry| {
|
Ok(entry) => {
|
||||||
let file_name = entry.file_name().to_string_lossy().to_lowercase();
|
let file_name = entry.file_name().to_string_lossy().to_lowercase();
|
||||||
let test_name = format!("{}.desktop", app_id.to_lowercase());
|
let test_name = format!("{}.desktop", app_id.to_lowercase());
|
||||||
file_name == test_name
|
file_name == test_name
|
||||||
})
|
}
|
||||||
|
_ => false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(Ok(entry)) = entry {
|
if let Some(Ok(entry)) = entry {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ impl DynamicString {
|
|||||||
if let OutputStream::Stdout(out) = out {
|
if let OutputStream::Stdout(out) = out {
|
||||||
let mut label_parts = lock!(label_parts);
|
let mut label_parts = lock!(label_parts);
|
||||||
|
|
||||||
let _: String = std::mem::replace(&mut label_parts[i], out);
|
let _ = std::mem::replace(&mut label_parts[i], out);
|
||||||
|
|
||||||
let string = label_parts.join("");
|
let string = label_parts.join("");
|
||||||
send!(tx, string);
|
send!(tx, string);
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ pub enum ExitCode {
|
|||||||
Config = 3,
|
Config = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ERR_OUTPUTS: &str = "GTK and Wayland are reporting a different set of outputs - this is a severe bug and should never happen";
|
pub const ERR_OUTPUTS: &str = "GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen";
|
||||||
pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex";
|
pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex";
|
||||||
pub const ERR_READ_LOCK: &str = "Failed to get read lock";
|
pub const ERR_READ_LOCK: &str = "Failed to get read lock";
|
||||||
pub const ERR_WRITE_LOCK: &str = "Failed to get write lock";
|
pub const ERR_WRITE_LOCK: &str = "Failed to get write lock";
|
||||||
pub const ERR_CHANNEL_SEND: &str = "Failed to send message to channel";
|
pub const ERR_CHANNEL_SEND: &str = "Failed to send message to channel";
|
||||||
pub const ERR_CHANNEL_RECV: &str = "Failed to receive message from channel";
|
pub const ERR_CHANNEL_RECV: &str = "Failed to receive message from channel";
|
||||||
|
|
||||||
pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object";
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
use glib::IsA;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use gtk::Widget;
|
|
||||||
|
|
||||||
/// Adds a new CSS class to a widget.
|
|
||||||
pub fn add_class<W: IsA<Widget>>(widget: &W, class: &str) {
|
|
||||||
widget.style_context().add_class(class);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
use super::ImageProvider;
|
use super::ImageProvider;
|
||||||
use crate::gtk_helpers::add_class;
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Image, Label, Orientation};
|
use gtk::{Button, IconTheme, Image, Label, Orientation};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
@@ -10,7 +9,7 @@ pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button
|
|||||||
|
|
||||||
if ImageProvider::is_definitely_image_input(input) {
|
if ImageProvider::is_definitely_image_input(input) {
|
||||||
let image = Image::new();
|
let image = Image::new();
|
||||||
add_class(&image, "image");
|
image.set_widget_name("image");
|
||||||
|
|
||||||
match ImageProvider::parse(input, icon_theme, size)
|
match ImageProvider::parse(input, icon_theme, size)
|
||||||
.and_then(|provider| provider.load_into_image(image.clone()))
|
.and_then(|provider| provider.load_into_image(image.clone()))
|
||||||
@@ -37,9 +36,9 @@ pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Bo
|
|||||||
|
|
||||||
if ImageProvider::is_definitely_image_input(input) {
|
if ImageProvider::is_definitely_image_input(input) {
|
||||||
let image = Image::new();
|
let image = Image::new();
|
||||||
add_class(&image, "image");
|
image.set_widget_name("image");
|
||||||
|
|
||||||
container.add(&image);
|
container.append(&image);
|
||||||
|
|
||||||
if let Err(err) = ImageProvider::parse(input, icon_theme, size)
|
if let Err(err) = ImageProvider::parse(input, icon_theme, size)
|
||||||
.and_then(|provider| provider.load_into_image(image))
|
.and_then(|provider| provider.load_into_image(image))
|
||||||
@@ -48,9 +47,9 @@ pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Bo
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let label = Label::new(Some(input));
|
let label = Label::new(Some(input));
|
||||||
add_class(&label, "label");
|
label.set_widget_name("label");
|
||||||
|
|
||||||
container.add(&label);
|
container.append(&label);
|
||||||
}
|
}
|
||||||
|
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ fn install_tracing() -> Result<WorkerGuard> {
|
|||||||
const DEFAULT_LOG: &str = "info";
|
const DEFAULT_LOG: &str = "info";
|
||||||
const DEFAULT_FILE_LOG: &str = "warn";
|
const DEFAULT_FILE_LOG: &str = "warn";
|
||||||
|
|
||||||
let fmt_layer = fmt::layer().with_target(true).with_line_number(true);
|
let fmt_layer = fmt::layer().with_target(true);
|
||||||
let filter_layer =
|
let filter_layer =
|
||||||
EnvFilter::try_from_env("IRONBAR_LOG").or_else(|_| EnvFilter::try_new(DEFAULT_LOG))?;
|
EnvFilter::try_from_env("IRONBAR_LOG").or_else(|_| EnvFilter::try_new(DEFAULT_LOG))?;
|
||||||
|
|
||||||
|
|||||||
@@ -53,10 +53,9 @@ macro_rules! try_send {
|
|||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! lock {
|
macro_rules! lock {
|
||||||
($mutex:expr) => {{
|
($mutex:expr) => {
|
||||||
tracing::trace!("Locking {}", std::stringify!($mutex));
|
|
||||||
$mutex.lock().expect($crate::error::ERR_MUTEX_LOCK)
|
$mutex.lock().expect($crate::error::ERR_MUTEX_LOCK)
|
||||||
}};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a read lock on a `RwLock`.
|
/// Gets a read lock on a `RwLock`.
|
||||||
|
|||||||
29
src/main.rs
29
src/main.rs
@@ -7,7 +7,6 @@ mod config;
|
|||||||
mod desktop_file;
|
mod desktop_file;
|
||||||
mod dynamic_string;
|
mod dynamic_string;
|
||||||
mod error;
|
mod error;
|
||||||
mod gtk_helpers;
|
|
||||||
mod image;
|
mod image;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod macros;
|
mod macros;
|
||||||
@@ -22,15 +21,13 @@ use crate::style::load_css;
|
|||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
use gtk::gdk::Display;
|
use gtk::gdk::{Display, Monitor};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Application;
|
use gtk::Application;
|
||||||
use std::cell::Cell;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::rc::Rc;
|
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
|
|
||||||
@@ -43,7 +40,7 @@ const GTK_APP_ID: &str = "dev.jstanger.ironbar";
|
|||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<()> {
|
||||||
let _guard = logging::install_logging();
|
let _guard = logging::install_logging();
|
||||||
|
|
||||||
info!("Ironbar version {}", VERSION);
|
info!("Ironbar version {}", VERSION);
|
||||||
@@ -53,16 +50,7 @@ async fn main() {
|
|||||||
|
|
||||||
let app = Application::builder().application_id(GTK_APP_ID).build();
|
let app = Application::builder().application_id(GTK_APP_ID).build();
|
||||||
|
|
||||||
let running = Rc::new(Cell::new(false));
|
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
if running.get() {
|
|
||||||
info!("Ironbar already running, returning");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
running.set(true);
|
|
||||||
|
|
||||||
let display = Display::default().map_or_else(
|
let display = Display::default().map_or_else(
|
||||||
|| {
|
|| {
|
||||||
let report = Report::msg("Failed to get default GTK display");
|
let report = Report::msg("Failed to get default GTK display");
|
||||||
@@ -117,8 +105,7 @@ async fn main() {
|
|||||||
// Some are provided by swaybar_config but not currently supported
|
// Some are provided by swaybar_config but not currently supported
|
||||||
app.run_with_args(&Vec::<&str>::new());
|
app.run_with_args(&Vec::<&str>::new());
|
||||||
|
|
||||||
info!("Shutting down");
|
Ok(())
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates each of the bars across each of the (configured) outputs.
|
/// Creates each of the bars across each of the (configured) outputs.
|
||||||
@@ -128,12 +115,15 @@ fn create_bars(
|
|||||||
wl: &WaylandClient,
|
wl: &WaylandClient,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let outputs = wl.get_outputs();
|
let outputs = wl.outputs.as_slice();
|
||||||
|
|
||||||
debug!("Received {} outputs from Wayland", outputs.len());
|
debug!("Received {} outputs from Wayland", outputs.len());
|
||||||
debug!("Outputs: {:?}", outputs);
|
debug!("Outputs: {:?}", outputs);
|
||||||
|
|
||||||
let num_monitors = display.n_monitors();
|
for monitor in display.monitors().iter::<Monitor>() {
|
||||||
|
let monitor = monitor.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for i in 0..num_monitors {
|
for i in 0..num_monitors {
|
||||||
let monitor = display
|
let monitor = display
|
||||||
@@ -142,8 +132,7 @@ fn create_bars(
|
|||||||
let output = outputs
|
let output = outputs
|
||||||
.get(i as usize)
|
.get(i as usize)
|
||||||
.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?;
|
.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?;
|
||||||
|
let monitor_name = &output.name;
|
||||||
let Some(monitor_name) = &output.name else { continue };
|
|
||||||
|
|
||||||
config.monitors.as_ref().map_or_else(
|
config.monitors.as_ref().map_or_else(
|
||||||
|| {
|
|| {
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ use crate::try_send;
|
|||||||
use gtk::gdk_pixbuf::Pixbuf;
|
use gtk::gdk_pixbuf::Pixbuf;
|
||||||
use gtk::gio::{Cancellable, MemoryInputStream};
|
use gtk::gio::{Cancellable, MemoryInputStream};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, EventBox, Image, Label, Orientation, RadioButton, Widget};
|
use gtk::{Button, Image, Label, Orientation, RadioButton, Widget};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use glib::signal::Inhibit;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
@@ -80,7 +81,7 @@ impl Module<Button> for ClipboardModule {
|
|||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut rx = {
|
let mut rx = {
|
||||||
let client = clipboard::get_client();
|
let client = clipboard::get_client();
|
||||||
client.subscribe(max_items)
|
client.subscribe(max_items).await
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
@@ -154,13 +155,17 @@ impl Module<Button> for ClipboardModule {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let container = gtk::Box::new(Orientation::Vertical, 10);
|
let container = gtk::Box::builder()
|
||||||
|
.orientation(Orientation::Vertical)
|
||||||
|
.spacing(10)
|
||||||
|
.name("popup-clipboard")
|
||||||
|
.build();
|
||||||
|
|
||||||
let entries = gtk::Box::new(Orientation::Vertical, 5);
|
let entries = gtk::Box::new(Orientation::Vertical, 5);
|
||||||
container.add(&entries);
|
container.append(&entries);
|
||||||
|
|
||||||
let hidden_option = RadioButton::new();
|
let hidden_option = RadioButton::new();
|
||||||
entries.add(&hidden_option);
|
entries.append(&hidden_option);
|
||||||
|
|
||||||
let mut items = HashMap::new();
|
let mut items = HashMap::new();
|
||||||
|
|
||||||
@@ -179,7 +184,7 @@ impl Module<Button> for ClipboardModule {
|
|||||||
let button = RadioButton::from_widget(&hidden_option);
|
let button = RadioButton::from_widget(&hidden_option);
|
||||||
|
|
||||||
let label = Label::new(Some(value));
|
let label = Label::new(Some(value));
|
||||||
button.add(&label);
|
button.append(&label);
|
||||||
|
|
||||||
if let Some(truncate) = self.truncate {
|
if let Some(truncate) = self.truncate {
|
||||||
truncate.truncate_label(&label);
|
truncate.truncate_label(&label);
|
||||||
@@ -214,7 +219,7 @@ impl Module<Button> for ClipboardModule {
|
|||||||
button.set_active(true); // if just added, should be on clipboard
|
button.set_active(true); // if just added, should be on clipboard
|
||||||
|
|
||||||
let button_wrapper = EventBox::new();
|
let button_wrapper = EventBox::new();
|
||||||
button_wrapper.add(&button);
|
button_wrapper.append(&button);
|
||||||
|
|
||||||
button_wrapper.set_widget_name(&format!("copy-{id}"));
|
button_wrapper.set_widget_name(&format!("copy-{id}"));
|
||||||
button_wrapper.set_above_child(true);
|
button_wrapper.set_above_child(true);
|
||||||
@@ -257,12 +262,11 @@ impl Module<Button> for ClipboardModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
row.add(&button_wrapper);
|
row.append(&button_wrapper);
|
||||||
row.pack_end(&remove_button, false, false, 0);
|
row.pack_end(&remove_button, false, false, 0);
|
||||||
|
|
||||||
entries.add(&row);
|
entries.append(&row);
|
||||||
entries.reorder_child(&row, 0);
|
entries.reorder_child(&row, 0);
|
||||||
row.show_all();
|
|
||||||
|
|
||||||
items.insert(id, (row, button));
|
items.insert(id, (row, button));
|
||||||
}
|
}
|
||||||
@@ -296,7 +300,6 @@ impl Module<Button> for ClipboardModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
container.show_all();
|
|
||||||
hidden_option.hide();
|
hidden_option.hide();
|
||||||
|
|
||||||
Some(container)
|
Some(container)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::gtk_helpers::add_class;
|
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::{send_async, try_send};
|
use crate::{send_async, try_send};
|
||||||
@@ -64,7 +63,7 @@ impl Module<Button> for ClockModule {
|
|||||||
let button = Button::new();
|
let button = Button::new();
|
||||||
let label = Label::new(None);
|
let label = Label::new(None);
|
||||||
label.set_angle(info.bar_position.get_angle());
|
label.set_angle(info.bar_position.get_angle());
|
||||||
button.add(&label);
|
button.append(&label);
|
||||||
|
|
||||||
let orientation = info.bar_position.get_orientation();
|
let orientation = info.bar_position.get_orientation();
|
||||||
button.connect_clicked(move |button| {
|
button.connect_clicked(move |button| {
|
||||||
@@ -97,17 +96,21 @@ impl Module<Button> for ClockModule {
|
|||||||
rx: glib::Receiver<Self::SendMessage>,
|
rx: glib::Receiver<Self::SendMessage>,
|
||||||
_info: &ModuleInfo,
|
_info: &ModuleInfo,
|
||||||
) -> Option<gtk::Box> {
|
) -> Option<gtk::Box> {
|
||||||
let container = gtk::Box::new(Orientation::Vertical, 0);
|
let container = gtk::Box::builder()
|
||||||
|
.orientation(Orientation::Vertical)
|
||||||
|
.name("popup-clock")
|
||||||
|
.build();
|
||||||
|
|
||||||
let clock = Label::builder().halign(Align::Center).build();
|
let clock = Label::builder()
|
||||||
add_class(&clock, "calendar-clock");
|
.name("calendar-clock")
|
||||||
|
.halign(Align::Center)
|
||||||
|
.build();
|
||||||
let format = "%H:%M:%S";
|
let format = "%H:%M:%S";
|
||||||
|
|
||||||
container.add(&clock);
|
container.append(&clock);
|
||||||
|
|
||||||
let calendar = Calendar::new();
|
let calendar = Calendar::builder().name("calendar").build();
|
||||||
add_class(&calendar, "calendar");
|
container.append(&calendar);
|
||||||
container.add(&calendar);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
rx.attach(None, move |date| {
|
rx.attach(None, move |date| {
|
||||||
@@ -117,8 +120,6 @@ impl Module<Button> for ClockModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
container.show_all();
|
|
||||||
|
|
||||||
Some(container)
|
Some(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl CustomWidget for ButtonWidget {
|
|||||||
if let Some(text) = self.label {
|
if let Some(text) = self.label {
|
||||||
let label = Label::new(None);
|
let label = Label::new(None);
|
||||||
label.set_use_markup(true);
|
label.set_use_markup(true);
|
||||||
button.add(&label);
|
button.append(&label);
|
||||||
|
|
||||||
DynamicString::new(&text, move |string| {
|
DynamicString::new(&text, move |string| {
|
||||||
label.set_markup(&string);
|
label.set_markup(&string);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ use tracing::{debug, error};
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct CustomModule {
|
pub struct CustomModule {
|
||||||
|
/// Container class name
|
||||||
|
class: Option<String>,
|
||||||
/// Widgets to add to the bar container
|
/// Widgets to add to the bar container
|
||||||
bar: Vec<WidgetConfig>,
|
bar: Vec<WidgetConfig>,
|
||||||
/// Widgets to add to the popup container
|
/// Widgets to add to the popup container
|
||||||
@@ -195,6 +197,10 @@ impl Module<gtk::Box> for CustomModule {
|
|||||||
let orientation = info.bar_position.get_orientation();
|
let orientation = info.bar_position.get_orientation();
|
||||||
let container = gtk::Box::builder().orientation(orientation).build();
|
let container = gtk::Box::builder().orientation(orientation).build();
|
||||||
|
|
||||||
|
if let Some(ref class) = self.class {
|
||||||
|
container.style_context().add_class(class);
|
||||||
|
}
|
||||||
|
|
||||||
let custom_context = CustomWidgetContext {
|
let custom_context = CustomWidgetContext {
|
||||||
tx: &context.controller_tx,
|
tx: &context.controller_tx,
|
||||||
bar_orientation: orientation,
|
bar_orientation: orientation,
|
||||||
@@ -224,7 +230,13 @@ impl Module<gtk::Box> for CustomModule {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
let container = gtk::Box::builder().name("popup-custom").build();
|
||||||
|
|
||||||
|
if let Some(class) = self.class {
|
||||||
|
container
|
||||||
|
.style_context()
|
||||||
|
.add_class(format!("popup-{class}").as_str());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(popup) = self.popup {
|
if let Some(popup) = self.popup {
|
||||||
let custom_context = CustomWidgetContext {
|
let custom_context = CustomWidgetContext {
|
||||||
@@ -240,8 +252,6 @@ impl Module<gtk::Box> for CustomModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container.show_all();
|
|
||||||
|
|
||||||
Some(container)
|
Some(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use gtk::Scale;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::ops::Neg;
|
use std::ops::Neg;
|
||||||
|
use glib::signal::Inhibit;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use crate::clients::wayland::{self, ToplevelEvent};
|
use crate::clients::wayland::{self, ToplevelChange};
|
||||||
use crate::config::{CommonConfig, TruncateMode};
|
use crate::config::{CommonConfig, TruncateMode};
|
||||||
use crate::gtk_helpers::add_class;
|
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::{send_async, try_send};
|
use crate::{await_sync, read_lock, send_async};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
@@ -11,7 +10,7 @@ use gtk::Label;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, error};
|
use tracing::error;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct FocusedModule {
|
pub struct FocusedModule {
|
||||||
@@ -50,38 +49,40 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
_rx: Receiver<Self::ReceiveMessage>,
|
_rx: Receiver<Self::ReceiveMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let focused = await_sync(async {
|
||||||
|
let wl = wayland::get_client().await;
|
||||||
|
let toplevels = read_lock!(wl.toplevels);
|
||||||
|
|
||||||
|
toplevels
|
||||||
|
.iter()
|
||||||
|
.find(|(_, (top, _))| top.active)
|
||||||
|
.map(|(_, (top, _))| top.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(top) = focused {
|
||||||
|
tx.try_send(ModuleUpdateEvent::Update((top.title.clone(), top.app_id)))?;
|
||||||
|
}
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let (mut wlrx, handles) = {
|
let mut wlrx = {
|
||||||
let wl = wayland::get_client().await;
|
let wl = wayland::get_client().await;
|
||||||
wl.subscribe_toplevels()
|
wl.subscribe_toplevels()
|
||||||
};
|
};
|
||||||
|
|
||||||
let focused = handles.values().find_map(|handle| {
|
while let Ok(event) = wlrx.recv().await {
|
||||||
handle
|
let update = match event.change {
|
||||||
.info()
|
ToplevelChange::Focus(focus) => focus,
|
||||||
.and_then(|info| if info.focused { Some(info) } else { None })
|
ToplevelChange::Title(_) => event.toplevel.active,
|
||||||
});
|
_ => false,
|
||||||
|
|
||||||
if let Some(focused) = focused {
|
|
||||||
try_send!(
|
|
||||||
tx,
|
|
||||||
ModuleUpdateEvent::Update((focused.title.clone(), focused.app_id))
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Ok(event) = wlrx.recv().await {
|
if update {
|
||||||
if let ToplevelEvent::Update(handle) = event {
|
|
||||||
let info = handle.info().unwrap_or_default();
|
|
||||||
|
|
||||||
if info.focused {
|
|
||||||
debug!("Changing focus");
|
|
||||||
send_async!(
|
send_async!(
|
||||||
tx,
|
tx,
|
||||||
ModuleUpdateEvent::Update((info.title.clone(), info.app_id.clone()))
|
ModuleUpdateEvent::Update((event.toplevel.title, event.toplevel.app_id))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -96,18 +97,15 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
|
|
||||||
let container = gtk::Box::new(info.bar_position.get_orientation(), 5);
|
let container = gtk::Box::new(info.bar_position.get_orientation(), 5);
|
||||||
|
|
||||||
let icon = gtk::Image::new();
|
let icon = gtk::Image::builder().name("icon").build();
|
||||||
add_class(&icon, "icon");
|
let label = Label::builder().name("label").build();
|
||||||
|
|
||||||
let label = Label::new(None);
|
|
||||||
add_class(&label, "label");
|
|
||||||
|
|
||||||
if let Some(truncate) = self.truncate {
|
if let Some(truncate) = self.truncate {
|
||||||
truncate.truncate_label(&label);
|
truncate.truncate_label(&label);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.add(&icon);
|
container.append(&icon);
|
||||||
container.add(&label);
|
container.append(&label);
|
||||||
|
|
||||||
{
|
{
|
||||||
let icon_theme = icon_theme.clone();
|
let icon_theme = icon_theme.clone();
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
use super::open_state::OpenState;
|
use super::open_state::OpenState;
|
||||||
use crate::clients::wayland::ToplevelHandle;
|
use crate::clients::wayland::ToplevelInfo;
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
||||||
use crate::modules::ModuleUpdateEvent;
|
use crate::modules::ModuleUpdateEvent;
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::{read_lock, try_send};
|
use crate::{read_lock, try_send};
|
||||||
use color_eyre::{Report, Result};
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Orientation};
|
use gtk::{Button, IconTheme, Orientation};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use glib::signal::Inhibit;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
@@ -36,31 +35,25 @@ impl Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Merges the provided node into this launcher item
|
/// Merges the provided node into this launcher item
|
||||||
pub fn merge_toplevel(&mut self, handle: ToplevelHandle) -> Result<Window> {
|
pub fn merge_toplevel(&mut self, node: ToplevelInfo) -> Window {
|
||||||
let info = handle
|
let id = node.id;
|
||||||
.info()
|
|
||||||
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
|
||||||
|
|
||||||
let id = info.id;
|
|
||||||
|
|
||||||
if self.windows.is_empty() {
|
if self.windows.is_empty() {
|
||||||
self.name = info.title;
|
self.name = node.title.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let window = Window::try_from(handle)?;
|
let window: Window = node.into();
|
||||||
self.windows.insert(id, window.clone());
|
self.windows.insert(id, window.clone());
|
||||||
|
|
||||||
self.recalculate_open_state();
|
self.recalculate_open_state();
|
||||||
|
|
||||||
Ok(window)
|
window
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unmerge_toplevel(&mut self, handle: &ToplevelHandle) {
|
pub fn unmerge_toplevel(&mut self, node: &ToplevelInfo) {
|
||||||
if let Some(info) = handle.info() {
|
self.windows.remove(&node.id);
|
||||||
self.windows.remove(&info.id);
|
|
||||||
self.recalculate_open_state();
|
self.recalculate_open_state();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_window_name(&mut self, window_id: usize, name: String) {
|
pub fn set_window_name(&mut self, window_id: usize, name: String) {
|
||||||
if let Some(window) = self.windows.get_mut(&window_id) {
|
if let Some(window) = self.windows.get_mut(&window_id) {
|
||||||
@@ -95,29 +88,22 @@ impl Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ToplevelHandle> for Item {
|
impl From<ToplevelInfo> for Item {
|
||||||
type Error = Report;
|
fn from(toplevel: ToplevelInfo) -> Self {
|
||||||
|
let open_state = OpenState::from_toplevel(&toplevel);
|
||||||
fn try_from(handle: ToplevelHandle) -> std::result::Result<Self, Self::Error> {
|
let name = toplevel.title.clone();
|
||||||
let info = handle
|
let app_id = toplevel.app_id.clone();
|
||||||
.info()
|
|
||||||
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
|
||||||
|
|
||||||
let name = info.title.clone();
|
|
||||||
let app_id = info.app_id.clone();
|
|
||||||
let open_state = OpenState::from(&info);
|
|
||||||
|
|
||||||
let mut windows = IndexMap::new();
|
let mut windows = IndexMap::new();
|
||||||
let window = Window::try_from(handle)?;
|
windows.insert(toplevel.id, toplevel.into());
|
||||||
windows.insert(info.id, window);
|
|
||||||
|
|
||||||
Ok(Self {
|
Self {
|
||||||
app_id,
|
app_id,
|
||||||
favorite: false,
|
favorite: false,
|
||||||
open_state,
|
open_state,
|
||||||
windows,
|
windows,
|
||||||
name,
|
name,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,30 +112,17 @@ pub struct Window {
|
|||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub open_state: OpenState,
|
pub open_state: OpenState,
|
||||||
handle: ToplevelHandle,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ToplevelHandle> for Window {
|
impl From<ToplevelInfo> for Window {
|
||||||
type Error = Report;
|
fn from(node: ToplevelInfo) -> Self {
|
||||||
|
let open_state = OpenState::from_toplevel(&node);
|
||||||
|
|
||||||
fn try_from(handle: ToplevelHandle) -> Result<Self, Self::Error> {
|
Self {
|
||||||
let info = handle
|
id: node.id,
|
||||||
.info()
|
name: node.title,
|
||||||
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
|
||||||
let open_state = OpenState::from(&info);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
id: info.id,
|
|
||||||
name: info.title,
|
|
||||||
open_state,
|
open_state,
|
||||||
handle,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn focus(&self, seat: &WlSeat) {
|
|
||||||
self.handle.focus(seat);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,8 +235,6 @@ impl ItemButton {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
button.show_all();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
button,
|
button,
|
||||||
persistent: item.favorite,
|
persistent: item.favorite,
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ mod open_state;
|
|||||||
|
|
||||||
use self::item::{Item, ItemButton, Window};
|
use self::item::{Item, ItemButton, Window};
|
||||||
use self::open_state::OpenState;
|
use self::open_state::OpenState;
|
||||||
use crate::clients::wayland::{self, ToplevelEvent};
|
use crate::clients::wayland::{self, ToplevelChange};
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::desktop_file::find_desktop_file;
|
use crate::desktop_file::find_desktop_file;
|
||||||
use crate::modules::launcher::item::AppearanceOptions;
|
use crate::modules::launcher::item::AppearanceOptions;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::{lock, send_async, try_send, write_lock};
|
use crate::{lock, read_lock, try_send, write_lock};
|
||||||
use color_eyre::{Help, Report};
|
use color_eyre::{Help, Report};
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
@@ -18,6 +18,7 @@ use serde::Deserialize;
|
|||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
@@ -110,64 +111,70 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
|
|
||||||
let items = Arc::new(Mutex::new(items));
|
let items = Arc::new(Mutex::new(items));
|
||||||
|
|
||||||
let items2 = Arc::clone(&items);
|
{
|
||||||
let tx2 = tx.clone();
|
let items = Arc::clone(&items);
|
||||||
|
let tx = tx.clone();
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let items = items2;
|
|
||||||
let tx = tx2;
|
|
||||||
|
|
||||||
let (mut wlrx, handles) = {
|
|
||||||
let wl = wayland::get_client().await;
|
let wl = wayland::get_client().await;
|
||||||
wl.subscribe_toplevels()
|
let open_windows = read_lock!(wl.toplevels);
|
||||||
};
|
|
||||||
|
|
||||||
for handle in handles.values() {
|
|
||||||
let Some(info) = handle.info() else { continue };
|
|
||||||
|
|
||||||
|
let open_windows = open_windows.clone();
|
||||||
|
for (_, (window, _)) in open_windows {
|
||||||
let mut items = lock!(items);
|
let mut items = lock!(items);
|
||||||
let item = items.get_mut(&info.app_id);
|
let item = items.get_mut(&window.app_id);
|
||||||
match item {
|
match item {
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
item.merge_toplevel(handle.clone())?;
|
item.merge_toplevel(window);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
items.insert(info.app_id.clone(), Item::try_from(handle.clone())?);
|
items.insert(window.app_id.clone(), window.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
let items = lock!(items);
|
let items = lock!(items);
|
||||||
let items = items.iter();
|
let items = items.iter();
|
||||||
for (_, item) in items {
|
for (_, item) in items {
|
||||||
try_send!(
|
tx.try_send(ModuleUpdateEvent::Update(LauncherUpdate::AddItem(
|
||||||
tx,
|
item.clone(),
|
||||||
ModuleUpdateEvent::Update(LauncherUpdate::AddItem(item.clone()))
|
)))?;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok::<(), Report>(())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let items2 = Arc::clone(&items);
|
||||||
|
spawn(async move {
|
||||||
|
let items = items2;
|
||||||
|
|
||||||
|
let mut wlrx = {
|
||||||
|
let wl = wayland::get_client().await;
|
||||||
|
wl.subscribe_toplevels()
|
||||||
|
};
|
||||||
|
|
||||||
let send_update = |update: LauncherUpdate| tx.send(ModuleUpdateEvent::Update(update));
|
let send_update = |update: LauncherUpdate| tx.send(ModuleUpdateEvent::Update(update));
|
||||||
|
|
||||||
while let Ok(event) = wlrx.recv().await {
|
while let Ok(event) = wlrx.recv().await {
|
||||||
trace!("event: {:?}", event);
|
trace!("event: {:?}", event);
|
||||||
|
|
||||||
match event {
|
let window = event.toplevel;
|
||||||
ToplevelEvent::New(handle) => {
|
let app_id = window.app_id.clone();
|
||||||
let Some(info) = handle.info() else { continue };
|
|
||||||
|
|
||||||
|
match event.change {
|
||||||
|
ToplevelChange::New => {
|
||||||
let new_item = {
|
let new_item = {
|
||||||
let mut items = lock!(items);
|
let mut items = lock!(items);
|
||||||
let item = items.get_mut(&info.app_id);
|
let item = items.get_mut(&app_id);
|
||||||
match item {
|
match item {
|
||||||
None => {
|
None => {
|
||||||
let item: Item = handle.try_into()?;
|
let item: Item = window.into();
|
||||||
items.insert(info.app_id.clone(), item.clone());
|
items.insert(app_id.clone(), item.clone());
|
||||||
|
|
||||||
ItemOrWindow::Item(item)
|
ItemOrWindow::Item(item)
|
||||||
}
|
}
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
let window = item.merge_toplevel(handle)?;
|
let window = item.merge_toplevel(window);
|
||||||
ItemOrWindow::Window(window)
|
ItemOrWindow::Window(window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,40 +185,20 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
send_update(LauncherUpdate::AddItem(item)).await
|
send_update(LauncherUpdate::AddItem(item)).await
|
||||||
}
|
}
|
||||||
ItemOrWindow::Window(window) => {
|
ItemOrWindow::Window(window) => {
|
||||||
send_update(LauncherUpdate::AddWindow(info.app_id.clone(), window))
|
send_update(LauncherUpdate::AddWindow(app_id, window)).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
ToplevelEvent::Update(handle) => {
|
ToplevelChange::Close => {
|
||||||
let Some(info) = handle.info() else { continue };
|
|
||||||
|
|
||||||
if let Some(item) = lock!(items).get_mut(&info.app_id) {
|
|
||||||
item.set_window_focused(info.id, info.focused);
|
|
||||||
item.set_window_name(info.id, info.title.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
send_update(LauncherUpdate::Focus(info.app_id.clone(), info.focused))
|
|
||||||
.await?;
|
|
||||||
send_update(LauncherUpdate::Title(
|
|
||||||
info.app_id.clone(),
|
|
||||||
info.id,
|
|
||||||
info.title.clone(),
|
|
||||||
))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
ToplevelEvent::Remove(handle) => {
|
|
||||||
let Some(info) = handle.info() else { continue };
|
|
||||||
|
|
||||||
let remove_item = {
|
let remove_item = {
|
||||||
let mut items = lock!(items);
|
let mut items = lock!(items);
|
||||||
let item = items.get_mut(&info.app_id);
|
let item = items.get_mut(&app_id);
|
||||||
match item {
|
match item {
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
item.unmerge_toplevel(&handle);
|
item.unmerge_toplevel(&window);
|
||||||
|
|
||||||
if item.windows.is_empty() {
|
if item.windows.is_empty() {
|
||||||
items.remove(&info.app_id);
|
items.remove(&app_id);
|
||||||
Some(ItemOrWindowId::Item)
|
Some(ItemOrWindowId::Item)
|
||||||
} else {
|
} else {
|
||||||
Some(ItemOrWindowId::Window)
|
Some(ItemOrWindowId::Window)
|
||||||
@@ -223,28 +210,56 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
|
|
||||||
match remove_item {
|
match remove_item {
|
||||||
Some(ItemOrWindowId::Item) => {
|
Some(ItemOrWindowId::Item) => {
|
||||||
send_update(LauncherUpdate::RemoveItem(info.app_id.clone()))
|
send_update(LauncherUpdate::RemoveItem(app_id)).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
Some(ItemOrWindowId::Window) => {
|
Some(ItemOrWindowId::Window) => {
|
||||||
send_update(LauncherUpdate::RemoveWindow(
|
send_update(LauncherUpdate::RemoveWindow(app_id, window.id))
|
||||||
info.app_id.clone(),
|
|
||||||
info.id,
|
|
||||||
))
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
ToplevelChange::Focus(focused) => {
|
||||||
|
let mut update_title = false;
|
||||||
|
|
||||||
|
if focused {
|
||||||
|
if let Some(item) = lock!(items).get_mut(&app_id) {
|
||||||
|
item.set_window_focused(window.id, true);
|
||||||
|
|
||||||
|
// might be switching focus between windows of same app
|
||||||
|
if item.windows.len() > 1 {
|
||||||
|
item.set_window_name(window.id, window.title.clone());
|
||||||
|
update_title = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), Report>(())
|
send_update(LauncherUpdate::Focus(app_id.clone(), focused)).await?;
|
||||||
|
|
||||||
|
if update_title {
|
||||||
|
send_update(LauncherUpdate::Title(app_id, window.id, window.title))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToplevelChange::Title(title) => {
|
||||||
|
if let Some(item) = lock!(items).get_mut(&app_id) {
|
||||||
|
item.set_window_name(window.id, title.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
send_update(LauncherUpdate::Title(app_id, window.id, title)).await?;
|
||||||
|
}
|
||||||
|
ToplevelChange::Fullscreen(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<(), mpsc::error::SendError<ModuleUpdateEvent<LauncherUpdate>>>(())
|
||||||
});
|
});
|
||||||
|
|
||||||
// listen to ui events
|
// listen to ui events
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
|
trace!("{:?}", event);
|
||||||
|
|
||||||
if let ItemEvent::OpenItem(app_id) = event {
|
if let ItemEvent::OpenItem(app_id) = event {
|
||||||
find_desktop_file(&app_id).map_or_else(
|
find_desktop_file(&app_id).map_or_else(
|
||||||
|| error!("Could not find desktop file for {}", app_id),
|
|| error!("Could not find desktop file for {}", app_id),
|
||||||
@@ -268,8 +283,6 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
send_async!(tx, ModuleUpdateEvent::ClosePopup);
|
|
||||||
|
|
||||||
let wl = wayland::get_client().await;
|
let wl = wayland::get_client().await;
|
||||||
let items = lock!(items);
|
let items = lock!(items);
|
||||||
|
|
||||||
@@ -282,12 +295,11 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
if let Some(window) =
|
let toplevels = read_lock!(wl.toplevels);
|
||||||
items.iter().find_map(|(_, item)| item.windows.get(&id))
|
let seat = wl.seats.first().expect("Failed to get Wayland seat");
|
||||||
{
|
if let Some((_top, handle)) = toplevels.get(&id) {
|
||||||
let seat = wl.get_seats().pop().expect("Failed to get Wayland seat");
|
handle.activate(seat);
|
||||||
window.focus(&seat);
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// roundtrip to immediately send activate event
|
// roundtrip to immediately send activate event
|
||||||
@@ -342,7 +354,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
&controller_tx,
|
&controller_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
container.add(&button.button);
|
container.append(&button.button);
|
||||||
buttons.insert(item.app_id, button);
|
buttons.insert(item.app_id, button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,12 +425,15 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
) -> Option<gtk::Box> {
|
) -> Option<gtk::Box> {
|
||||||
const MAX_WIDTH: i32 = 250;
|
const MAX_WIDTH: i32 = 250;
|
||||||
|
|
||||||
let container = gtk::Box::new(Orientation::Vertical, 0);
|
let container = gtk::Box::builder()
|
||||||
|
.orientation(Orientation::Vertical)
|
||||||
|
.name("popup-launcher")
|
||||||
|
.build();
|
||||||
|
|
||||||
// we need some content to force the container to have a size
|
// we need some content to force the container to have a size
|
||||||
let placeholder = Button::with_label("PLACEHOLDER");
|
let placeholder = Button::with_label("PLACEHOLDER");
|
||||||
placeholder.set_width_request(MAX_WIDTH);
|
placeholder.set_width_request(MAX_WIDTH);
|
||||||
container.add(&placeholder);
|
container.append(&placeholder);
|
||||||
|
|
||||||
let mut buttons = IndexMap::<String, IndexMap<usize, Button>>::new();
|
let mut buttons = IndexMap::<String, IndexMap<usize, Button>>::new();
|
||||||
|
|
||||||
@@ -441,8 +456,12 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let tx = controller_tx.clone();
|
let tx = controller_tx.clone();
|
||||||
button.connect_clicked(move |_| {
|
button.connect_clicked(move |button| {
|
||||||
try_send!(tx, ItemEvent::FocusWindow(win.id));
|
try_send!(tx, ItemEvent::FocusWindow(win.id));
|
||||||
|
|
||||||
|
if let Some(win) = button.window() {
|
||||||
|
win.hide();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,10 +525,9 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
if let Some(buttons) = buttons.get(&app_id) {
|
if let Some(buttons) = buttons.get(&app_id) {
|
||||||
for (_, button) in buttons {
|
for (_, button) in buttons {
|
||||||
button.style_context().add_class("popup-item");
|
button.style_context().add_class("popup-item");
|
||||||
container.add(button);
|
container.append(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.show_all();
|
|
||||||
container.set_width_request(MAX_WIDTH);
|
container.set_width_request(MAX_WIDTH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,9 +547,6 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
/// This is a hacky number derived from
|
/// This is a hacky number derived from
|
||||||
/// "what fits inside the 250px popup"
|
/// "what fits inside the 250px popup"
|
||||||
/// and probably won't hold up with wide fonts.
|
/// and probably won't hold up with wide fonts.
|
||||||
///
|
|
||||||
/// TODO: Migrate this to truncate system
|
|
||||||
///
|
|
||||||
fn clamp(str: &str) -> String {
|
fn clamp(str: &str) -> String {
|
||||||
const MAX_CHARS: usize = 24;
|
const MAX_CHARS: usize = 24;
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ pub enum OpenState {
|
|||||||
Open { focused: bool },
|
Open { focused: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ToplevelInfo> for OpenState {
|
|
||||||
fn from(info: &ToplevelInfo) -> Self {
|
|
||||||
Self::Open {
|
|
||||||
focused: info.focused,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenState {
|
impl OpenState {
|
||||||
|
/// Creates from `SwayNode`
|
||||||
|
pub const fn from_toplevel(toplevel: &ToplevelInfo) -> Self {
|
||||||
|
Self::Open {
|
||||||
|
focused: toplevel.active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates open with focused
|
/// Creates open with focused
|
||||||
pub const fn focused(focused: bool) -> Self {
|
pub const fn focused(focused: bool) -> Self {
|
||||||
Self::Open { focused }
|
Self::Open { focused }
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ pub fn create_module<TModule, TWidget, TSend, TRec>(
|
|||||||
id: usize,
|
id: usize,
|
||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
popup: &Arc<RwLock<Popup>>,
|
popup: &Arc<RwLock<Popup>>,
|
||||||
) -> Result<ModuleWidget<TWidget>>
|
) -> Result<TWidget>
|
||||||
where
|
where
|
||||||
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
|
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
|
||||||
TWidget: IsA<Widget>,
|
TWidget: IsA<Widget>,
|
||||||
@@ -145,21 +145,17 @@ where
|
|||||||
let name = TModule::name();
|
let name = TModule::name();
|
||||||
|
|
||||||
let module_parts = module.into_widget(context, info)?;
|
let module_parts = module.into_widget(context, info)?;
|
||||||
module_parts.widget.style_context().add_class(name);
|
module_parts.widget.set_widget_name(name);
|
||||||
|
|
||||||
let mut has_popup = false;
|
let mut has_popup = false;
|
||||||
if let Some(popup_content) = module_parts.popup.clone() {
|
if let Some(popup_content) = module_parts.popup {
|
||||||
popup_content
|
|
||||||
.style_context()
|
|
||||||
.add_class(&format!("popup-{name}"));
|
|
||||||
|
|
||||||
register_popup_content(popup, id, popup_content);
|
register_popup_content(popup, id, popup_content);
|
||||||
has_popup = true;
|
has_popup = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup);
|
setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup);
|
||||||
|
|
||||||
Ok(module_parts)
|
Ok(module_parts.widget)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers the popup content with the popup.
|
/// Registers the popup content with the popup.
|
||||||
@@ -238,32 +234,6 @@ fn setup_receiver<TSend>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_widget_identifiers<TWidget: IsA<Widget>>(
|
|
||||||
widget_parts: &ModuleWidget<TWidget>,
|
|
||||||
common: &CommonConfig,
|
|
||||||
) {
|
|
||||||
if let Some(ref name) = common.name {
|
|
||||||
widget_parts.widget.set_widget_name(name);
|
|
||||||
|
|
||||||
if let Some(ref popup) = widget_parts.popup {
|
|
||||||
popup.set_widget_name(&format!("popup-{name}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref class) = common.class {
|
|
||||||
// gtk counts classes with spaces as the same class
|
|
||||||
for part in class.split(' ') {
|
|
||||||
widget_parts.widget.style_context().add_class(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref popup) = widget_parts.popup {
|
|
||||||
for part in class.split(' ') {
|
|
||||||
popup.style_context().add_class(&format!("popup-{part}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes a widget and adds it into a new `gtk::EventBox`.
|
/// Takes a widget and adds it into a new `gtk::EventBox`.
|
||||||
/// The event box container is returned.
|
/// The event box container is returned.
|
||||||
pub fn wrap_widget<W: IsA<Widget>>(
|
pub fn wrap_widget<W: IsA<Widget>>(
|
||||||
@@ -271,14 +241,14 @@ pub fn wrap_widget<W: IsA<Widget>>(
|
|||||||
common: CommonConfig,
|
common: CommonConfig,
|
||||||
orientation: Orientation,
|
orientation: Orientation,
|
||||||
) -> EventBox {
|
) -> EventBox {
|
||||||
let transition_type = common
|
let revealer = Revealer::builder()
|
||||||
|
.transition_type(
|
||||||
|
common
|
||||||
.transition_type
|
.transition_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&TransitionType::SlideStart)
|
.unwrap_or(&TransitionType::SlideStart)
|
||||||
.to_revealer_transition_type(orientation);
|
.to_revealer_transition_type(orientation),
|
||||||
|
)
|
||||||
let revealer = Revealer::builder()
|
|
||||||
.transition_type(transition_type)
|
|
||||||
.transition_duration(common.transition_duration.unwrap_or(250))
|
.transition_duration(common.transition_duration.unwrap_or(250))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -287,9 +257,9 @@ pub fn wrap_widget<W: IsA<Widget>>(
|
|||||||
|
|
||||||
let container = EventBox::new();
|
let container = EventBox::new();
|
||||||
container.add_events(EventMask::SCROLL_MASK);
|
container.add_events(EventMask::SCROLL_MASK);
|
||||||
container.add(&revealer);
|
container.append(&revealer);
|
||||||
|
|
||||||
common.install_events(&container, &revealer);
|
common.install(&container, &revealer);
|
||||||
|
|
||||||
container
|
container
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track};
|
use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track};
|
||||||
use crate::gtk_helpers::add_class;
|
|
||||||
use crate::image::{new_icon_button, new_icon_label, ImageProvider};
|
use crate::image::{new_icon_button, new_icon_label, ImageProvider};
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
@@ -14,6 +13,7 @@ use regex::Regex;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use glib::signal::Inhibit;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
@@ -136,7 +136,7 @@ impl Module<Button> for MusicModule {
|
|||||||
PlayerCommand::Play => client.play(),
|
PlayerCommand::Play => client.play(),
|
||||||
PlayerCommand::Pause => client.pause(),
|
PlayerCommand::Pause => client.pause(),
|
||||||
PlayerCommand::Next => client.next(),
|
PlayerCommand::Next => client.next(),
|
||||||
PlayerCommand::Volume(vol) => client.set_volume_percent(vol),
|
PlayerCommand::Volume(vol) => client.set_volume_percent(vol), // .unwrap_or_else(|_| error!("Failed to update player volume")),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
@@ -156,9 +156,7 @@ impl Module<Button> for MusicModule {
|
|||||||
) -> Result<ModuleWidget<Button>> {
|
) -> Result<ModuleWidget<Button>> {
|
||||||
let button = Button::new();
|
let button = Button::new();
|
||||||
let button_contents = gtk::Box::new(Orientation::Horizontal, 5);
|
let button_contents = gtk::Box::new(Orientation::Horizontal, 5);
|
||||||
add_class(&button_contents, "contents");
|
button.append(&button_contents);
|
||||||
|
|
||||||
button.add(&button_contents);
|
|
||||||
|
|
||||||
let icon_play = new_icon_label(&self.icons.play, info.icon_theme, self.icon_size);
|
let icon_play = new_icon_label(&self.icons.play, info.icon_theme, self.icon_size);
|
||||||
let icon_pause = new_icon_label(&self.icons.pause, info.icon_theme, self.icon_size);
|
let icon_pause = new_icon_label(&self.icons.pause, info.icon_theme, self.icon_size);
|
||||||
@@ -170,9 +168,9 @@ impl Module<Button> for MusicModule {
|
|||||||
truncate.truncate_label(&label);
|
truncate.truncate_label(&label);
|
||||||
}
|
}
|
||||||
|
|
||||||
button_contents.add(&icon_pause);
|
button_contents.append(&icon_pause);
|
||||||
button_contents.add(&icon_play);
|
button_contents.append(&icon_play);
|
||||||
button_contents.add(&label);
|
button_contents.append(&label);
|
||||||
|
|
||||||
let orientation = info.bar_position.get_orientation();
|
let orientation = info.bar_position.get_orientation();
|
||||||
|
|
||||||
@@ -241,13 +239,17 @@ impl Module<Button> for MusicModule {
|
|||||||
) -> Option<gtk::Box> {
|
) -> Option<gtk::Box> {
|
||||||
let icon_theme = info.icon_theme;
|
let icon_theme = info.icon_theme;
|
||||||
|
|
||||||
let container = gtk::Box::new(Orientation::Horizontal, 10);
|
let container = gtk::Box::builder()
|
||||||
|
.orientation(Orientation::Horizontal)
|
||||||
|
.spacing(10)
|
||||||
|
.name("popup-music")
|
||||||
|
.build();
|
||||||
|
|
||||||
let album_image = gtk::Image::builder()
|
let album_image = gtk::Image::builder()
|
||||||
.width_request(128)
|
.width_request(128)
|
||||||
.height_request(128)
|
.height_request(128)
|
||||||
|
.name("album-art")
|
||||||
.build();
|
.build();
|
||||||
add_class(&album_image, "album-art");
|
|
||||||
|
|
||||||
let icons = self.icons;
|
let icons = self.icons;
|
||||||
|
|
||||||
@@ -256,52 +258,54 @@ impl Module<Button> for MusicModule {
|
|||||||
let album_label = IconLabel::new(&icons.album, None, icon_theme);
|
let album_label = IconLabel::new(&icons.album, None, icon_theme);
|
||||||
let artist_label = IconLabel::new(&icons.artist, None, icon_theme);
|
let artist_label = IconLabel::new(&icons.artist, None, icon_theme);
|
||||||
|
|
||||||
add_class(&title_label.container, "title");
|
title_label.container.set_widget_name("title");
|
||||||
add_class(&album_label.container, "album");
|
album_label.container.set_widget_name("album");
|
||||||
add_class(&artist_label.container, "artist");
|
artist_label.container.set_widget_name("artist");
|
||||||
|
|
||||||
info_box.add(&title_label.container);
|
info_box.append(&title_label.container);
|
||||||
info_box.add(&album_label.container);
|
info_box.append(&album_label.container);
|
||||||
info_box.add(&artist_label.container);
|
info_box.append(&artist_label.container);
|
||||||
|
|
||||||
let controls_box = gtk::Box::new(Orientation::Horizontal, 0);
|
let controls_box = gtk::Box::builder().name("controls").build();
|
||||||
add_class(&controls_box, "controls");
|
|
||||||
|
|
||||||
let btn_prev = new_icon_button(&icons.prev, icon_theme, self.icon_size);
|
let btn_prev = new_icon_button(&icons.prev, icon_theme, self.icon_size);
|
||||||
add_class(&btn_prev, "btn-prev");
|
btn_prev.set_widget_name("btn-prev");
|
||||||
|
|
||||||
let btn_play = new_icon_button(&icons.play, icon_theme, self.icon_size);
|
let btn_play = new_icon_button(&icons.play, icon_theme, self.icon_size);
|
||||||
add_class(&btn_play, "btn-play");
|
btn_play.set_widget_name("btn-play");
|
||||||
|
|
||||||
let btn_pause = new_icon_button(&icons.pause, icon_theme, self.icon_size);
|
let btn_pause = new_icon_button(&icons.pause, icon_theme, self.icon_size);
|
||||||
add_class(&btn_pause, "btn-pause");
|
btn_pause.set_widget_name("btn-pause");
|
||||||
|
|
||||||
let btn_next = new_icon_button(&icons.next, icon_theme, self.icon_size);
|
let btn_next = new_icon_button(&icons.next, icon_theme, self.icon_size);
|
||||||
add_class(&btn_next, "btn-next");
|
btn_next.set_widget_name("btn-next");
|
||||||
|
|
||||||
controls_box.add(&btn_prev);
|
controls_box.append(&btn_prev);
|
||||||
controls_box.add(&btn_play);
|
controls_box.append(&btn_play);
|
||||||
controls_box.add(&btn_pause);
|
controls_box.append(&btn_pause);
|
||||||
controls_box.add(&btn_next);
|
controls_box.append(&btn_next);
|
||||||
|
|
||||||
info_box.add(&controls_box);
|
info_box.append(&controls_box);
|
||||||
|
|
||||||
let volume_box = gtk::Box::new(Orientation::Vertical, 5);
|
let volume_box = gtk::Box::builder()
|
||||||
add_class(&volume_box, "volume");
|
.orientation(Orientation::Vertical)
|
||||||
|
.spacing(5)
|
||||||
|
.name("volume")
|
||||||
|
.build();
|
||||||
|
|
||||||
let volume_slider = Scale::with_range(Orientation::Vertical, 0.0, 100.0, 5.0);
|
let volume_slider = Scale::with_range(Orientation::Vertical, 0.0, 100.0, 5.0);
|
||||||
volume_slider.set_inverted(true);
|
volume_slider.set_inverted(true);
|
||||||
add_class(&volume_slider, "slider");
|
volume_slider.set_widget_name("slider");
|
||||||
|
|
||||||
let volume_icon = new_icon_label(&icons.volume, icon_theme, self.icon_size);
|
let volume_icon = new_icon_label(&icons.volume, icon_theme, self.icon_size);
|
||||||
add_class(&volume_icon, "icon");
|
volume_icon.style_context().add_class("icon");
|
||||||
|
|
||||||
volume_box.pack_start(&volume_slider, true, true, 0);
|
volume_box.pack_start(&volume_slider, true, true, 0);
|
||||||
volume_box.pack_end(&volume_icon, false, false, 0);
|
volume_box.pack_end(&volume_icon, false, false, 0);
|
||||||
|
|
||||||
container.add(&album_image);
|
container.append(&album_image);
|
||||||
container.add(&info_box);
|
container.append(&info_box);
|
||||||
container.add(&volume_box);
|
container.append(&volume_box);
|
||||||
|
|
||||||
let tx_prev = tx.clone();
|
let tx_prev = tx.clone();
|
||||||
btn_prev.connect_clicked(move |_| {
|
btn_prev.connect_clicked(move |_| {
|
||||||
@@ -329,8 +333,6 @@ impl Module<Button> for MusicModule {
|
|||||||
Inhibit(false)
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
container.show_all();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let icon_theme = icon_theme.clone();
|
let icon_theme = icon_theme.clone();
|
||||||
let image_size = self.cover_image_size;
|
let image_size = self.cover_image_size;
|
||||||
@@ -458,11 +460,11 @@ impl IconLabel {
|
|||||||
let icon = new_icon_label(icon_input, icon_theme, 24);
|
let icon = new_icon_label(icon_input, icon_theme, 24);
|
||||||
let label = Label::new(label);
|
let label = Label::new(label);
|
||||||
|
|
||||||
add_class(&icon, "icon");
|
icon.style_context().add_class("icon");
|
||||||
add_class(&label, "label");
|
label.style_context().add_class("label");
|
||||||
|
|
||||||
container.add(&icon);
|
container.append(&icon);
|
||||||
container.add(&label);
|
container.append(&label);
|
||||||
|
|
||||||
Self { label, container }
|
Self { label, container }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::gtk_helpers::add_class;
|
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::send_async;
|
use crate::send_async;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -194,12 +193,13 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||||||
let mut labels = Vec::new();
|
let mut labels = Vec::new();
|
||||||
|
|
||||||
for format in &self.format {
|
for format in &self.format {
|
||||||
let label = Label::builder().label(format).use_markup(true).build();
|
let label = Label::builder()
|
||||||
|
.label(format)
|
||||||
add_class(&label, "item");
|
.use_markup(true)
|
||||||
|
.name("item")
|
||||||
|
.build();
|
||||||
label.set_angle(info.bar_position.get_angle());
|
label.set_angle(info.bar_position.get_angle());
|
||||||
|
container.append(&label);
|
||||||
container.add(&label);
|
|
||||||
labels.push(label);
|
labels.push(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,20 +27,15 @@ pub struct TrayModule {
|
|||||||
/// Attempts to get a GTK `Image` component
|
/// Attempts to get a GTK `Image` component
|
||||||
/// for the status notifier item's icon.
|
/// for the status notifier item's icon.
|
||||||
fn get_image_from_icon_name(item: &StatusNotifierItem) -> Option<Image> {
|
fn get_image_from_icon_name(item: &StatusNotifierItem) -> Option<Image> {
|
||||||
let theme = item
|
item.icon_theme_path.as_ref().and_then(|path| {
|
||||||
.icon_theme_path
|
|
||||||
.as_ref()
|
|
||||||
.map(|path| {
|
|
||||||
let theme = IconTheme::new();
|
let theme = IconTheme::new();
|
||||||
theme.append_search_path(path);
|
theme.append_search_path(path);
|
||||||
theme
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
item.icon_name.as_ref().and_then(|icon_name| {
|
item.icon_name.as_ref().and_then(|icon_name| {
|
||||||
let icon_info = theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
|
let icon_info = theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
|
||||||
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref()))
|
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref()))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to get an image from the item pixmap.
|
/// Attempts to get an image from the item pixmap.
|
||||||
@@ -96,7 +91,7 @@ fn get_menu_items(
|
|||||||
let menu = Menu::new();
|
let menu = Menu::new();
|
||||||
get_menu_items(&item_info.submenu, &tx.clone(), id, path)
|
get_menu_items(&item_info.submenu, &tx.clone(), id, path)
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|item| menu.add(item));
|
.for_each(|item| menu.append(item));
|
||||||
|
|
||||||
builder = builder.submenu(&menu);
|
builder = builder.submenu(&menu);
|
||||||
}
|
}
|
||||||
@@ -202,11 +197,11 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
},
|
},
|
||||||
|image| {
|
|image| {
|
||||||
image.set_widget_name(address.as_str());
|
image.set_widget_name(address.as_str());
|
||||||
menu_item.add(&image);
|
menu_item.append(&image);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
container.add(&menu_item);
|
container.append(&menu_item);
|
||||||
menu_item.show_all();
|
menu_item.show_all();
|
||||||
menu_item
|
menu_item
|
||||||
});
|
});
|
||||||
@@ -221,7 +216,7 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
&menu_path,
|
&menu_path,
|
||||||
)
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|item| menu.add(item));
|
.for_each(|item| menu.append(item));
|
||||||
menu_item.set_submenu(Some(&menu));
|
menu_item.set_submenu(Some(&menu));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::clients::upower::get_display_proxy;
|
use crate::clients::upower::get_display_proxy;
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::gtk_helpers::add_class;
|
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
@@ -145,20 +144,20 @@ impl Module<gtk::Box> for UpowerModule {
|
|||||||
info: &ModuleInfo,
|
info: &ModuleInfo,
|
||||||
) -> Result<ModuleWidget<gtk::Box>> {
|
) -> Result<ModuleWidget<gtk::Box>> {
|
||||||
let icon_theme = info.icon_theme.clone();
|
let icon_theme = info.icon_theme.clone();
|
||||||
let icon = gtk::Image::new();
|
let icon = gtk::Image::builder().name("icon").build();
|
||||||
add_class(&icon, "icon");
|
|
||||||
|
|
||||||
let label = Label::builder()
|
let label = Label::builder()
|
||||||
.label(&self.format)
|
.label(&self.format)
|
||||||
.use_markup(true)
|
.use_markup(true)
|
||||||
|
.name("label")
|
||||||
.build();
|
.build();
|
||||||
add_class(&label, "label");
|
|
||||||
|
|
||||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
let container = gtk::Box::builder()
|
||||||
add_class(&container, "upower");
|
.orientation(Orientation::Horizontal)
|
||||||
|
.name("upower")
|
||||||
|
.build();
|
||||||
|
|
||||||
let button = Button::new();
|
let button = Button::builder().name("button").build();
|
||||||
add_class(&button, "button");
|
|
||||||
|
|
||||||
button.add(&label);
|
button.add(&label);
|
||||||
container.add(&button);
|
container.add(&button);
|
||||||
@@ -208,10 +207,11 @@ impl Module<gtk::Box> for UpowerModule {
|
|||||||
{
|
{
|
||||||
let container = gtk::Box::builder()
|
let container = gtk::Box::builder()
|
||||||
.orientation(Orientation::Horizontal)
|
.orientation(Orientation::Horizontal)
|
||||||
|
.name("popup-upower")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let label = Label::new(None);
|
let label = Label::builder().name("upower-details").build();
|
||||||
add_class(&label, "upower-details");
|
container.add(&label);
|
||||||
|
|
||||||
rx.attach(None, move |properties| {
|
rx.attach(None, move |properties| {
|
||||||
let mut format = String::new();
|
let mut format = String::new();
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
icon_size,
|
icon_size,
|
||||||
&context.controller_tx,
|
&context.controller_tx,
|
||||||
);
|
);
|
||||||
container.add(&item);
|
container.append(&item);
|
||||||
|
|
||||||
button_map.insert(workspace.name, item);
|
button_map.insert(workspace.name, item);
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,6 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
reorder_workspaces(&container);
|
reorder_workspaces(&container);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.show_all();
|
|
||||||
has_initialized = true;
|
has_initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,7 +222,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
&context.controller_tx,
|
&context.controller_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
container.add(&item);
|
container.append(&item);
|
||||||
if self.sort == SortOrder::Alphanumeric {
|
if self.sort == SortOrder::Alphanumeric {
|
||||||
reorder_workspaces(&container);
|
reorder_workspaces(&container);
|
||||||
}
|
}
|
||||||
@@ -248,7 +247,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
&context.controller_tx,
|
&context.controller_tx,
|
||||||
);
|
);
|
||||||
|
|
||||||
container.add(&item);
|
container.append(&item);
|
||||||
|
|
||||||
if self.sort == SortOrder::Alphanumeric {
|
if self.sort == SortOrder::Alphanumeric {
|
||||||
reorder_workspaces(&container);
|
reorder_workspaces(&container);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use glib::signal::Inhibit;
|
||||||
|
|
||||||
use crate::config::BarPosition;
|
use crate::config::BarPosition;
|
||||||
use crate::modules::ModuleInfo;
|
use crate::modules::ModuleInfo;
|
||||||
@@ -121,7 +122,7 @@ impl Popup {
|
|||||||
|
|
||||||
if let Some(content) = self.cache.get(&key) {
|
if let Some(content) = self.cache.get(&key) {
|
||||||
content.style_context().add_class("popup");
|
content.style_context().add_class("popup");
|
||||||
self.window.add(content);
|
self.window.append(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user