Add client side decorations to toplevels
These simple decorations will be rendered only when the host compositor doesn't support server side decorations and the X11 window does not render its own decorations. Closes #31
This commit is contained in:
parent
56256a1340
commit
b39388d91a
10 changed files with 1087 additions and 49 deletions
180
Cargo.lock
generated
180
Cargo.lock
generated
|
|
@ -2,6 +2,28 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ab_glyph"
|
||||||
|
version = "0.2.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
|
||||||
|
dependencies = [
|
||||||
|
"ab_glyph_rasterizer",
|
||||||
|
"owned_ttf_parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ab_glyph_rasterizer"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.12"
|
version = "0.8.12"
|
||||||
|
|
@ -23,12 +45,30 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.98"
|
version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.72.1"
|
version = "0.72.1"
|
||||||
|
|
@ -61,6 +101,12 @@ version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.24"
|
version = "1.2.24"
|
||||||
|
|
@ -96,6 +142,15 @@ dependencies = [
|
||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cursor-icon"
|
name = "cursor-icon"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
@ -218,12 +273,47 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fontdue"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e57e16b3fe8ff4364c0661fdaac543fb38b29ea9bc9c2f45612d90adf931d2b"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"ttf-parser 0.21.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -244,6 +334,11 @@ name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hecs"
|
name = "hecs"
|
||||||
|
|
@ -379,6 +474,16 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
|
@ -432,12 +537,34 @@ version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "owned_ttf_parser"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
|
||||||
|
dependencies = [
|
||||||
|
"ttf-parser 0.25.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.17.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -598,6 +725,12 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
@ -635,6 +768,12 @@ version = "0.9.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strict-num"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|
@ -725,6 +864,32 @@ dependencies = [
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg-if",
|
||||||
|
"log",
|
||||||
|
"png",
|
||||||
|
"tiny-skia-path",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-skia-path"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"bytemuck",
|
||||||
|
"strict-num",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
|
|
@ -742,6 +907,18 @@ dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttf-parser"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttf-parser"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -1135,8 +1312,10 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||||
name = "xwayland-satellite"
|
name = "xwayland-satellite"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ab_glyph",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
|
"fontdue",
|
||||||
"hecs",
|
"hecs",
|
||||||
"log",
|
"log",
|
||||||
"macros",
|
"macros",
|
||||||
|
|
@ -1146,6 +1325,7 @@ dependencies = [
|
||||||
"sd-notify",
|
"sd-notify",
|
||||||
"smithay-client-toolkit",
|
"smithay-client-toolkit",
|
||||||
"testwl",
|
"testwl",
|
||||||
|
"tiny-skia",
|
||||||
"vergen-gitcl",
|
"vergen-gitcl",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ sd-notify = { version = "0.4.2", optional = true }
|
||||||
macros = { version = "0.1.0", path = "macros" }
|
macros = { version = "0.1.0", path = "macros" }
|
||||||
hecs = { version = "0.10.5", features = ["macros"] }
|
hecs = { version = "0.10.5", features = ["macros"] }
|
||||||
num_enum = "0.7.4"
|
num_enum = "0.7.4"
|
||||||
|
tiny-skia = "0.11.4"
|
||||||
|
ab_glyph = "0.2.32"
|
||||||
|
fontdue = "0.9.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
||||||
BIN
OpenSans-Regular.ttf
Normal file
BIN
OpenSans-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -1,3 +1,5 @@
|
||||||
|
use super::decoration::DecorationMarker;
|
||||||
|
|
||||||
use super::ObjectEvent;
|
use super::ObjectEvent;
|
||||||
use hecs::{Entity, World};
|
use hecs::{Entity, World};
|
||||||
use smithay_client_toolkit::{
|
use smithay_client_toolkit::{
|
||||||
|
|
@ -19,7 +21,8 @@ use wayland_client::protocol::{
|
||||||
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
|
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
|
||||||
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_region::WlRegion,
|
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_region::WlRegion,
|
||||||
wl_registry::WlRegistry, wl_seat::WlSeat, wl_shm::WlShm, wl_shm_pool::WlShmPool,
|
wl_registry::WlRegistry, wl_seat::WlSeat, wl_shm::WlShm, wl_shm_pool::WlShmPool,
|
||||||
wl_surface::WlSurface, wl_touch::WlTouch,
|
wl_subcompositor::WlSubcompositor, wl_subsurface::WlSubsurface, wl_surface::WlSurface,
|
||||||
|
wl_touch::WlTouch,
|
||||||
};
|
};
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
delegate_noop, event_created_child,
|
delegate_noop, event_created_child,
|
||||||
|
|
@ -164,6 +167,7 @@ impl MyWorld {
|
||||||
pub type Event<T> = <T as Proxy>::Event;
|
pub type Event<T> = <T as Proxy>::Event;
|
||||||
|
|
||||||
delegate_noop!(MyWorld: WlCompositor);
|
delegate_noop!(MyWorld: WlCompositor);
|
||||||
|
delegate_noop!(MyWorld: WlSubcompositor);
|
||||||
delegate_noop!(MyWorld: WlRegion);
|
delegate_noop!(MyWorld: WlRegion);
|
||||||
delegate_noop!(MyWorld: ignore WlShm);
|
delegate_noop!(MyWorld: ignore WlShm);
|
||||||
delegate_noop!(MyWorld: ignore ZwpLinuxDmabufV1);
|
delegate_noop!(MyWorld: ignore ZwpLinuxDmabufV1);
|
||||||
|
|
@ -179,8 +183,8 @@ delegate_noop!(MyWorld: ZwpTabletManagerV2);
|
||||||
delegate_noop!(MyWorld: XdgActivationV1);
|
delegate_noop!(MyWorld: XdgActivationV1);
|
||||||
delegate_noop!(MyWorld: ZxdgDecorationManagerV1);
|
delegate_noop!(MyWorld: ZxdgDecorationManagerV1);
|
||||||
delegate_noop!(MyWorld: WpFractionalScaleManagerV1);
|
delegate_noop!(MyWorld: WpFractionalScaleManagerV1);
|
||||||
delegate_noop!(MyWorld: ignore ZxdgToplevelDecorationV1);
|
|
||||||
delegate_noop!(MyWorld: ZwpPrimarySelectionDeviceManagerV1);
|
delegate_noop!(MyWorld: ZwpPrimarySelectionDeviceManagerV1);
|
||||||
|
delegate_noop!(MyWorld: WlSubsurface);
|
||||||
|
|
||||||
impl Dispatch<WlRegistry, GlobalListContents> for MyWorld {
|
impl Dispatch<WlRegistry, GlobalListContents> for MyWorld {
|
||||||
fn event(
|
fn event(
|
||||||
|
|
@ -236,6 +240,18 @@ impl Dispatch<WlCallback, server::wl_callback::WlCallback> for MyWorld {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dispatch<WlSurface, DecorationMarker> for MyWorld {
|
||||||
|
fn event(
|
||||||
|
_: &mut Self,
|
||||||
|
_: &WlSurface,
|
||||||
|
_: <WlSurface as Proxy>::Event,
|
||||||
|
_: &DecorationMarker,
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! push_events {
|
macro_rules! push_events {
|
||||||
($type:ident) => {
|
($type:ident) => {
|
||||||
impl Dispatch<$type, Entity> for MyWorld {
|
impl Dispatch<$type, Entity> for MyWorld {
|
||||||
|
|
@ -270,6 +286,7 @@ push_events!(WlTouch);
|
||||||
push_events!(ZwpConfinedPointerV1);
|
push_events!(ZwpConfinedPointerV1);
|
||||||
push_events!(ZwpLockedPointerV1);
|
push_events!(ZwpLockedPointerV1);
|
||||||
push_events!(WpFractionalScaleV1);
|
push_events!(WpFractionalScaleV1);
|
||||||
|
push_events!(ZxdgToplevelDecorationV1);
|
||||||
|
|
||||||
pub(crate) struct LateInitObjectKey<P: Proxy> {
|
pub(crate) struct LateInitObjectKey<P: Proxy> {
|
||||||
key: OnceLock<Entity>,
|
key: OnceLock<Entity>,
|
||||||
|
|
|
||||||
480
src/server/decoration.rs
Normal file
480
src/server/decoration.rs
Normal file
|
|
@ -0,0 +1,480 @@
|
||||||
|
use crate::server::{InnerServerState, ServerState, SurfaceRole};
|
||||||
|
use crate::{X11Selection, XConnection};
|
||||||
|
|
||||||
|
use ab_glyph::{Font, FontRef, Glyph, PxScaleFont, ScaleFont};
|
||||||
|
use hecs::{CommandBuffer, Entity, World};
|
||||||
|
use log::{error, warn};
|
||||||
|
use smithay_client_toolkit::registry::SimpleGlobal;
|
||||||
|
use smithay_client_toolkit::shm::slot::SlotPool;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use tiny_skia::{Color, Paint, PathBuilder, Pixmap, Stroke, Transform};
|
||||||
|
use tiny_skia::{ColorU8, Rect};
|
||||||
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
|
use wayland_client::protocol::wl_shm;
|
||||||
|
use wayland_client::protocol::wl_subsurface::WlSubsurface;
|
||||||
|
use wayland_client::protocol::wl_surface::WlSurface;
|
||||||
|
use wayland_client::Proxy;
|
||||||
|
use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||||
|
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1;
|
||||||
|
use wayland_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel;
|
||||||
|
use xcb::x;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DecorationsData {
|
||||||
|
pub wl: Option<ZxdgToplevelDecorationV1>,
|
||||||
|
// Boxed to avoid making ToplevelData so much bigger than PopupData
|
||||||
|
pub satellite: Option<Box<DecorationsDataSatellite>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DecorationMarker {
|
||||||
|
pub parent: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DecorationsDataSatellite {
|
||||||
|
surface: WlSurface,
|
||||||
|
subsurface: WlSubsurface,
|
||||||
|
pool: Entity,
|
||||||
|
viewport: WpViewport,
|
||||||
|
scale: f32,
|
||||||
|
pixmap: Pixmap,
|
||||||
|
x_data: DecorationsBox,
|
||||||
|
title: Option<String>,
|
||||||
|
title_rect: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DecorationsDataSatellite {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.subsurface.destroy();
|
||||||
|
self.surface.destroy();
|
||||||
|
self.viewport.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecorationsDataSatellite {
|
||||||
|
pub const TITLEBAR_HEIGHT: i32 = 25;
|
||||||
|
|
||||||
|
pub fn try_new(
|
||||||
|
state: &InnerServerState<impl X11Selection>,
|
||||||
|
parent: &WlSurface,
|
||||||
|
title: Option<&str>,
|
||||||
|
) -> Option<(Box<Self>, Option<CommandBuffer>)> {
|
||||||
|
let mut new_pool = None;
|
||||||
|
let mut query = state.world.query::<&SlotPool>();
|
||||||
|
let pool_entity = if let Some((pool_entity, _)) = query.into_iter().next() {
|
||||||
|
pool_entity
|
||||||
|
} else {
|
||||||
|
new_pool = Some(
|
||||||
|
SlotPool::new(1, &SimpleGlobal::from_bound(state.shm.clone()))
|
||||||
|
.inspect_err(|e| {
|
||||||
|
warn!("Couldn't create slot pool for decorations: {e:?}");
|
||||||
|
})
|
||||||
|
.ok()?,
|
||||||
|
);
|
||||||
|
state.world.reserve_entity()
|
||||||
|
};
|
||||||
|
|
||||||
|
let surface = state.compositor.create_surface(
|
||||||
|
&state.qh,
|
||||||
|
DecorationMarker {
|
||||||
|
parent: parent.data().copied().unwrap(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let subsurface = {
|
||||||
|
state
|
||||||
|
.subcompositor
|
||||||
|
.get_subsurface(&surface, parent, &state.qh, ())
|
||||||
|
};
|
||||||
|
subsurface.set_desync();
|
||||||
|
subsurface.set_position(0, -Self::TITLEBAR_HEIGHT);
|
||||||
|
let viewport = state.viewporter.get_viewport(&surface, &state.qh, ());
|
||||||
|
|
||||||
|
Some((
|
||||||
|
Self {
|
||||||
|
surface,
|
||||||
|
subsurface,
|
||||||
|
pool: pool_entity,
|
||||||
|
viewport,
|
||||||
|
x_data: DecorationsBox::default(),
|
||||||
|
pixmap: Pixmap::new(1, 1).unwrap(),
|
||||||
|
scale: 1.0,
|
||||||
|
title: title.map(str::to_string),
|
||||||
|
title_rect: Rect::from_ltrb(0.0, 0.0, 0.0, 0.0).unwrap(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
new_pool.map(|p| {
|
||||||
|
let mut buf = CommandBuffer::new();
|
||||||
|
buf.insert_one(pool_entity, p);
|
||||||
|
buf
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pool<'a>(&self, world: &'a World) -> hecs::RefMut<'a, SlotPool> {
|
||||||
|
world.get::<&mut SlotPool>(self.pool).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_buffer(&mut self, world: &World) {
|
||||||
|
let mut pool = self.pool(world);
|
||||||
|
let (buffer, data) = match pool.create_buffer(
|
||||||
|
self.pixmap.width() as i32,
|
||||||
|
self.pixmap.height() as i32,
|
||||||
|
self.pixmap.width() as i32 * 4,
|
||||||
|
wl_shm::Format::Xrgb8888,
|
||||||
|
) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to create buffer for decorations: {err:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
draw_pixmap_to_buffer(&self.pixmap, data);
|
||||||
|
buffer.attach_to(&self.surface).unwrap();
|
||||||
|
self.surface.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_decorations(&mut self, world: &World, width: i32, parent_scale_factor: f32) {
|
||||||
|
if width <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scale = parent_scale_factor;
|
||||||
|
let mut drawn_width = (width as f32 * self.scale).ceil() as i32;
|
||||||
|
let drawn_height = (Self::TITLEBAR_HEIGHT as f32 * self.scale).ceil() as i32;
|
||||||
|
|
||||||
|
let x = x_pixmap(drawn_height as u32, self.scale, self.x_data.hovered);
|
||||||
|
if x.width() > drawn_width as u32 {
|
||||||
|
drawn_width = x.width() as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = self.title.as_ref().and_then(|t| {
|
||||||
|
let width = (drawn_width as u32).saturating_sub(x.width());
|
||||||
|
if width > 0 {
|
||||||
|
title_pixmap(t, width, drawn_height as u32, self.scale)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw the bar and its components
|
||||||
|
let mut bar = Pixmap::new(drawn_width as u32, drawn_height as u32).unwrap();
|
||||||
|
bar.fill(Color::WHITE);
|
||||||
|
|
||||||
|
if let Some(title) = title {
|
||||||
|
bar.draw_pixmap(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
title.as_ref(),
|
||||||
|
&Default::default(),
|
||||||
|
Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
self.title_rect =
|
||||||
|
Rect::from_xywh(0.0, 0.0, title.width() as f32, title.height() as f32).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
bar.draw_pixmap(
|
||||||
|
(bar.width() - x.width()) as i32,
|
||||||
|
0,
|
||||||
|
x.as_ref(),
|
||||||
|
&Default::default(),
|
||||||
|
Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
self.x_data = DecorationsBox {
|
||||||
|
rect: Rect::from_ltrb(
|
||||||
|
width as f32 - Self::TITLEBAR_HEIGHT as f32,
|
||||||
|
0.0,
|
||||||
|
width as f32,
|
||||||
|
Self::TITLEBAR_HEIGHT as f32,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
hovered: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pixmap = bar;
|
||||||
|
self.viewport.set_destination(width, Self::TITLEBAR_HEIGHT);
|
||||||
|
self.update_buffer(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redraw_x_pixmap(&mut self, world: &World) {
|
||||||
|
let x = x_pixmap(self.pixmap.height(), self.scale, self.x_data.hovered);
|
||||||
|
|
||||||
|
self.pixmap.draw_pixmap(
|
||||||
|
(self.pixmap.width() - x.width()) as i32,
|
||||||
|
0,
|
||||||
|
x.as_ref(),
|
||||||
|
&Default::default(),
|
||||||
|
Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.surface.damage_buffer(
|
||||||
|
(self.pixmap.width() - x.width()) as i32,
|
||||||
|
0,
|
||||||
|
x.width() as i32,
|
||||||
|
x.height() as i32,
|
||||||
|
);
|
||||||
|
self.update_buffer(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title(&mut self, world: &World, title: &str) {
|
||||||
|
self.title = Some(title.to_string());
|
||||||
|
|
||||||
|
// Don't draw title if there's not enough space
|
||||||
|
let title_pixmap = title_pixmap(
|
||||||
|
title,
|
||||||
|
self.pixmap.width() - self.x_data.rect.width() as u32,
|
||||||
|
self.pixmap.height(),
|
||||||
|
self.scale,
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_title_rect = title_pixmap
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| Rect::from_xywh(0.0, 0.0, p.width() as f32, p.height() as f32).unwrap())
|
||||||
|
.unwrap_or_else(|| Rect::from_ltrb(0.0, 0.0, 0.0, 0.0).unwrap());
|
||||||
|
|
||||||
|
let last_title_rect = std::mem::replace(&mut self.title_rect, new_title_rect);
|
||||||
|
|
||||||
|
// Clear last title with white
|
||||||
|
let mut paint = Paint::default();
|
||||||
|
paint.set_color(Color::WHITE);
|
||||||
|
self.pixmap
|
||||||
|
.fill_rect(last_title_rect, &paint, Transform::identity(), None);
|
||||||
|
|
||||||
|
if let Some(p) = title_pixmap.as_ref() {
|
||||||
|
self.pixmap.draw_pixmap(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
p.as_ref(),
|
||||||
|
&Default::default(),
|
||||||
|
Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let damaged_width = last_title_rect
|
||||||
|
.width()
|
||||||
|
.max(title_pixmap.map(|p| p.width() as f32).unwrap_or(0.0));
|
||||||
|
|
||||||
|
self.surface
|
||||||
|
.damage_buffer(0, 0, damaged_width as i32, last_title_rect.height() as i32);
|
||||||
|
self.update_buffer(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_motion(&mut self, world: &World, x: f64, y: f64) {
|
||||||
|
if self.x_data.check_hovered(x as f32, y as f32) {
|
||||||
|
self.redraw_x_pixmap(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_leave(&mut self, world: &World) {
|
||||||
|
if self.x_data.hovered {
|
||||||
|
self.x_data.hovered = false;
|
||||||
|
self.redraw_x_pixmap(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the toplevel should be closed
|
||||||
|
fn handle_click(&self, toplevel: &XdgToplevel, seat: &WlSeat, serial: u32) -> bool {
|
||||||
|
if self.x_data.hovered {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
toplevel._move(seat, serial);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DecorationsBox {
|
||||||
|
rect: Rect,
|
||||||
|
hovered: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DecorationsBox {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
rect: Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(),
|
||||||
|
hovered: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecorationsBox {
|
||||||
|
/// Returns true if hover state changed.
|
||||||
|
fn check_hovered(&mut self, x: f32, y: f32) -> bool {
|
||||||
|
let old_hovered = self.hovered;
|
||||||
|
self.hovered = (self.rect.left()..=self.rect.right()).contains(&x)
|
||||||
|
&& (self.rect.top()..=self.rect.bottom()).contains(&y);
|
||||||
|
|
||||||
|
old_hovered != self.hovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixmap_to_buffer(pixmap: &Pixmap, buffer: &mut [u8]) {
|
||||||
|
// TODO: support big endian?
|
||||||
|
for (data, pixel) in buffer.chunks_exact_mut(4).zip(pixmap.pixels()) {
|
||||||
|
data[0] = pixel.blue();
|
||||||
|
data[1] = pixel.green();
|
||||||
|
data[2] = pixel.red();
|
||||||
|
data[3] = pixel.alpha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn x_pixmap(bar_height: u32, scale: f32, hovered: bool) -> Pixmap {
|
||||||
|
let mut x = Pixmap::new(bar_height, bar_height).unwrap();
|
||||||
|
if hovered {
|
||||||
|
x.fill(Color::from_rgba(1.0, 0.0, 0.0, 0.8).unwrap());
|
||||||
|
} else {
|
||||||
|
x.fill(Color::WHITE);
|
||||||
|
}
|
||||||
|
let size = x.width() as f32;
|
||||||
|
let margin = 8.4 * scale;
|
||||||
|
|
||||||
|
let mut line = PathBuilder::new();
|
||||||
|
line.move_to(margin, margin);
|
||||||
|
line.line_to(size - margin, size - margin);
|
||||||
|
line.move_to(size - margin, margin);
|
||||||
|
line.line_to(margin, size - margin);
|
||||||
|
let line = line.finish().unwrap();
|
||||||
|
x.stroke_path(
|
||||||
|
&line,
|
||||||
|
&Default::default(),
|
||||||
|
&Stroke {
|
||||||
|
width: scale + 0.5,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Default::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title_pixmap(title: &str, max_width: u32, height: u32, scale: f32) -> Option<Pixmap> {
|
||||||
|
if title.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (glyphs, font) = layout_title_glyphs(title, max_width, height, scale);
|
||||||
|
|
||||||
|
let width = glyphs
|
||||||
|
.last()
|
||||||
|
.map(|g| g.position.x + font.h_advance(g.id))
|
||||||
|
.unwrap()
|
||||||
|
.ceil() as u32;
|
||||||
|
|
||||||
|
let mut pixmap = Pixmap::new(width, height).unwrap();
|
||||||
|
let data = pixmap.pixels_mut();
|
||||||
|
|
||||||
|
for glyph in glyphs {
|
||||||
|
if let Some(og) = font.outline_glyph(glyph) {
|
||||||
|
let bounds = og.px_bounds();
|
||||||
|
og.draw(|x, y, coverage| {
|
||||||
|
let pixel_idx =
|
||||||
|
((bounds.min.x as u32 + x) + (bounds.min.y as u32 + y) * width) as usize;
|
||||||
|
|
||||||
|
data[pixel_idx] =
|
||||||
|
ColorU8::from_rgba(0, 0, 0, (coverage * 255.0) as u8).premultiply();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(pixmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout_title_glyphs(
|
||||||
|
text: &str,
|
||||||
|
max_width: u32,
|
||||||
|
height: u32,
|
||||||
|
scale: f32,
|
||||||
|
) -> (Vec<Glyph>, PxScaleFont<&FontRef<'_>>) {
|
||||||
|
const TEXT_SIZE: f32 = 10.0;
|
||||||
|
const TEXT_MARGIN: f32 = 11.0;
|
||||||
|
static FONT: LazyLock<FontRef<'_>> = LazyLock::new(|| {
|
||||||
|
FontRef::try_from_slice(include_bytes!("../../OpenSans-Regular.ttf")).unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut ret = Vec::<Glyph>::new();
|
||||||
|
|
||||||
|
let px_scale = FONT.pt_to_px_scale(TEXT_SIZE * scale).unwrap();
|
||||||
|
let font = FONT.as_scaled(px_scale);
|
||||||
|
for c in text.chars() {
|
||||||
|
let mut glyph = font.scaled_glyph(c);
|
||||||
|
// This centers the glyphs vertically
|
||||||
|
glyph.position.y = (height as f32 / 2.0) - font.descent();
|
||||||
|
if let Some(previous) = ret.last() {
|
||||||
|
glyph.position.x = previous.position.x
|
||||||
|
+ font.h_advance(previous.id)
|
||||||
|
+ font.kern(glyph.id, previous.id);
|
||||||
|
} else {
|
||||||
|
glyph.position.x = TEXT_MARGIN * scale;
|
||||||
|
}
|
||||||
|
if (glyph.position.x + font.h_advance(glyph.id)).ceil() as u32 > max_width {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.push(glyph);
|
||||||
|
}
|
||||||
|
|
||||||
|
(ret, font)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_decoration(
|
||||||
|
world: &World,
|
||||||
|
parent: Entity,
|
||||||
|
) -> Option<hecs::RefMut<'_, Box<DecorationsDataSatellite>>> {
|
||||||
|
let role = world.get::<&mut SurfaceRole>(parent).ok()?;
|
||||||
|
Some(hecs::RefMut::map(role, |role| {
|
||||||
|
let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
toplevel.decoration.satellite.as_mut().unwrap()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_pointer_leave(state: &InnerServerState<impl X11Selection>, parent: Entity) {
|
||||||
|
if let Some(mut decoration) = get_decoration(&state.world, parent) {
|
||||||
|
decoration.handle_leave(&state.world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_pointer_motion(
|
||||||
|
state: &InnerServerState<impl X11Selection>,
|
||||||
|
parent: Entity,
|
||||||
|
surface_x: f64,
|
||||||
|
surface_y: f64,
|
||||||
|
) {
|
||||||
|
if let Some(mut decoration) = get_decoration(&state.world, parent) {
|
||||||
|
decoration.handle_motion(&state.world, surface_x, surface_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_pointer_click(
|
||||||
|
state: &mut ServerState<impl XConnection>,
|
||||||
|
parent: Entity,
|
||||||
|
seat: &WlSeat,
|
||||||
|
serial: u32,
|
||||||
|
) {
|
||||||
|
let Ok(mut role) = state.world.get::<&mut SurfaceRole>(parent) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
if toplevel
|
||||||
|
.decoration
|
||||||
|
.satellite
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.handle_click(&toplevel.toplevel, seat, serial)
|
||||||
|
{
|
||||||
|
let window = *state.world.get::<&x::Window>(parent).unwrap();
|
||||||
|
drop(role);
|
||||||
|
state.close_x_window(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use super::clientside::LateInitObjectKey;
|
use super::clientside::LateInitObjectKey;
|
||||||
|
use super::decoration::DecorationMarker;
|
||||||
use super::*;
|
use super::*;
|
||||||
use hecs::CommandBuffer;
|
use hecs::{CommandBuffer, World};
|
||||||
use log::{debug, trace, warn};
|
use log::{debug, error, trace, warn};
|
||||||
use macros::simple_event_shunt;
|
use macros::simple_event_shunt;
|
||||||
use std::os::fd::AsFd;
|
use std::os::fd::AsFd;
|
||||||
use wayland_client::{protocol as client, Proxy};
|
use wayland_client::{protocol as client, Proxy};
|
||||||
|
|
@ -35,6 +36,7 @@ use wayland_protocols::{
|
||||||
viewporter::client::wp_viewport::WpViewport,
|
viewporter::client::wp_viewport::WpViewport,
|
||||||
},
|
},
|
||||||
xdg::{
|
xdg::{
|
||||||
|
decoration::zv1::client::zxdg_toplevel_decoration_v1,
|
||||||
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
|
shell::client::{xdg_popup, xdg_surface, xdg_toplevel},
|
||||||
xdg_output::zv1::{
|
xdg_output::zv1::{
|
||||||
client::zxdg_output_v1, server::zxdg_output_v1::ZxdgOutputV1 as XdgOutputServer,
|
client::zxdg_output_v1, server::zxdg_output_v1::ZxdgOutputV1 as XdgOutputServer,
|
||||||
|
|
@ -64,6 +66,7 @@ pub(crate) enum SurfaceEvents {
|
||||||
Toplevel(xdg_toplevel::Event),
|
Toplevel(xdg_toplevel::Event),
|
||||||
Popup(xdg_popup::Event),
|
Popup(xdg_popup::Event),
|
||||||
FractionalScale(wp_fractional_scale_v1::Event),
|
FractionalScale(wp_fractional_scale_v1::Event),
|
||||||
|
DecorationEvent(zxdg_toplevel_decoration_v1::Event),
|
||||||
}
|
}
|
||||||
macro_rules! impl_from {
|
macro_rules! impl_from {
|
||||||
($type:ty, $variant:ident) => {
|
($type:ty, $variant:ident) => {
|
||||||
|
|
@ -79,6 +82,7 @@ impl_from!(xdg_surface::Event, XdgSurface);
|
||||||
impl_from!(xdg_toplevel::Event, Toplevel);
|
impl_from!(xdg_toplevel::Event, Toplevel);
|
||||||
impl_from!(xdg_popup::Event, Popup);
|
impl_from!(xdg_popup::Event, Popup);
|
||||||
impl_from!(wp_fractional_scale_v1::Event, FractionalScale);
|
impl_from!(wp_fractional_scale_v1::Event, FractionalScale);
|
||||||
|
impl_from!(zxdg_toplevel_decoration_v1::Event, DecorationEvent);
|
||||||
|
|
||||||
impl Event for SurfaceEvents {
|
impl Event for SurfaceEvents {
|
||||||
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
fn handle<C: XConnection>(self, target: Entity, state: &mut ServerState<C>) {
|
||||||
|
|
@ -114,6 +118,63 @@ impl Event for SurfaceEvents {
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
|
SurfaceEvents::DecorationEvent(event) => {
|
||||||
|
use zxdg_toplevel_decoration_v1::{Event, Mode};
|
||||||
|
let Event::Configure { mode } = event else {
|
||||||
|
error!("unhandled toplevel decoration event: {event:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let entity = state.world.entity(target).unwrap();
|
||||||
|
let Some(window_data) = entity.get::<&WindowData>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(mode) = mode.into_result() else {
|
||||||
|
warn!("unknown decoration mode: {mode:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let needs_server_side_decorations = window_data
|
||||||
|
.attrs
|
||||||
|
.decorations
|
||||||
|
.is_none_or(|d| d == Decorations::Server);
|
||||||
|
|
||||||
|
if mode == Mode::ServerSide || !needs_server_side_decorations {
|
||||||
|
let mut role = entity.get::<&mut SurfaceRole>().unwrap();
|
||||||
|
if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role {
|
||||||
|
toplevel.decoration.satellite.take();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((sat_decoration, buf)) = entity
|
||||||
|
.get::<&client::wl_surface::WlSurface>()
|
||||||
|
.and_then(|surface| {
|
||||||
|
DecorationsDataSatellite::try_new(
|
||||||
|
state,
|
||||||
|
&surface,
|
||||||
|
window_data.attrs.title.as_ref().map(WmName::name),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
warn!("Needed to create decorations for window, but couldn't create them!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut role = entity.get::<&mut SurfaceRole>().unwrap();
|
||||||
|
// This should always be the case, but, you never know.
|
||||||
|
if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role {
|
||||||
|
toplevel.decoration.satellite = Some(sat_decoration);
|
||||||
|
} else {
|
||||||
|
warn!("Created a decoration for a surface that isn't a toplevel?");
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(window_data);
|
||||||
|
drop(role);
|
||||||
|
if let Some(mut buf) = buf {
|
||||||
|
buf.run_on(&mut state.world);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,8 +284,13 @@ impl SurfaceEvents {
|
||||||
drop(xdg);
|
drop(xdg);
|
||||||
|
|
||||||
if let Some(pending) = pending {
|
if let Some(pending) = pending {
|
||||||
let mut query = data.query::<(&SurfaceScaleFactor, &x::Window, &mut WindowData)>();
|
let mut query = data.query::<(
|
||||||
let (scale_factor, window, window_data) = query.get().unwrap();
|
&SurfaceScaleFactor,
|
||||||
|
&x::Window,
|
||||||
|
&mut WindowData,
|
||||||
|
&mut SurfaceRole,
|
||||||
|
)>();
|
||||||
|
let (scale_factor, window, window_data, role) = query.get().unwrap();
|
||||||
|
|
||||||
let window = *window;
|
let window = *window;
|
||||||
let x = (pending.x.max(0) as f64 * scale_factor.0) as i32 + window_data.output_offset.x;
|
let x = (pending.x.max(0) as f64 * scale_factor.0) as i32 + window_data.output_offset.x;
|
||||||
|
|
@ -234,7 +300,7 @@ impl SurfaceEvents {
|
||||||
} else {
|
} else {
|
||||||
window_data.attrs.dims.width
|
window_data.attrs.dims.width
|
||||||
};
|
};
|
||||||
let height = if pending.height > 0 {
|
let mut height = if pending.height > 0 {
|
||||||
(pending.height as f64 * scale_factor.0) as u16
|
(pending.height as f64 * scale_factor.0) as u16
|
||||||
} else {
|
} else {
|
||||||
window_data.attrs.dims.height
|
window_data.attrs.dims.height
|
||||||
|
|
@ -243,6 +309,20 @@ impl SurfaceEvents {
|
||||||
"configuring {} ({window:?}): {x}x{y}, {width}x{height}",
|
"configuring {} ({window:?}): {x}x{y}, {width}x{height}",
|
||||||
data.get::<&WlSurface>().unwrap().id(),
|
data.get::<&WlSurface>().unwrap().id(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role {
|
||||||
|
if let Some(d) = &mut toplevel.decoration.satellite {
|
||||||
|
let surface_width = (width as f64 / scale_factor.0) as i32;
|
||||||
|
d.draw_decorations(&state.world, surface_width, scale_factor.0 as f32);
|
||||||
|
height = height
|
||||||
|
.saturating_sub(
|
||||||
|
(DecorationsDataSatellite::TITLEBAR_HEIGHT as f64 * scale_factor.0)
|
||||||
|
as u16,
|
||||||
|
)
|
||||||
|
.max(DecorationsDataSatellite::TITLEBAR_HEIGHT as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connection.set_window_dims(
|
connection.set_window_dims(
|
||||||
window,
|
window,
|
||||||
PendingSurfaceState {
|
PendingSurfaceState {
|
||||||
|
|
@ -446,7 +526,16 @@ impl Event for client::wl_seat::Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PendingEnter(client::wl_pointer::Event);
|
struct PendingEnter(client::wl_pointer::Event);
|
||||||
struct CurrentSurface(Entity);
|
enum CurrentSurface {
|
||||||
|
Xwayland(Entity),
|
||||||
|
Decoration(Entity),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CurrentSurface {
|
||||||
|
fn is_decoration(&self) -> bool {
|
||||||
|
matches!(self, Self::Decoration(..))
|
||||||
|
}
|
||||||
|
}
|
||||||
pub struct LastClickSerial(pub client::wl_seat::WlSeat, pub u32);
|
pub struct LastClickSerial(pub client::wl_seat::WlSeat, pub u32);
|
||||||
|
|
||||||
impl Event for client::wl_pointer::Event {
|
impl Event for client::wl_pointer::Event {
|
||||||
|
|
@ -511,7 +600,6 @@ impl Event for client::wl_pointer::Event {
|
||||||
let state = &mut state.inner;
|
let state = &mut state.inner;
|
||||||
let mut cmd = CommandBuffer::new();
|
let mut cmd = CommandBuffer::new();
|
||||||
let pending_enter = state.world.remove_one::<PendingEnter>(target).ok();
|
let pending_enter = state.world.remove_one::<PendingEnter>(target).ok();
|
||||||
let server = state.world.get::<&WlPointer>(target).unwrap();
|
|
||||||
let surface_entity = surface.data().copied();
|
let surface_entity = surface.data().copied();
|
||||||
let mut query = surface_entity.and_then(|e| {
|
let mut query = surface_entity.and_then(|e| {
|
||||||
state
|
state
|
||||||
|
|
@ -521,10 +609,20 @@ impl Event for client::wl_pointer::Event {
|
||||||
});
|
});
|
||||||
let Some((surface, role, scale, window)) = query.as_mut().and_then(|q| q.get())
|
let Some((surface, role, scale, window)) = query.as_mut().and_then(|q| q.get())
|
||||||
else {
|
else {
|
||||||
warn!("could not enter surface: stale surface");
|
if let Some(&DecorationMarker { parent }) = surface.data() {
|
||||||
|
drop(query);
|
||||||
|
state
|
||||||
|
.world
|
||||||
|
.insert_one(target, CurrentSurface::Decoration(parent))
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
warn!("could not enter surface: stale surface");
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let server = state.world.get::<&WlPointer>(target).unwrap();
|
||||||
cmd.insert(target, (*scale,));
|
cmd.insert(target, (*scale,));
|
||||||
|
|
||||||
let surface_is_popup = matches!(role, SurfaceRole::Popup(_));
|
let surface_is_popup = matches!(role, SurfaceRole::Popup(_));
|
||||||
|
|
@ -535,7 +633,7 @@ impl Event for client::wl_pointer::Event {
|
||||||
if !surface_is_popup {
|
if !surface_is_popup {
|
||||||
state.last_hovered = Some(*window);
|
state.last_hovered = Some(*window);
|
||||||
}
|
}
|
||||||
cmd.insert(target, (CurrentSurface(surface_entity.unwrap()),));
|
cmd.insert_one(target, CurrentSurface::Xwayland(surface_entity.unwrap()));
|
||||||
};
|
};
|
||||||
|
|
||||||
if !surface_is_popup {
|
if !surface_is_popup {
|
||||||
|
|
@ -565,13 +663,18 @@ impl Event for client::wl_pointer::Event {
|
||||||
drop(server);
|
drop(server);
|
||||||
cmd.run_on(&mut state.world);
|
cmd.run_on(&mut state.world);
|
||||||
}
|
}
|
||||||
client::wl_pointer::Event::Leave { serial, surface } => {
|
Self::Leave { serial, surface } => {
|
||||||
let _ = state.world.remove_one::<PendingEnter>(target);
|
let _ = state.world.remove_one::<PendingEnter>(target);
|
||||||
let _ = state.world.remove_one::<CurrentSurface>(target);
|
|
||||||
if !surface.is_alive() {
|
if !surface.is_alive() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debug!("leaving surface ({serial})");
|
debug!("leaving surface ({serial})");
|
||||||
|
if let Ok(CurrentSurface::Decoration(parent)) =
|
||||||
|
state.world.remove_one::<CurrentSurface>(target)
|
||||||
|
{
|
||||||
|
decoration::handle_pointer_leave(state, parent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(surface) = surface
|
if let Some(surface) = surface
|
||||||
.data()
|
.data()
|
||||||
.copied()
|
.copied()
|
||||||
|
|
@ -586,7 +689,7 @@ impl Event for client::wl_pointer::Event {
|
||||||
warn!("could not leave surface: stale surface");
|
warn!("could not leave surface: stale surface");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client::wl_pointer::Event::Motion {
|
Self::Motion {
|
||||||
time,
|
time,
|
||||||
surface_x,
|
surface_x,
|
||||||
surface_y,
|
surface_y,
|
||||||
|
|
@ -594,6 +697,13 @@ impl Event for client::wl_pointer::Event {
|
||||||
if !handle_pending_enter(target, state, "motion") {
|
if !handle_pending_enter(target, state, "motion") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let surface = state.world.get::<&CurrentSurface>(target).unwrap();
|
||||||
|
if let CurrentSurface::Decoration(parent) = &*surface {
|
||||||
|
decoration::handle_pointer_motion(state, *parent, surface_x, surface_y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
let (server, scale) = state
|
let (server, scale) = state
|
||||||
.world
|
.world
|
||||||
.query_one_mut::<(&WlPointer, &SurfaceScaleFactor)>(target)
|
.query_one_mut::<(&WlPointer, &SurfaceScaleFactor)>(target)
|
||||||
|
|
@ -606,7 +716,7 @@ impl Event for client::wl_pointer::Event {
|
||||||
);
|
);
|
||||||
server.motion(time, surface_x * scale.0, surface_y * scale.0);
|
server.motion(time, surface_x * scale.0, surface_y * scale.0);
|
||||||
}
|
}
|
||||||
client::wl_pointer::Event::Button {
|
Self::Button {
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
button,
|
button,
|
||||||
|
|
@ -616,13 +726,14 @@ impl Event for client::wl_pointer::Event {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut cmd = CommandBuffer::new();
|
let mut cmd = CommandBuffer::new();
|
||||||
let (server, seat, CurrentSurface(surface)) = state
|
|
||||||
|
let mut query = state
|
||||||
.world
|
.world
|
||||||
.query_one_mut::<(&WlPointer, &client::wl_seat::WlSeat, &CurrentSurface)>(
|
.query_one::<(&WlPointer, &client::wl_seat::WlSeat, &CurrentSurface)>(target)
|
||||||
target,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let (server, seat, current_surface) = query.get().unwrap();
|
||||||
|
|
||||||
// from linux/input-event-codes.h
|
// from linux/input-event-codes.h
|
||||||
mod button_codes {
|
mod button_codes {
|
||||||
pub const LEFT: u32 = 0x110;
|
pub const LEFT: u32 = 0x110;
|
||||||
|
|
@ -631,14 +742,33 @@ impl Event for client::wl_pointer::Event {
|
||||||
if button_state == WEnum::Value(client::wl_pointer::ButtonState::Pressed)
|
if button_state == WEnum::Value(client::wl_pointer::ButtonState::Pressed)
|
||||||
&& button == button_codes::LEFT
|
&& button == button_codes::LEFT
|
||||||
{
|
{
|
||||||
cmd.insert(*surface, (LastClickSerial(seat.clone(), serial),));
|
match current_surface {
|
||||||
|
CurrentSurface::Xwayland(entity) => {
|
||||||
|
cmd.insert(*entity, (LastClickSerial(seat.clone(), serial),));
|
||||||
|
}
|
||||||
|
CurrentSurface::Decoration(parent) => {
|
||||||
|
let seat = seat.clone();
|
||||||
|
let parent = *parent;
|
||||||
|
drop(query);
|
||||||
|
decoration::handle_pointer_click(state, parent, &seat, serial);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.button(serial, time, button, convert_wenum(button_state));
|
server.button(serial, time, button, convert_wenum(button_state));
|
||||||
|
drop(query);
|
||||||
cmd.run_on(&mut state.world);
|
cmd.run_on(&mut state.world);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let server = state.world.get::<&WlPointer>(target).unwrap();
|
let (server, current_surface) = state
|
||||||
|
.world
|
||||||
|
.query_one_mut::<(&WlPointer, Option<&CurrentSurface>)>(target)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if current_surface.is_some_and(CurrentSurface::is_decoration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
simple_event_shunt! {
|
simple_event_shunt! {
|
||||||
server, self => [
|
server, self => [
|
||||||
Frame,
|
Frame,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod clientside;
|
mod clientside;
|
||||||
|
mod decoration;
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
mod event;
|
mod event;
|
||||||
pub(crate) mod selection;
|
pub(crate) mod selection;
|
||||||
|
|
@ -9,7 +10,8 @@ use self::event::*;
|
||||||
use crate::xstate::{Decorations, MoveResizeDirection, WindowDims, WmHints, WmName, WmNormalHints};
|
use crate::xstate::{Decorations, MoveResizeDirection, WindowDims, WmHints, WmName, WmNormalHints};
|
||||||
use crate::{timespec_from_millis, X11Selection, XConnection};
|
use crate::{timespec_from_millis, X11Selection, XConnection};
|
||||||
use clientside::MyWorld;
|
use clientside::MyWorld;
|
||||||
use hecs::{Entity, World};
|
use decoration::{DecorationsData, DecorationsDataSatellite};
|
||||||
|
use hecs::Entity;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use rustix::event::{poll, PollFd, PollFlags};
|
use rustix::event::{poll, PollFd, PollFlags};
|
||||||
use smithay_client_toolkit::activation::ActivationState;
|
use smithay_client_toolkit::activation::ActivationState;
|
||||||
|
|
@ -17,14 +19,13 @@ use std::collections::{HashMap, HashSet};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::os::fd::{AsFd, BorrowedFd};
|
use std::os::fd::{AsFd, BorrowedFd};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
|
use wayland_client::protocol::wl_subcompositor::WlSubcompositor;
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
globals::{registry_queue_init, Global},
|
globals::{registry_queue_init, Global},
|
||||||
protocol as client, Connection, EventQueue, Proxy, QueueHandle,
|
protocol as client, Connection, EventQueue, Proxy, QueueHandle,
|
||||||
};
|
};
|
||||||
use wayland_protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
|
use wayland_protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
|
||||||
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::{
|
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::{self};
|
||||||
self, ZxdgToplevelDecorationV1,
|
|
||||||
};
|
|
||||||
use wayland_protocols::xdg::shell::client::xdg_positioner::ConstraintAdjustment;
|
use wayland_protocols::xdg::shell::client::xdg_positioner::ConstraintAdjustment;
|
||||||
use wayland_protocols::{
|
use wayland_protocols::{
|
||||||
wp::{
|
wp::{
|
||||||
|
|
@ -194,7 +195,7 @@ impl SurfaceRole {
|
||||||
fn destroy(&mut self) {
|
fn destroy(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
SurfaceRole::Toplevel(Some(ref mut t)) => {
|
SurfaceRole::Toplevel(Some(ref mut t)) => {
|
||||||
if let Some(decoration) = t.decoration.take() {
|
if let Some(decoration) = t.decoration.wl.take() {
|
||||||
decoration.destroy();
|
decoration.destroy();
|
||||||
}
|
}
|
||||||
t.toplevel.destroy();
|
t.toplevel.destroy();
|
||||||
|
|
@ -222,7 +223,7 @@ struct ToplevelData {
|
||||||
toplevel: XdgToplevel,
|
toplevel: XdgToplevel,
|
||||||
xdg: XdgSurfaceData,
|
xdg: XdgSurfaceData,
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
decoration: Option<ZxdgToplevelDecorationV1>,
|
decoration: decoration::DecorationsData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -447,6 +448,9 @@ pub struct InnerServerState<S: X11Selection> {
|
||||||
last_hovered: Option<x::Window>,
|
last_hovered: Option<x::Window>,
|
||||||
|
|
||||||
xdg_wm_base: XdgWmBase,
|
xdg_wm_base: XdgWmBase,
|
||||||
|
compositor: client::wl_compositor::WlCompositor,
|
||||||
|
subcompositor: WlSubcompositor,
|
||||||
|
shm: client::wl_shm::WlShm,
|
||||||
viewporter: WpViewporter,
|
viewporter: WpViewporter,
|
||||||
fractional_scale: Option<WpFractionalScaleManagerV1>,
|
fractional_scale: Option<WpFractionalScaleManagerV1>,
|
||||||
decoration_manager: Option<ZxdgDecorationManagerV1>,
|
decoration_manager: Option<ZxdgDecorationManagerV1>,
|
||||||
|
|
@ -482,6 +486,18 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
|
||||||
warn!("xdg_wm_base version 2 detected. Popup repositioning will not work, and some popups may not work correctly.");
|
warn!("xdg_wm_base version 2 detected. Popup repositioning will not work, and some popups may not work correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let compositor = global_list
|
||||||
|
.bind::<client::wl_compositor::WlCompositor, _, _>(&qh, 4..=6, ())
|
||||||
|
.expect("Could not bind wl_compositor");
|
||||||
|
|
||||||
|
let subcompositor = global_list
|
||||||
|
.bind::<WlSubcompositor, _, _>(&qh, 1..=1, ())
|
||||||
|
.expect("Could not bind wl_subcompositor");
|
||||||
|
|
||||||
|
let shm = global_list
|
||||||
|
.bind::<client::wl_shm::WlShm, _, _>(&qh, 1..=1, ())
|
||||||
|
.expect("Could not bind wl_shm");
|
||||||
|
|
||||||
let viewporter = global_list
|
let viewporter = global_list
|
||||||
.bind::<WpViewporter, _, _>(&qh, 1..=1, ())
|
.bind::<WpViewporter, _, _>(&qh, 1..=1, ())
|
||||||
.expect("Could not bind wp_viewporter");
|
.expect("Could not bind wp_viewporter");
|
||||||
|
|
@ -511,6 +527,7 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
|
||||||
.contents()
|
.contents()
|
||||||
.with_list(|globals| handle_globals::<S>(&dh, globals));
|
.with_list(|globals| handle_globals::<S>(&dh, globals));
|
||||||
|
|
||||||
|
let world = MyWorld::new(global_list);
|
||||||
let client = dh.insert_client(client, std::sync::Arc::new(())).unwrap();
|
let client = dh.insert_client(client, std::sync::Arc::new(())).unwrap();
|
||||||
|
|
||||||
let inner = InnerServerState {
|
let inner = InnerServerState {
|
||||||
|
|
@ -525,6 +542,9 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
|
||||||
last_focused_toplevel: None,
|
last_focused_toplevel: None,
|
||||||
last_hovered: None,
|
last_hovered: None,
|
||||||
xdg_wm_base,
|
xdg_wm_base,
|
||||||
|
compositor,
|
||||||
|
subcompositor,
|
||||||
|
shm,
|
||||||
viewporter,
|
viewporter,
|
||||||
fractional_scale,
|
fractional_scale,
|
||||||
selection_states,
|
selection_states,
|
||||||
|
|
@ -544,7 +564,7 @@ impl<S: X11Selection> ServerState<NoConnection<S>> {
|
||||||
updated_outputs: Vec::new(),
|
updated_outputs: Vec::new(),
|
||||||
new_scale: None,
|
new_scale: None,
|
||||||
decoration_manager,
|
decoration_manager,
|
||||||
world: MyWorld::new(global_list),
|
world,
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
inner,
|
inner,
|
||||||
|
|
@ -778,9 +798,12 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(role) = data.get::<&SurfaceRole>() {
|
if let Some(mut role) = data.get::<&mut SurfaceRole>() {
|
||||||
if let SurfaceRole::Toplevel(Some(data)) = &*role {
|
if let SurfaceRole::Toplevel(Some(data)) = &mut *role {
|
||||||
data.toplevel.set_title(title.name().to_string());
|
data.toplevel.set_title(title.name().to_string());
|
||||||
|
if let Some(d) = &mut data.decoration.satellite {
|
||||||
|
d.set_title(&self.world, title.name());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -850,10 +873,6 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_win_decorations(&mut self, window: x::Window, decorations: Decorations) {
|
pub fn set_win_decorations(&mut self, window: x::Window, decorations: Decorations) {
|
||||||
if self.decoration_manager.is_none() {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(data) = self
|
let Some(data) = self
|
||||||
.windows
|
.windows
|
||||||
.get(&window)
|
.get(&window)
|
||||||
|
|
@ -870,10 +889,9 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
debug!("setting {window:?} decorations {decorations:?}");
|
debug!("setting {window:?} decorations {decorations:?}");
|
||||||
if let Some(role) = data.get::<&SurfaceRole>() {
|
if let Some(role) = data.get::<&SurfaceRole>() {
|
||||||
if let SurfaceRole::Toplevel(Some(data)) = &*role {
|
if let SurfaceRole::Toplevel(Some(data)) = &*role {
|
||||||
data.decoration
|
if let Some(decoration) = &data.decoration.wl {
|
||||||
.as_ref()
|
decoration.set_mode(decorations.into());
|
||||||
.unwrap()
|
}
|
||||||
.set_mode(decorations.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
win.attrs.decorations = Some(decorations);
|
win.attrs.decorations = Some(decorations);
|
||||||
|
|
@ -1312,8 +1330,9 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
toplevel.set_fullscreen(None);
|
toplevel.set_fullscreen(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let decoration = self.decoration_manager.as_ref().map(|decoration_manager| {
|
let wl_decoration = self.decoration_manager.as_ref().map(|decoration_manager| {
|
||||||
let decoration = decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, ());
|
let decoration =
|
||||||
|
decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, entity);
|
||||||
decoration.set_mode(
|
decoration.set_mode(
|
||||||
window
|
window
|
||||||
.attrs
|
.attrs
|
||||||
|
|
@ -1323,10 +1342,29 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
decoration
|
decoration
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// X11 side wants server side decorations, but compositor won't provide them
|
||||||
|
// so we provide our own
|
||||||
|
|
||||||
let surface = self
|
let surface = self
|
||||||
.world
|
.world
|
||||||
.get::<&client::wl_surface::WlSurface>(entity)
|
.get::<&client::wl_surface::WlSurface>(entity)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let needs_satellite_decorations = wl_decoration.is_none()
|
||||||
|
&& window
|
||||||
|
.attrs
|
||||||
|
.decorations
|
||||||
|
.is_none_or(|d| d == Decorations::Server);
|
||||||
|
let (sat_decoration, buf) = needs_satellite_decorations
|
||||||
|
.then(|| {
|
||||||
|
DecorationsDataSatellite::try_new(
|
||||||
|
self,
|
||||||
|
&surface,
|
||||||
|
window.attrs.title.as_ref().map(WmName::name),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unzip();
|
||||||
|
|
||||||
if let (Some(activation_state), Some(token)) = (
|
if let (Some(activation_state), Some(token)) = (
|
||||||
self.activation_state.as_ref(),
|
self.activation_state.as_ref(),
|
||||||
window.activation_token.clone(),
|
window.activation_token.clone(),
|
||||||
|
|
@ -1356,6 +1394,13 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drop(window);
|
||||||
|
drop(group);
|
||||||
|
drop(surface);
|
||||||
|
if let Some(mut b) = buf.flatten() {
|
||||||
|
b.run_on(&mut self.world);
|
||||||
|
}
|
||||||
|
|
||||||
ToplevelData {
|
ToplevelData {
|
||||||
xdg: XdgSurfaceData {
|
xdg: XdgSurfaceData {
|
||||||
surface: xdg,
|
surface: xdg,
|
||||||
|
|
@ -1364,7 +1409,10 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
|
||||||
},
|
},
|
||||||
toplevel,
|
toplevel,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
decoration,
|
decoration: DecorationsData {
|
||||||
|
wl: wl_decoration,
|
||||||
|
satellite: sat_decoration,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::io::Write;
|
||||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use testwl::SendDataForMimeFn;
|
use testwl::{SendDataForMimeFn, SurfaceRole};
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
|
backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError},
|
||||||
protocol::{
|
protocol::{
|
||||||
|
|
@ -27,6 +27,7 @@ use wayland_client::{
|
||||||
},
|
},
|
||||||
Connection, Proxy, WEnum,
|
Connection, Proxy, WEnum,
|
||||||
};
|
};
|
||||||
|
use wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||||
use wayland_protocols::{
|
use wayland_protocols::{
|
||||||
wp::{
|
wp::{
|
||||||
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||||
|
|
@ -2600,6 +2601,83 @@ fn scaled_pointer_lock_position_hint() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_side_decorations() {
|
||||||
|
let (mut f, compositor) = TestFixture::new_with_compositor();
|
||||||
|
let window = unsafe { Window::new(1) };
|
||||||
|
let (_, id) = f.create_toplevel(&compositor, window);
|
||||||
|
f.testwl
|
||||||
|
.force_decoration_mode(id, zxdg_toplevel_decoration_v1::Mode::ClientSide);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let subsurface_id = f.testwl.last_created_surface_id().unwrap();
|
||||||
|
assert_ne!(subsurface_id, id);
|
||||||
|
let data = f.testwl.get_surface_data(subsurface_id).unwrap();
|
||||||
|
let Some(SurfaceRole::Subsurface(subsurface)) = &data.role else {
|
||||||
|
panic!("surface was not a subsurface: {:?}", data.role);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(subsurface.position, testwl::Vec2 { x: 0, y: -25 });
|
||||||
|
assert_eq!(subsurface.parent, id);
|
||||||
|
let subsurface = subsurface.subsurface.clone();
|
||||||
|
|
||||||
|
f.testwl
|
||||||
|
.force_decoration_mode(id, zxdg_toplevel_decoration_v1::Mode::ServerSide);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
assert!(f.testwl.get_surface_data(subsurface_id).is_none());
|
||||||
|
assert!(!subsurface.is_alive());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_side_decorations_no_global() {
|
||||||
|
let mut f = TestFixture::new_pre_connect(|testwl| {
|
||||||
|
testwl.disable_decorations_global();
|
||||||
|
});
|
||||||
|
let compositor = f.compositor();
|
||||||
|
let window = unsafe { Window::new(1) };
|
||||||
|
let (buffer, surface) = compositor.create_surface();
|
||||||
|
|
||||||
|
let data = WindowData {
|
||||||
|
mapped: true,
|
||||||
|
dims: WindowDims {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
},
|
||||||
|
fullscreen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
f.new_window(window, false, data);
|
||||||
|
f.map_window(&compositor, window, &surface.obj, &buffer);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let surfaces = f.testwl.created_surfaces();
|
||||||
|
assert_eq!(surfaces.len(), 2);
|
||||||
|
let mut toplevel = None;
|
||||||
|
let mut subsurface_parent = None;
|
||||||
|
for id in surfaces {
|
||||||
|
let data = f.testwl.get_surface_data(*id).unwrap();
|
||||||
|
match data
|
||||||
|
.role
|
||||||
|
.as_ref()
|
||||||
|
.expect("A surface was created without a role")
|
||||||
|
{
|
||||||
|
SurfaceRole::Toplevel(_) => {
|
||||||
|
toplevel = Some(*id);
|
||||||
|
}
|
||||||
|
SurfaceRole::Subsurface(sub) => {
|
||||||
|
assert_eq!(sub.position, testwl::Vec2 { x: 0, y: -25 });
|
||||||
|
subsurface_parent = Some(sub.parent);
|
||||||
|
}
|
||||||
|
other => panic!("got surface with unexpected role: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(toplevel.unwrap(), subsurface_parent.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
/// See Pointer::handle_event for an explanation.
|
/// See Pointer::handle_event for an explanation.
|
||||||
#[test]
|
#[test]
|
||||||
fn popup_pointer_motion_workaround() {}
|
fn popup_pointer_motion_workaround() {}
|
||||||
|
|
|
||||||
|
|
@ -1766,7 +1766,7 @@ fn xdg_decorations() {
|
||||||
let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
|
let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
|
||||||
let surface = f.map_as_toplevel(&mut connection, window);
|
let surface = f.map_as_toplevel(&mut connection, window);
|
||||||
let data = f.testwl.get_surface_data(surface).unwrap();
|
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||||
// The default decoration mode in x11 is SDD
|
// The default decoration mode in x11 is SSD
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.toplevel()
|
data.toplevel()
|
||||||
.decoration
|
.decoration
|
||||||
|
|
@ -1782,8 +1782,7 @@ fn xdg_decorations() {
|
||||||
connection.atoms.motif_wm_hints,
|
connection.atoms.motif_wm_hints,
|
||||||
&[2u32, 0, 0, 0, 0],
|
&[2u32, 0, 0, 0, 0],
|
||||||
);
|
);
|
||||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
f.wait_and_dispatch();
|
||||||
f.testwl.dispatch();
|
|
||||||
let data = f.testwl.get_surface_data(surface).unwrap();
|
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.toplevel()
|
data.toplevel()
|
||||||
|
|
@ -1800,8 +1799,7 @@ fn xdg_decorations() {
|
||||||
connection.atoms.motif_wm_hints,
|
connection.atoms.motif_wm_hints,
|
||||||
&[2u32, 0, 1, 0, 0],
|
&[2u32, 0, 1, 0, 0],
|
||||||
);
|
);
|
||||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
f.wait_and_dispatch();
|
||||||
f.testwl.dispatch();
|
|
||||||
let data = f.testwl.get_surface_data(surface).unwrap();
|
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.toplevel()
|
data.toplevel()
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,9 @@ use wayland_protocols::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use wayland_server::backend::GlobalId;
|
||||||
|
use wayland_server::protocol::wl_subcompositor::WlSubcompositor;
|
||||||
|
use wayland_server::protocol::wl_subsurface::WlSubsurface;
|
||||||
use wayland_server::{
|
use wayland_server::{
|
||||||
backend::{
|
backend::{
|
||||||
protocol::{Interface, ProtocolError},
|
protocol::{Interface, ProtocolError},
|
||||||
|
|
@ -119,6 +122,7 @@ impl SurfaceData {
|
||||||
match self.role.as_ref().expect("Surface missing role") {
|
match self.role.as_ref().expect("Surface missing role") {
|
||||||
SurfaceRole::Toplevel(ref t) => &t.xdg,
|
SurfaceRole::Toplevel(ref t) => &t.xdg,
|
||||||
SurfaceRole::Popup(ref p) => &p.xdg,
|
SurfaceRole::Popup(ref p) => &p.xdg,
|
||||||
|
SurfaceRole::Subsurface(_) => panic!("subsurface doesn't have an XdgSurface"),
|
||||||
SurfaceRole::Cursor => panic!("cursor surface doesn't have an XdgSurface"),
|
SurfaceRole::Cursor => panic!("cursor surface doesn't have an XdgSurface"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +146,7 @@ pub enum SurfaceRole {
|
||||||
Toplevel(Toplevel),
|
Toplevel(Toplevel),
|
||||||
Popup(Popup),
|
Popup(Popup),
|
||||||
Cursor,
|
Cursor,
|
||||||
|
Subsurface(Subsurface),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
|
@ -169,6 +174,13 @@ pub struct Popup {
|
||||||
pub positioner_state: PositionerState,
|
pub positioner_state: PositionerState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Subsurface {
|
||||||
|
pub subsurface: WlSubsurface,
|
||||||
|
pub position: Vec2,
|
||||||
|
pub parent: SurfaceId,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct Vec2 {
|
pub struct Vec2 {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
|
|
@ -251,6 +263,7 @@ struct State {
|
||||||
buffers: HashSet<WlBuffer>,
|
buffers: HashSet<WlBuffer>,
|
||||||
begin: Instant,
|
begin: Instant,
|
||||||
last_surface_id: Option<SurfaceId>,
|
last_surface_id: Option<SurfaceId>,
|
||||||
|
created_surfaces: Vec<SurfaceId>,
|
||||||
last_output: Option<WlOutput>,
|
last_output: Option<WlOutput>,
|
||||||
callbacks: Vec<WlCallback>,
|
callbacks: Vec<WlCallback>,
|
||||||
seat: Option<WlSeat>,
|
seat: Option<WlSeat>,
|
||||||
|
|
@ -275,6 +288,7 @@ impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
surfaces: Default::default(),
|
surfaces: Default::default(),
|
||||||
|
created_surfaces: Default::default(),
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
buffers: Default::default(),
|
buffers: Default::default(),
|
||||||
positioners: Default::default(),
|
positioners: Default::default(),
|
||||||
|
|
@ -415,6 +429,7 @@ pub struct Server {
|
||||||
dh: DisplayHandle,
|
dh: DisplayHandle,
|
||||||
state: State,
|
state: State,
|
||||||
client: Option<Client>,
|
client: Option<Client>,
|
||||||
|
decorations_global: GlobalId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SendDataForMimeFn: FnMut(&str, &mut Server) -> bool {}
|
pub trait SendDataForMimeFn: FnMut(&str, &mut Server) -> bool {}
|
||||||
|
|
@ -448,6 +463,7 @@ impl Server {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
dh.create_global::<State, WlCompositor, _>(6, ());
|
dh.create_global::<State, WlCompositor, _>(6, ());
|
||||||
|
dh.create_global::<State, WlSubcompositor, _>(1, ());
|
||||||
dh.create_global::<State, WlShm, _>(1, ());
|
dh.create_global::<State, WlShm, _>(1, ());
|
||||||
dh.create_global::<State, XdgWmBase, _>(6, ());
|
dh.create_global::<State, XdgWmBase, _>(6, ());
|
||||||
dh.create_global::<State, WlSeat, _>(5, ());
|
dh.create_global::<State, WlSeat, _>(5, ());
|
||||||
|
|
@ -455,7 +471,7 @@ impl Server {
|
||||||
dh.create_global::<State, ZwpPrimarySelectionDeviceManagerV1, _>(1, ());
|
dh.create_global::<State, ZwpPrimarySelectionDeviceManagerV1, _>(1, ());
|
||||||
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
|
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
|
||||||
dh.create_global::<State, XdgActivationV1, _>(1, ());
|
dh.create_global::<State, XdgActivationV1, _>(1, ());
|
||||||
dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
|
let decorations_global = dh.create_global::<State, ZxdgDecorationManagerV1, _>(1, ());
|
||||||
dh.create_global::<State, WpViewporter, _>(1, ());
|
dh.create_global::<State, WpViewporter, _>(1, ());
|
||||||
dh.create_global::<State, ZwpPointerConstraintsV1, _>(1, ());
|
dh.create_global::<State, ZwpPointerConstraintsV1, _>(1, ());
|
||||||
global_noop!(ZwpLinuxDmabufV1);
|
global_noop!(ZwpLinuxDmabufV1);
|
||||||
|
|
@ -515,6 +531,7 @@ impl Server {
|
||||||
dh,
|
dh,
|
||||||
state: State::default(),
|
state: State::default(),
|
||||||
client: None,
|
client: None,
|
||||||
|
decorations_global,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -542,6 +559,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_surface_data(&self, surface_id: SurfaceId) -> Option<&SurfaceData> {
|
pub fn get_surface_data(&self, surface_id: SurfaceId) -> Option<&SurfaceData> {
|
||||||
|
println!("{:?}", self.state.surfaces);
|
||||||
self.state.surfaces.get(&surface_id)
|
self.state.surfaces.get(&surface_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -549,6 +567,10 @@ impl Server {
|
||||||
self.state.last_surface_id
|
self.state.last_surface_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn created_surfaces(&self) -> &[SurfaceId] {
|
||||||
|
&self.state.created_surfaces
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn last_created_output(&self) -> WlOutput {
|
pub fn last_created_output(&self) -> WlOutput {
|
||||||
self.state
|
self.state
|
||||||
|
|
@ -893,6 +915,27 @@ impl Server {
|
||||||
pub fn tablet(&mut self) -> &ZwpTabletV2 {
|
pub fn tablet(&mut self) -> &ZwpTabletV2 {
|
||||||
self.state.tablet.as_ref().expect("No tablet created")
|
self.state.tablet.as_ref().expect("No tablet created")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn force_decoration_mode(
|
||||||
|
&mut self,
|
||||||
|
surface: SurfaceId,
|
||||||
|
mode: zxdg_toplevel_decoration_v1::Mode,
|
||||||
|
) {
|
||||||
|
let toplevel = self.state.get_toplevel(surface);
|
||||||
|
toplevel
|
||||||
|
.decoration
|
||||||
|
.as_mut()
|
||||||
|
.expect("Missing toplevel decoration")
|
||||||
|
.0
|
||||||
|
.configure(mode);
|
||||||
|
self.display.flush_clients().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_decorations_global(&self) {
|
||||||
|
self.display
|
||||||
|
.handle()
|
||||||
|
.remove_global::<State>(self.decorations_global.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
|
|
@ -930,6 +973,7 @@ impl Write for TransferFd {
|
||||||
|
|
||||||
simple_global_dispatch!(WlShm);
|
simple_global_dispatch!(WlShm);
|
||||||
simple_global_dispatch!(WlCompositor);
|
simple_global_dispatch!(WlCompositor);
|
||||||
|
simple_global_dispatch!(WlSubcompositor);
|
||||||
simple_global_dispatch!(XdgWmBase);
|
simple_global_dispatch!(XdgWmBase);
|
||||||
simple_global_dispatch!(ZxdgOutputManagerV1);
|
simple_global_dispatch!(ZxdgOutputManagerV1);
|
||||||
simple_global_dispatch!(ZwpTabletManagerV2);
|
simple_global_dispatch!(ZwpTabletManagerV2);
|
||||||
|
|
@ -1629,7 +1673,7 @@ impl Dispatch<XdgSurface, SurfaceId> for State {
|
||||||
|| match data.role.as_ref().unwrap() {
|
|| match data.role.as_ref().unwrap() {
|
||||||
SurfaceRole::Toplevel(t) => t.toplevel.is_alive(),
|
SurfaceRole::Toplevel(t) => t.toplevel.is_alive(),
|
||||||
SurfaceRole::Popup(p) => p.popup.is_alive(),
|
SurfaceRole::Popup(p) => p.popup.is_alive(),
|
||||||
SurfaceRole::Cursor => false,
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
if role_alive {
|
if role_alive {
|
||||||
client.kill(
|
client.kill(
|
||||||
|
|
@ -1875,12 +1919,44 @@ impl Dispatch<WlCompositor, ()> for State {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
state.last_surface_id = Some(SurfaceId(id));
|
state.last_surface_id = Some(SurfaceId(id));
|
||||||
|
state.created_surfaces.push(SurfaceId(id));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dispatch<WlSubcompositor, ()> for State {
|
||||||
|
fn request(
|
||||||
|
state: &mut Self,
|
||||||
|
_: &Client,
|
||||||
|
_: &WlSubcompositor,
|
||||||
|
request: <WlSubcompositor as Resource>::Request,
|
||||||
|
_: &(),
|
||||||
|
_: &DisplayHandle,
|
||||||
|
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||||
|
) {
|
||||||
|
use proto::wl_subcompositor::Request::*;
|
||||||
|
match request {
|
||||||
|
GetSubsurface {
|
||||||
|
id,
|
||||||
|
surface,
|
||||||
|
parent,
|
||||||
|
} => {
|
||||||
|
let surface_id = SurfaceId::from(&surface);
|
||||||
|
let data = state.surfaces.get_mut(&surface_id).unwrap();
|
||||||
|
data.role = Some(SurfaceRole::Subsurface(Subsurface {
|
||||||
|
parent: SurfaceId::from(&parent),
|
||||||
|
subsurface: data_init.init(id, surface_id),
|
||||||
|
position: Vec2::default(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Destroy => {}
|
||||||
|
other => todo!("unhandled subcompositor request {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Dispatch<WlSurface, ()> for State {
|
impl Dispatch<WlSurface, ()> for State {
|
||||||
fn request(
|
fn request(
|
||||||
state: &mut Self,
|
state: &mut Self,
|
||||||
|
|
@ -1940,6 +2016,32 @@ impl Dispatch<WlSurface, ()> for State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dispatch<WlSubsurface, SurfaceId> for State {
|
||||||
|
fn request(
|
||||||
|
state: &mut Self,
|
||||||
|
_: &Client,
|
||||||
|
_: &WlSubsurface,
|
||||||
|
request: <WlSubsurface as Resource>::Request,
|
||||||
|
surface_id: &SurfaceId,
|
||||||
|
_: &DisplayHandle,
|
||||||
|
_: &mut wayland_server::DataInit<'_, Self>,
|
||||||
|
) {
|
||||||
|
use proto::wl_subsurface::Request::*;
|
||||||
|
match request {
|
||||||
|
SetPosition { x, y } => {
|
||||||
|
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||||
|
let Some(SurfaceRole::Subsurface(subsurface)) = &mut data.role else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
subsurface.position = Vec2 { x, y };
|
||||||
|
}
|
||||||
|
SetDesync | Destroy => {}
|
||||||
|
other => todo!("unhandled wl_subsurface request: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Dispatch<WlCallback, ()> for State {
|
impl Dispatch<WlCallback, ()> for State {
|
||||||
fn request(
|
fn request(
|
||||||
_: &mut Self,
|
_: &mut Self,
|
||||||
|
|
@ -2137,6 +2239,8 @@ impl Dispatch<ZxdgToplevelDecorationV1, SurfaceId> for State {
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|(_, decoration)| decoration)
|
.map(|(_, decoration)| decoration)
|
||||||
.unwrap() = Some(mode);
|
.unwrap() = Some(mode);
|
||||||
|
|
||||||
|
resource.configure(mode);
|
||||||
} else {
|
} else {
|
||||||
resource.post_error(
|
resource.post_error(
|
||||||
zxdg_toplevel_decoration_v1::Error::Orphaned,
|
zxdg_toplevel_decoration_v1::Error::Orphaned,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue