mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 72c8f569ac | |||
| 798d9c55df | |||
| 05613eed1e | |||
| b23dd4b800 | |||
| 1f72089a46 | |||
| fbe9020915 | |||
| 2036116f16 | |||
| 9afd728ae9 | |||
| e51268a39e | |||
| 0a715ce155 | |||
| 89ac958670 | |||
| 2e50f8dee0 | |||
| 7052f0129e | |||
| 962e159db6 | |||
| 11bff3a2f1 | |||
| 15606304f2 | |||
| 85eac9d9d0 | |||
| d3f4583c90 | |||
| fefb1cccd6 | |||
| deef52519a | |||
| 59ff331597 | |||
| b813f99abd | |||
| d9b9cec8b8 | |||
| 597ea62d17 | |||
| 51243a0a50 | |||
| 0ebcc3e0d6 | |||
| 64c85d865e | |||
| 367e4955ea |
Generated
+94
-78
@@ -38,9 +38,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39b801912a977c3fd52d80511fe1c0c8480c6f957f21ae2ce1b92ffe970cf4b9"
|
||||
checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289"
|
||||
dependencies = [
|
||||
"android-properties",
|
||||
"bitflags 2.4.2",
|
||||
@@ -79,9 +79,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
@@ -215,9 +215,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb41eb19024a91746eba0773aa5e16036045bbf45733766661099e182ea6a744"
|
||||
checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65"
|
||||
dependencies = [
|
||||
"async-lock 3.3.0",
|
||||
"cfg-if",
|
||||
@@ -226,7 +226,7 @@ dependencies = [
|
||||
"futures-lite 2.2.0",
|
||||
"parking",
|
||||
"polling 3.3.2",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -265,7 +265,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"event-listener 3.1.0",
|
||||
"futures-lite 1.13.0",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -286,13 +286,13 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
|
||||
dependencies = [
|
||||
"async-io 2.3.0",
|
||||
"async-io 2.3.1",
|
||||
"async-lock 2.8.0",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.48.0",
|
||||
@@ -438,9 +438,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.14.0"
|
||||
version = "1.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||
checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
@@ -504,7 +504,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"log",
|
||||
"polling 3.3.2",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"slab",
|
||||
"thiserror",
|
||||
]
|
||||
@@ -516,7 +516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
@@ -861,7 +861,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"drm-ffi",
|
||||
"drm-fourcc",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -871,7 +871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6"
|
||||
dependencies = [
|
||||
"drm-sys",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1308,7 +1308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate 2.0.1",
|
||||
"proc-macro-crate 2.0.2",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1396,9 +1396,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.1.0"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@@ -1551,9 +1551,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -1746,9 +1746,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92"
|
||||
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -1842,7 +1842,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "niri"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec",
|
||||
@@ -1883,7 +1883,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "niri-config"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"knuffel",
|
||||
@@ -1895,7 +1895,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "niri-ipc"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -1946,6 +1946,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
@@ -1971,7 +1977,7 @@ version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro-crate 2.0.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
@@ -2253,7 +2259,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -2288,9 +2294,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a"
|
||||
checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
|
||||
dependencies = [
|
||||
"toml_datetime",
|
||||
"toml_edit 0.20.2",
|
||||
@@ -2331,9 +2337,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e"
|
||||
checksum = "0f0f7f43585c34e4fdd7497d746bc32e14458cf11c69341cc0587b1d825dde42"
|
||||
dependencies = [
|
||||
"profiling-procmacros",
|
||||
"tracy-client",
|
||||
@@ -2341,9 +2347,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "profiling-procmacros"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b322d7d65c1ab449be3c890fcbd0db6e1092d0dd05d79dba2dd28032cebeb05"
|
||||
checksum = "ce97fecd27bc49296e5e20518b5a1bb54a14f7d5fe6228bc9686ee2a74915cc8"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
@@ -2395,6 +2401,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
@@ -2486,7 +2501,7 @@ checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.4",
|
||||
"regex-automata 0.4.5",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
@@ -2501,9 +2516,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
|
||||
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2544,9 +2559,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.30"
|
||||
version = "0.38.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
|
||||
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"errno",
|
||||
@@ -2608,18 +2623,18 @@ checksum = "621e3680f3e07db4c9c2c3fb07c6223ab2fab2e54bd3c04c3ae037990f428c32"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.195"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.195"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2628,9 +2643,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.111"
|
||||
version = "1.0.113"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
|
||||
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -2716,7 +2731,7 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
[[package]]
|
||||
name = "smithay"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/Smithay/smithay.git#8854dee7c2f49e9077f10d484b0de9a8e81c587c"
|
||||
source = "git+https://github.com/Smithay/smithay.git#0eac415ba2d9409cbc201955dc0fd306c116ae05"
|
||||
dependencies = [
|
||||
"appendlist",
|
||||
"bitflags 2.4.2",
|
||||
@@ -2742,7 +2757,7 @@ dependencies = [
|
||||
"pkg-config",
|
||||
"profiling",
|
||||
"rand",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"scan_fmt",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
@@ -2772,8 +2787,8 @@ dependencies = [
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2 0.9.3",
|
||||
"rustix 0.38.30",
|
||||
"memmap2 0.9.4",
|
||||
"rustix 0.38.31",
|
||||
"thiserror",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
@@ -2788,7 +2803,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "smithay-drm-extras"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Smithay/smithay.git#8854dee7c2f49e9077f10d484b0de9a8e81c587c"
|
||||
source = "git+https://github.com/Smithay/smithay.git#0eac415ba2d9409cbc201955dc0fd306c116ae05"
|
||||
dependencies = [
|
||||
"drm",
|
||||
"edid-rs",
|
||||
@@ -2872,7 +2887,7 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2"
|
||||
dependencies = [
|
||||
"quick-xml",
|
||||
"quick-xml 0.30.0",
|
||||
"windows 0.51.1",
|
||||
]
|
||||
|
||||
@@ -2885,7 +2900,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.0.1",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -2921,11 +2936,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.31"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
|
||||
checksum = "fe80ced77cbfb4cb91a94bf72b378b4b6791a0d9b7f09d0be747d1bdff4e68bd"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
@@ -3071,9 +3087,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracy-client-sys"
|
||||
version = "0.22.1"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078c7ed72141b0e4369671a7f7af0eecffe18d753bf0296adca9c7add7276c9d"
|
||||
checksum = "9d104d610dfa9dd154535102cc9c6164ae1fa37842bc2d9e83f9ac82b0ae0882"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -3280,13 +3296,13 @@ checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4"
|
||||
checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"nix",
|
||||
"rustix 0.38.31",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
@@ -3294,12 +3310,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.1"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3"
|
||||
checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"nix",
|
||||
"rustix 0.38.31",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
@@ -3317,11 +3333,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.31.0"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b"
|
||||
checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"rustix 0.38.31",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
@@ -3338,9 +3354,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.31.0"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c"
|
||||
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"wayland-backend",
|
||||
@@ -3391,25 +3407,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.0"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
|
||||
checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quick-xml 0.31.0",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-server"
|
||||
version = "0.31.0"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3f0c52a445936ca1184c98f1a69cf4ad9c9130788884531ef04428468cb1ce"
|
||||
checksum = "00e6e4d5c285bc24ba4ed2d5a4bd4febd5fd904451f465973225c8e99772fdb7"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"downcast-rs",
|
||||
"io-lifetimes 2.0.3",
|
||||
"nix",
|
||||
"rustix 0.38.31",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
@@ -3725,7 +3741,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2 0.9.3",
|
||||
"memmap2 0.9.4",
|
||||
"ndk",
|
||||
"ndk-sys",
|
||||
"objc2",
|
||||
@@ -3734,7 +3750,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"smithay-client-toolkit",
|
||||
"smol_str",
|
||||
"unicode-segmentation",
|
||||
@@ -3754,9 +3770,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.34"
|
||||
version = "0.5.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
|
||||
checksum = "818ce546a11a9986bc24f93d0cdf38a8a1a400f1473ea8c82e59f6e0ffab9249"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -3783,7 +3799,7 @@ dependencies = [
|
||||
"libc",
|
||||
"libloading",
|
||||
"once_cell",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
description = "A scrollable-tiling Wayland compositor"
|
||||
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -9,7 +9,7 @@ repository = "https://github.com/YaLTeR/niri"
|
||||
[workspace.dependencies]
|
||||
bitflags = "2.4.2"
|
||||
directories = "5.0.1"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
tracy-client = { version = "0.16.5", default-features = false }
|
||||
|
||||
@@ -46,20 +46,20 @@ directories = "5.0.1"
|
||||
futures-util = { version = "0.3.30", default-features = false, features = ["std", "io"] }
|
||||
git-version = "0.3.9"
|
||||
keyframe = { version = "1.1.1", default-features = false }
|
||||
libc = "0.2.152"
|
||||
libc = "0.2.153"
|
||||
log = { version = "0.4.20", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
logind-zbus = { version = "3.1.2", optional = true }
|
||||
niri-config = { version = "0.1.0", path = "niri-config" }
|
||||
niri-ipc = { version = "0.1.0", path = "niri-ipc" }
|
||||
niri-config = { version = "0.1.1", path = "niri-config" }
|
||||
niri-ipc = { version = "0.1.1", path = "niri-ipc" }
|
||||
notify-rust = { version = "4.10.0", optional = true }
|
||||
pangocairo = "0.18.0"
|
||||
pipewire = { version = "0.7.2", optional = true }
|
||||
png = "0.17.11"
|
||||
portable-atomic = { version = "1.6.0", default-features = false, features = ["float"] }
|
||||
profiling = "1.0.13"
|
||||
profiling = "1.0.14"
|
||||
sd-notify = "0.4.1"
|
||||
serde.workspace = true
|
||||
serde_json = "1.0.111"
|
||||
serde_json = "1.0.113"
|
||||
smithay-drm-extras.workspace = true
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tracing.workspace = true
|
||||
@@ -109,7 +109,7 @@ lto = "thin"
|
||||
debug = false
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
assets = [
|
||||
{ source = "target/release/niri", dest = "/usr/bin/", mode = "755" },
|
||||
{ source = "resources/niri-session", dest = "/usr/bin/", mode = "755" },
|
||||
|
||||
+60
-1
@@ -187,6 +187,8 @@ pub struct Output {
|
||||
pub name: String,
|
||||
#[knuffel(child, unwrap(argument), default = 1.)]
|
||||
pub scale: f64,
|
||||
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
|
||||
pub transform: Transform,
|
||||
#[knuffel(child)]
|
||||
pub position: Option<Position>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
@@ -199,12 +201,62 @@ impl Default for Output {
|
||||
off: false,
|
||||
name: String::new(),
|
||||
scale: 1.,
|
||||
transform: Transform::Normal,
|
||||
position: None,
|
||||
mode: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Output transform, which goes counter-clockwise.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Transform {
|
||||
Normal,
|
||||
_90,
|
||||
_180,
|
||||
_270,
|
||||
Flipped,
|
||||
Flipped90,
|
||||
Flipped180,
|
||||
Flipped270,
|
||||
}
|
||||
|
||||
impl FromStr for Transform {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"normal" => Ok(Self::Normal),
|
||||
"90" => Ok(Self::_90),
|
||||
"180" => Ok(Self::_180),
|
||||
"270" => Ok(Self::_270),
|
||||
"flipped" => Ok(Self::Flipped),
|
||||
"flipped-90" => Ok(Self::Flipped90),
|
||||
"flipped-180" => Ok(Self::Flipped180),
|
||||
"flipped-270" => Ok(Self::Flipped270),
|
||||
_ => Err(miette!(concat!(
|
||||
r#"invalid transform, can be "90", "180", "270", "#,
|
||||
r#""flipped", "flipped-90", "flipped-180" or "flipped-270""#
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Transform> for smithay::utils::Transform {
|
||||
fn from(value: Transform) -> Self {
|
||||
match value {
|
||||
Transform::Normal => Self::Normal,
|
||||
Transform::_90 => Self::_90,
|
||||
Transform::_180 => Self::_180,
|
||||
Transform::_270 => Self::_270,
|
||||
Transform::Flipped => Self::Flipped,
|
||||
Transform::Flipped90 => Self::Flipped90,
|
||||
Transform::Flipped180 => Self::Flipped180,
|
||||
Transform::Flipped270 => Self::Flipped270,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Position {
|
||||
#[knuffel(property)]
|
||||
@@ -296,7 +348,8 @@ impl Color {
|
||||
|
||||
impl From<Color> for [f32; 4] {
|
||||
fn from(c: Color) -> Self {
|
||||
[c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.)
|
||||
let [r, g, b, a] = [c.r, c.g, c.b, c.a].map(|x| x as f32 / 255.);
|
||||
[r * a, g * a, b * a, a]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +491,10 @@ pub enum Action {
|
||||
SetColumnWidth(#[knuffel(argument, str)] SizeChange),
|
||||
SwitchLayout(#[knuffel(argument)] LayoutAction),
|
||||
ShowHotkeyOverlay,
|
||||
MoveWorkspaceToMonitorLeft,
|
||||
MoveWorkspaceToMonitorRight,
|
||||
MoveWorkspaceToMonitorDown,
|
||||
MoveWorkspaceToMonitorUp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
@@ -726,6 +783,7 @@ mod tests {
|
||||
|
||||
output "eDP-1" {
|
||||
scale 2.0
|
||||
transform "flipped-90"
|
||||
position x=10 y=20
|
||||
mode "1920x1080@144"
|
||||
}
|
||||
@@ -826,6 +884,7 @@ mod tests {
|
||||
off: false,
|
||||
name: "eDP-1".to_owned(),
|
||||
scale: 2.,
|
||||
transform: Transform::Flipped90,
|
||||
position: Some(Position { x: 10, y: 20 }),
|
||||
mode: Some(Mode {
|
||||
width: 1920,
|
||||
|
||||
@@ -65,6 +65,10 @@ input {
|
||||
// Scale is a floating-point number, but at the moment only integer values work.
|
||||
scale 2.0
|
||||
|
||||
// Transform allows to rotate the output counter-clockwise, valid values are:
|
||||
// normal, 90, 180, 270, flipped, flipped-90, flipped-180 and flipped-270.
|
||||
transform "normal"
|
||||
|
||||
// Resolution and, optionally, refresh rate of the output.
|
||||
// The format is "<width>x<height>" or "<width>x<height>@<refresh rate>".
|
||||
// If the refresh rate is omitted, niri will pick the highest refresh rate
|
||||
@@ -86,6 +90,14 @@ input {
|
||||
}
|
||||
|
||||
layout {
|
||||
// By default focus ring and border are rendered as a solid background rectangle
|
||||
// behind windows. That is, they will show up through semitransparent windows.
|
||||
// This is because windows using client-side decoratins can have an arbitrary shape.
|
||||
//
|
||||
// If you don't like that, you should uncomment `prefer-no-csd` below.
|
||||
// Niri will draw focus ring and border *around* windows that agree to omit their
|
||||
// client-side decorations.
|
||||
|
||||
// You can change how the focus ring looks.
|
||||
focus-ring {
|
||||
// Uncomment this line to disable the focus ring.
|
||||
@@ -263,6 +275,10 @@ binds {
|
||||
// Mod+Shift+Ctrl+Left { move-window-to-monitor-left; }
|
||||
// ...
|
||||
|
||||
// And you can also move a whole workspace to another monitor:
|
||||
// Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; }
|
||||
// ...
|
||||
|
||||
Mod+Page_Down { focus-workspace-down; }
|
||||
Mod+Page_Up { focus-workspace-up; }
|
||||
Mod+U { focus-workspace-down; }
|
||||
|
||||
+1
-1
@@ -138,7 +138,7 @@ impl Backend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_monitors_active(&self, active: bool) {
|
||||
pub fn set_monitors_active(&mut self, active: bool) {
|
||||
match self {
|
||||
Backend::Tty(tty) => tty.set_monitors_active(active),
|
||||
Backend::Winit(_) => (),
|
||||
|
||||
+47
-20
@@ -144,11 +144,19 @@ pub struct SurfaceDmabufFeedback {
|
||||
}
|
||||
|
||||
impl Tty {
|
||||
pub fn new(config: Rc<RefCell<Config>>, event_loop: LoopHandle<'static, State>) -> Self {
|
||||
let (session, notifier) = LibSeatSession::new().unwrap();
|
||||
pub fn new(
|
||||
config: Rc<RefCell<Config>>,
|
||||
event_loop: LoopHandle<'static, State>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let (session, notifier) = LibSeatSession::new().context(
|
||||
"Error creating a session. This might mean that you're trying to run niri on a TTY \
|
||||
that is already busy, for example if you're running this inside tmux that had been \
|
||||
originally started on a different TTY",
|
||||
)?;
|
||||
let seat_name = session.seat();
|
||||
|
||||
let udev_backend = UdevBackend::new(session.seat()).unwrap();
|
||||
let udev_backend =
|
||||
UdevBackend::new(session.seat()).context("error creating a udev backend")?;
|
||||
let udev_dispatcher = Dispatcher::new(udev_backend, move |event, _, state: &mut State| {
|
||||
state.backend.tty().on_udev_event(&mut state.niri, event);
|
||||
});
|
||||
@@ -157,7 +165,9 @@ impl Tty {
|
||||
.unwrap();
|
||||
|
||||
let mut libinput = Libinput::new_with_udev(LibinputSessionInterface::from(session.clone()));
|
||||
libinput.udev_assign_seat(&seat_name).unwrap();
|
||||
libinput
|
||||
.udev_assign_seat(&seat_name)
|
||||
.map_err(|()| anyhow!("error assigning the seat to libinput"))?;
|
||||
|
||||
let input_backend = LibinputInputBackend::new(libinput.clone());
|
||||
event_loop
|
||||
@@ -192,18 +202,23 @@ impl Tty {
|
||||
Ok(gles)
|
||||
};
|
||||
let api = GbmGlesBackend::with_factory(Box::new(create_renderer));
|
||||
let gpu_manager = GpuManager::new(api).unwrap();
|
||||
let gpu_manager = GpuManager::new(api).context("error creating the GPU manager")?;
|
||||
|
||||
let (primary_node, primary_render_node) = primary_node_from_config(&config.borrow())
|
||||
.unwrap_or_else(|| {
|
||||
let primary_gpu_path = udev::primary_gpu(&seat_name).unwrap().unwrap();
|
||||
let primary_node = DrmNode::from_path(primary_gpu_path).unwrap();
|
||||
.ok_or(())
|
||||
.or_else(|()| {
|
||||
let primary_gpu_path = udev::primary_gpu(&seat_name)
|
||||
.context("error getting the primary GPU")?
|
||||
.context("couldn't find a GPU")?;
|
||||
let primary_node = DrmNode::from_path(primary_gpu_path)
|
||||
.context("error opening the primary GPU DRM node")?;
|
||||
let primary_render_node = primary_node
|
||||
.node_with_type(NodeType::Render)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
(primary_node, primary_render_node)
|
||||
});
|
||||
.context("error getting the render node for the primary GPU")?
|
||||
.context("error getting the render node for the primary GPU")?;
|
||||
|
||||
Ok::<_, anyhow::Error>((primary_node, primary_render_node))
|
||||
})?;
|
||||
|
||||
let mut node_path = String::new();
|
||||
if let Some(path) = primary_render_node.dev_path() {
|
||||
@@ -213,7 +228,7 @@ impl Tty {
|
||||
}
|
||||
info!("using as the render node: {}", node_path);
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
config,
|
||||
session,
|
||||
udev_dispatcher,
|
||||
@@ -227,7 +242,7 @@ impl Tty {
|
||||
update_output_config_on_resume: false,
|
||||
ipc_outputs: Rc::new(RefCell::new(HashMap::new())),
|
||||
enabled_outputs: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init(&mut self, niri: &mut Niri) {
|
||||
@@ -728,7 +743,7 @@ impl Tty {
|
||||
|
||||
// Power on all monitors if necessary and queue a redraw on the new one.
|
||||
niri.event_loop.insert_idle(move |state| {
|
||||
state.niri.activate_monitors(&state.backend);
|
||||
state.niri.activate_monitors(&mut state.backend);
|
||||
state.niri.queue_redraw(output);
|
||||
});
|
||||
|
||||
@@ -1031,7 +1046,7 @@ impl Tty {
|
||||
niri.send_dmabuf_feedbacks(output, dmabuf_feedback, &res.states);
|
||||
}
|
||||
|
||||
if res.damage.is_some() {
|
||||
if !res.is_empty {
|
||||
let presentation_feedbacks =
|
||||
niri.take_presentation_feedbacks(output, &res.states);
|
||||
let data = (presentation_feedbacks, target_presentation_time);
|
||||
@@ -1196,10 +1211,22 @@ impl Tty {
|
||||
self.devices.get(&self.primary_node).map(|d| d.gbm.clone())
|
||||
}
|
||||
|
||||
pub fn set_monitors_active(&self, active: bool) {
|
||||
for device in self.devices.values() {
|
||||
for crtc in device.surfaces.keys() {
|
||||
set_crtc_active(&device.drm, *crtc, active);
|
||||
pub fn set_monitors_active(&mut self, active: bool) {
|
||||
// We only disable the CRTC here, this will also reset the
|
||||
// surface state so that the next call to `render_frame` will
|
||||
// always produce a new frame and `queue_frame` will change
|
||||
// the CRTC to active. This makes sure we always enable a CRTC
|
||||
// within an atomic operation.
|
||||
if active {
|
||||
return;
|
||||
}
|
||||
|
||||
for device in self.devices.values_mut() {
|
||||
for (crtc, surface) in device.surfaces.iter_mut() {
|
||||
set_crtc_active(&device.drm, *crtc, false);
|
||||
if let Err(err) = surface.compositor.reset_state() {
|
||||
warn!("error resetting surface state: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -16,7 +16,6 @@ use smithay::reexports::calloop::LoopHandle;
|
||||
use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
|
||||
use smithay::reexports::winit::dpi::LogicalSize;
|
||||
use smithay::reexports::winit::window::WindowBuilder;
|
||||
use smithay::utils::Transform;
|
||||
|
||||
use super::RenderResult;
|
||||
use crate::niri::{RedrawState, State};
|
||||
@@ -33,12 +32,15 @@ pub struct Winit {
|
||||
}
|
||||
|
||||
impl Winit {
|
||||
pub fn new(config: Rc<RefCell<Config>>, event_loop: LoopHandle<State>) -> Self {
|
||||
pub fn new(
|
||||
config: Rc<RefCell<Config>>,
|
||||
event_loop: LoopHandle<State>,
|
||||
) -> Result<Self, winit::Error> {
|
||||
let builder = WindowBuilder::new()
|
||||
.with_inner_size(LogicalSize::new(1280.0, 800.0))
|
||||
// .with_resizable(false)
|
||||
.with_title("niri");
|
||||
let (backend, winit) = winit::init_from_builder(builder).unwrap();
|
||||
let (backend, winit) = winit::init_from_builder(builder)?;
|
||||
|
||||
let output = Output::new(
|
||||
"winit".to_string(),
|
||||
@@ -54,7 +56,7 @@ impl Winit {
|
||||
size: backend.window_size(),
|
||||
refresh: 60_000,
|
||||
};
|
||||
output.change_current_state(Some(mode), Some(Transform::Flipped180), None, None);
|
||||
output.change_current_state(Some(mode), None, None, None);
|
||||
output.set_preferred(mode);
|
||||
|
||||
let physical_properties = output.physical_properties();
|
||||
@@ -107,21 +109,18 @@ impl Winit {
|
||||
WinitEvent::Redraw => state
|
||||
.niri
|
||||
.queue_redraw(state.backend.winit().output.clone()),
|
||||
WinitEvent::CloseRequested => {
|
||||
state.niri.stop_signal.stop();
|
||||
state.niri.remove_output(&state.backend.winit().output);
|
||||
}
|
||||
WinitEvent::CloseRequested => state.niri.stop_signal.stop(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
config,
|
||||
output,
|
||||
backend,
|
||||
damage_tracker,
|
||||
ipc_outputs,
|
||||
enabled_outputs,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init(&mut self, niri: &mut Niri) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use pangocairo::cairo::{self, ImageSurface};
|
||||
@@ -26,6 +27,10 @@ const BORDER: i32 = 4;
|
||||
pub struct ConfigErrorNotification {
|
||||
state: State,
|
||||
buffers: RefCell<HashMap<i32, Option<MemoryRenderBuffer>>>,
|
||||
|
||||
// If set, this is a "Created config at {path}" notification. If unset, this is a config error
|
||||
// notification.
|
||||
created_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
enum State {
|
||||
@@ -43,10 +48,25 @@ impl ConfigErrorNotification {
|
||||
Self {
|
||||
state: State::Hidden,
|
||||
buffers: RefCell::new(HashMap::new()),
|
||||
created_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_created(&mut self, created_path: Option<PathBuf>) {
|
||||
if self.created_path != created_path {
|
||||
self.created_path = created_path;
|
||||
self.buffers.borrow_mut().clear();
|
||||
}
|
||||
|
||||
self.state = State::Showing(Animation::new(0., 1., Duration::from_millis(250)));
|
||||
}
|
||||
|
||||
pub fn show(&mut self) {
|
||||
if self.created_path.is_some() {
|
||||
self.created_path = None;
|
||||
self.buffers.borrow_mut().clear();
|
||||
}
|
||||
|
||||
// Show from scratch even if already showing to bring attention.
|
||||
self.state = State::Showing(Animation::new(0., 1., Duration::from_millis(250)));
|
||||
}
|
||||
@@ -65,7 +85,15 @@ impl ConfigErrorNotification {
|
||||
State::Showing(anim) => {
|
||||
anim.set_current_time(target_presentation_time);
|
||||
if anim.is_done() {
|
||||
self.state = State::Shown(target_presentation_time + Duration::from_secs(4));
|
||||
let duration = if self.created_path.is_some() {
|
||||
// Make this quite a bit longer because it comes with a monitor modeset
|
||||
// (can take a while) and an important hotkeys popup diverting the
|
||||
// attention.
|
||||
Duration::from_secs(8)
|
||||
} else {
|
||||
Duration::from_secs(4)
|
||||
};
|
||||
self.state = State::Shown(target_presentation_time + duration);
|
||||
}
|
||||
}
|
||||
State::Shown(deadline) => {
|
||||
@@ -96,11 +124,12 @@ impl ConfigErrorNotification {
|
||||
}
|
||||
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let path = self.created_path.as_deref();
|
||||
|
||||
let mut buffers = self.buffers.borrow_mut();
|
||||
let buffer = buffers
|
||||
.entry(scale)
|
||||
.or_insert_with_key(move |&scale| render(scale).ok());
|
||||
.or_insert_with_key(move |&scale| render(scale, path).ok());
|
||||
let buffer = buffer.as_ref()?;
|
||||
|
||||
let elem = MemoryRenderBufferRenderElement::from_buffer(
|
||||
@@ -138,11 +167,22 @@ impl ConfigErrorNotification {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
fn render(scale: i32, created_path: Option<&Path>) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
let _span = tracy_client::span!("config_error_notification::render");
|
||||
|
||||
let padding = PADDING * scale;
|
||||
|
||||
let mut text = String::from(TEXT);
|
||||
let mut border_color = (1., 0.3, 0.3);
|
||||
if let Some(path) = created_path {
|
||||
text = format!(
|
||||
"Created a default config file at \
|
||||
<span face='monospace' bgcolor='#000000'>{:?}</span>",
|
||||
path
|
||||
);
|
||||
border_color = (0.5, 1., 0.5);
|
||||
};
|
||||
|
||||
let mut font = FontDescription::from_string(FONT);
|
||||
font.set_absolute_size((font.size() * scale).into());
|
||||
|
||||
@@ -150,7 +190,7 @@ fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
let layout = pangocairo::create_layout(&cr);
|
||||
layout.set_font_description(Some(&font));
|
||||
layout.set_markup(TEXT);
|
||||
layout.set_markup(&text);
|
||||
|
||||
let (mut width, mut height) = layout.pixel_size();
|
||||
width += padding * 2;
|
||||
@@ -168,7 +208,7 @@ fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
cr.move_to(padding.into(), padding.into());
|
||||
let layout = pangocairo::create_layout(&cr);
|
||||
layout.set_font_description(Some(&font));
|
||||
layout.set_markup(TEXT);
|
||||
layout.set_markup(&text);
|
||||
|
||||
cr.set_source_rgb(1., 1., 1.);
|
||||
pangocairo::show_layout(&cr, &layout);
|
||||
@@ -178,7 +218,7 @@ fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
cr.line_to(width.into(), height.into());
|
||||
cr.line_to(0., height.into());
|
||||
cr.line_to(0., 0.);
|
||||
cr.set_source_rgb(1., 0.3, 0.3);
|
||||
cr.set_source_rgb(border_color.0, border_color.1, border_color.2);
|
||||
cr.set_line_width((BORDER * scale).into());
|
||||
cr.stroke()?;
|
||||
drop(cr);
|
||||
|
||||
+80
-1
@@ -11,8 +11,10 @@ use std::thread;
|
||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||
use smithay::desktop::{PopupKind, PopupManager};
|
||||
use smithay::input::pointer::{CursorIcon, CursorImageStatus, PointerHandle};
|
||||
use smithay::input::{Seat, SeatHandler, SeatState};
|
||||
use smithay::input::{keyboard, Seat, SeatHandler, SeatState};
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::input;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_server::protocol::wl_data_source::WlDataSource;
|
||||
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
@@ -21,6 +23,7 @@ use smithay::utils::{Logical, Rectangle, Size};
|
||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
|
||||
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
||||
use smithay::wayland::output::OutputHandler;
|
||||
use smithay::wayland::pointer_constraints::PointerConstraintsHandler;
|
||||
use smithay::wayland::security_context::{
|
||||
SecurityContext, SecurityContextHandler, SecurityContextListenerSource,
|
||||
@@ -45,7 +48,11 @@ use smithay::{
|
||||
delegate_tablet_manager, delegate_text_input_manager, delegate_virtual_keyboard_manager,
|
||||
};
|
||||
|
||||
use crate::delegate_foreign_toplevel;
|
||||
use crate::niri::{ClientState, State};
|
||||
use crate::protocols::foreign_toplevel::{
|
||||
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
||||
};
|
||||
use crate::utils::output_size;
|
||||
|
||||
impl SeatHandler for State {
|
||||
@@ -73,6 +80,19 @@ impl SeatHandler for State {
|
||||
set_data_device_focus(dh, seat, client.clone());
|
||||
set_primary_focus(dh, seat, client);
|
||||
}
|
||||
|
||||
fn led_state_changed(&mut self, _seat: &Seat<Self>, led_state: keyboard::LedState) {
|
||||
let keyboards = self
|
||||
.niri
|
||||
.devices
|
||||
.iter()
|
||||
.filter(|device| device.has_capability(input::DeviceCapability::Keyboard))
|
||||
.cloned();
|
||||
|
||||
for mut keyboard in keyboards {
|
||||
keyboard.led_update(led_state.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_seat!(State);
|
||||
delegate_cursor_shape!(State);
|
||||
@@ -189,6 +209,11 @@ impl DataControlHandler for State {
|
||||
|
||||
delegate_data_control!(State);
|
||||
|
||||
impl OutputHandler for State {
|
||||
fn output_bound(&mut self, output: Output, wl_output: WlOutput) {
|
||||
foreign_toplevel::on_output_bound(self, &output, &wl_output);
|
||||
}
|
||||
}
|
||||
delegate_output!(State);
|
||||
|
||||
delegate_presentation!(State);
|
||||
@@ -277,3 +302,57 @@ impl SecurityContextHandler for State {
|
||||
}
|
||||
}
|
||||
delegate_security_context!(State);
|
||||
|
||||
impl ForeignToplevelHandler for State {
|
||||
fn foreign_toplevel_manager_state(&mut self) -> &mut ForeignToplevelManagerState {
|
||||
&mut self.niri.foreign_toplevel_state
|
||||
}
|
||||
|
||||
fn activate(&mut self, wl_surface: WlSurface) {
|
||||
if let Some((window, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
let window = window.clone();
|
||||
self.niri.layout.activate_window(&window);
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self, wl_surface: WlSurface) {
|
||||
if let Some((window, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
window.toplevel().send_close();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_fullscreen(&mut self, wl_surface: WlSurface, wl_output: Option<WlOutput>) {
|
||||
if let Some((window, current_output)) = self.niri.layout.find_window_and_output(&wl_surface)
|
||||
{
|
||||
if !window
|
||||
.toplevel()
|
||||
.current_state()
|
||||
.capabilities
|
||||
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let window = window.clone();
|
||||
|
||||
if let Some(requested_output) = wl_output.as_ref().and_then(Output::from_resource) {
|
||||
if &requested_output != current_output {
|
||||
self.niri
|
||||
.layout
|
||||
.move_window_to_output(window.clone(), &requested_output);
|
||||
}
|
||||
}
|
||||
|
||||
self.niri.layout.set_fullscreen(&window, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_fullscreen(&mut self, wl_surface: WlSurface) {
|
||||
if let Some((window, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
|
||||
let window = window.clone();
|
||||
self.niri.layout.set_fullscreen(&window, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_foreign_toplevel!(State);
|
||||
|
||||
@@ -230,6 +230,13 @@ impl XdgShellHandler for State {
|
||||
}
|
||||
|
||||
self.niri.layout.set_fullscreen(&window, true);
|
||||
} else if let Some(window) = self.niri.unmapped_windows.get(surface.wl_surface()) {
|
||||
if let Some(ws) = self.niri.layout.active_workspace() {
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(ws.view_size());
|
||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +253,13 @@ impl XdgShellHandler for State {
|
||||
{
|
||||
let window = window.clone();
|
||||
self.niri.layout.set_fullscreen(&window, false);
|
||||
} else if let Some(window) = self.niri.unmapped_windows.get(surface.wl_surface()) {
|
||||
if let Some(ws) = self.niri.layout.active_workspace() {
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(ws.new_window_size());
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+51
-7
@@ -51,7 +51,7 @@ impl State {
|
||||
|
||||
// Power on monitors if they were off.
|
||||
if should_activate_monitors(&event) {
|
||||
self.niri.activate_monitors(&self.backend);
|
||||
self.niri.activate_monitors(&mut self.backend);
|
||||
}
|
||||
|
||||
let hide_hotkey_overlay =
|
||||
@@ -127,6 +127,17 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
if device.has_capability(input::DeviceCapability::Keyboard) {
|
||||
if let Some(led_state) = self
|
||||
.niri
|
||||
.seat
|
||||
.get_keyboard()
|
||||
.map(|keyboard| keyboard.led_state())
|
||||
{
|
||||
device.led_update(led_state.into());
|
||||
}
|
||||
}
|
||||
|
||||
apply_libinput_settings(&self.niri.config.borrow().input, device);
|
||||
}
|
||||
InputEvent::DeviceRemoved { device } => {
|
||||
@@ -273,7 +284,7 @@ impl State {
|
||||
self.niri.suppressed_keys.clear();
|
||||
}
|
||||
Action::PowerOffMonitors => {
|
||||
self.niri.deactivate_monitors(&self.backend);
|
||||
self.niri.deactivate_monitors(&mut self.backend);
|
||||
}
|
||||
Action::ToggleDebugTint => {
|
||||
self.backend.toggle_debug_tint();
|
||||
@@ -598,6 +609,30 @@ impl State {
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
Action::MoveWorkspaceToMonitorLeft => {
|
||||
if let Some(output) = self.niri.output_left() {
|
||||
self.niri.layout.move_workspace_to_output(&output);
|
||||
self.move_cursor_to_output(&output);
|
||||
}
|
||||
}
|
||||
Action::MoveWorkspaceToMonitorRight => {
|
||||
if let Some(output) = self.niri.output_right() {
|
||||
self.niri.layout.move_workspace_to_output(&output);
|
||||
self.move_cursor_to_output(&output);
|
||||
}
|
||||
}
|
||||
Action::MoveWorkspaceToMonitorDown => {
|
||||
if let Some(output) = self.niri.output_down() {
|
||||
self.niri.layout.move_workspace_to_output(&output);
|
||||
self.move_cursor_to_output(&output);
|
||||
}
|
||||
}
|
||||
Action::MoveWorkspaceToMonitorUp => {
|
||||
if let Some(output) = self.niri.output_up() {
|
||||
self.niri.layout.move_workspace_to_output(&output);
|
||||
self.move_cursor_to_output(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1121,11 +1156,20 @@ impl State {
|
||||
);
|
||||
}
|
||||
|
||||
fn on_gesture_swipe_update<I: InputBackend>(&mut self, event: I::GestureSwipeUpdateEvent) {
|
||||
let res = self
|
||||
.niri
|
||||
.layout
|
||||
.workspace_switch_gesture_update(event.delta_y());
|
||||
fn on_gesture_swipe_update<I: InputBackend>(&mut self, event: I::GestureSwipeUpdateEvent)
|
||||
where
|
||||
I::Device: 'static,
|
||||
{
|
||||
let mut delta_y = event.delta_y();
|
||||
|
||||
let device = event.device();
|
||||
if let Some(device) = (&device as &dyn Any).downcast_ref::<input::Device>() {
|
||||
if device.config_scroll_natural_scroll_enabled() {
|
||||
delta_y = -delta_y;
|
||||
}
|
||||
}
|
||||
|
||||
let res = self.niri.layout.workspace_switch_gesture_update(delta_y);
|
||||
if let Some(output) = res {
|
||||
if let Some(output) = output {
|
||||
self.niri.queue_redraw(output);
|
||||
|
||||
+151
-2
@@ -29,6 +29,7 @@
|
||||
//! compromise we only keep the first workspace there, and move the rest to the primary output,
|
||||
//! making the primary output their original output.
|
||||
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
@@ -103,6 +104,11 @@ pub trait LayoutElement: PartialEq {
|
||||
///
|
||||
/// This will *not* switch immediately after a [`LayoutElement::request_fullscreen()`] call.
|
||||
fn is_fullscreen(&self) -> bool;
|
||||
|
||||
/// Whether we're requesting the element to be fullscreen.
|
||||
///
|
||||
/// This *will* switch immediately after a [`LayoutElement::request_fullscreen()`] call.
|
||||
fn is_pending_fullscreen(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -289,6 +295,11 @@ impl LayoutElement for Window {
|
||||
.states
|
||||
.contains(xdg_toplevel::State::Fullscreen)
|
||||
}
|
||||
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.toplevel()
|
||||
.with_pending_state(|state| state.states.contains(xdg_toplevel::State::Fullscreen))
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Layout<W> {
|
||||
@@ -776,6 +787,27 @@ impl<W: LayoutElement> Layout<W> {
|
||||
mon.workspaces.iter().flat_map(|ws| ws.windows())
|
||||
}
|
||||
|
||||
pub fn with_windows(&self, mut f: impl FnMut(&W, Option<&Output>)) {
|
||||
match &self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mon.workspaces {
|
||||
for win in ws.windows() {
|
||||
f(win, Some(&mon.output));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorSet::NoOutputs { workspaces } => {
|
||||
for ws in workspaces {
|
||||
for win in ws.windows() {
|
||||
f(win, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn active_monitor(&mut self) -> Option<&mut Monitor<W>> {
|
||||
let MonitorSet::Normal {
|
||||
monitors,
|
||||
@@ -1314,6 +1346,47 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_workspace_to_output(&mut self, output: &Output) {
|
||||
let MonitorSet::Normal {
|
||||
monitors,
|
||||
active_monitor_idx,
|
||||
..
|
||||
} = &mut self.monitor_set
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current = &mut monitors[*active_monitor_idx];
|
||||
if current.active_workspace_idx == current.workspaces.len() - 1 {
|
||||
// Insert a new empty workspace.
|
||||
let ws = Workspace::new(current.output.clone(), current.options.clone());
|
||||
current.workspaces.push(ws);
|
||||
}
|
||||
let mut ws = current.workspaces.remove(current.active_workspace_idx);
|
||||
current.active_workspace_idx = current.active_workspace_idx.saturating_sub(1);
|
||||
current.workspace_switch = None;
|
||||
current.clean_up_workspaces();
|
||||
|
||||
ws.set_output(Some(output.clone()));
|
||||
ws.original_output = OutputId::new(output);
|
||||
|
||||
let target_idx = monitors
|
||||
.iter()
|
||||
.position(|mon| &mon.output == output)
|
||||
.unwrap();
|
||||
let target = &mut monitors[target_idx];
|
||||
|
||||
// Insert the workspace after the currently active one. Unless the currently active one is
|
||||
// the last empty workspace, then insert before.
|
||||
let target_ws_idx = min(target.active_workspace_idx + 1, target.workspaces.len() - 1);
|
||||
target.workspaces.insert(target_ws_idx, ws);
|
||||
target.active_workspace_idx = target_ws_idx;
|
||||
target.workspace_switch = None;
|
||||
target.clean_up_workspaces();
|
||||
|
||||
*active_monitor_idx = target_idx;
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&mut self, window: &W, is_fullscreen: bool) {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
@@ -1399,7 +1472,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
for monitor in monitors {
|
||||
if let Some(WorkspaceSwitch::Gesture(gesture)) = &mut monitor.workspace_switch {
|
||||
// Normalize like GNOME Shell's workspace switching.
|
||||
let delta_y = -delta_y / 400.;
|
||||
let delta_y = delta_y / 400.;
|
||||
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(monitor.workspaces.len() - 1) as f64;
|
||||
@@ -1523,6 +1596,7 @@ mod tests {
|
||||
requested_size: Cell<Option<Size<i32, Logical>>>,
|
||||
min_size: Size<i32, Logical>,
|
||||
max_size: Size<i32, Logical>,
|
||||
pending_fullscreen: Cell<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -1542,6 +1616,7 @@ mod tests {
|
||||
requested_size: Cell::new(None),
|
||||
min_size,
|
||||
max_size,
|
||||
pending_fullscreen: Cell::new(false),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1598,9 +1673,12 @@ mod tests {
|
||||
|
||||
fn request_size(&self, size: Size<i32, Logical>) {
|
||||
self.0.requested_size.set(Some(size));
|
||||
self.0.pending_fullscreen.set(false);
|
||||
}
|
||||
|
||||
fn request_fullscreen(&self, _size: Size<i32, Logical>) {}
|
||||
fn request_fullscreen(&self, _size: Size<i32, Logical>) {
|
||||
self.0.pending_fullscreen.set(true);
|
||||
}
|
||||
|
||||
fn min_size(&self) -> Size<i32, Logical> {
|
||||
self.0.min_size
|
||||
@@ -1627,6 +1705,10 @@ mod tests {
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_pending_fullscreen(&self) -> bool {
|
||||
self.0.pending_fullscreen.get()
|
||||
}
|
||||
}
|
||||
|
||||
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
|
||||
@@ -1716,6 +1798,7 @@ mod tests {
|
||||
SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange),
|
||||
SetWindowHeight(#[proptest(strategy = "arbitrary_size_change()")] SizeChange),
|
||||
Communicate(#[proptest(strategy = "1..=5usize")] usize),
|
||||
MoveWorkspaceToOutput(#[proptest(strategy = "1..=5u8")] u8),
|
||||
}
|
||||
|
||||
impl Op {
|
||||
@@ -1889,6 +1972,14 @@ mod tests {
|
||||
layout.update_window(&win);
|
||||
}
|
||||
}
|
||||
Op::MoveWorkspaceToOutput(id) => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
layout.move_workspace_to_output(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1945,6 +2036,9 @@ mod tests {
|
||||
Op::CloseWindow(0),
|
||||
Op::CloseWindow(1),
|
||||
Op::CloseWindow(2),
|
||||
Op::FullscreenWindow(1),
|
||||
Op::FullscreenWindow(2),
|
||||
Op::FullscreenWindow(3),
|
||||
Op::FocusColumnLeft,
|
||||
Op::FocusColumnRight,
|
||||
Op::FocusWindowUp,
|
||||
@@ -1975,6 +2069,7 @@ mod tests {
|
||||
Op::MoveWindowDownOrToWorkspaceDown,
|
||||
Op::MoveWindowUp,
|
||||
Op::MoveWindowUpOrToWorkspaceUp,
|
||||
Op::MoveWorkspaceToOutput(1),
|
||||
];
|
||||
|
||||
for third in every_op {
|
||||
@@ -2072,6 +2167,9 @@ mod tests {
|
||||
Op::CloseWindow(0),
|
||||
Op::CloseWindow(1),
|
||||
Op::CloseWindow(2),
|
||||
Op::FullscreenWindow(1),
|
||||
Op::FullscreenWindow(2),
|
||||
Op::FullscreenWindow(3),
|
||||
Op::FocusColumnLeft,
|
||||
Op::FocusColumnRight,
|
||||
Op::FocusWindowUp,
|
||||
@@ -2389,6 +2487,57 @@ mod tests {
|
||||
check_ops(&ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_workspace_to_output() {
|
||||
let ops = [
|
||||
Op::AddOutput(1),
|
||||
Op::AddOutput(2),
|
||||
Op::FocusOutput(1),
|
||||
Op::AddWindow {
|
||||
id: 0,
|
||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||
min_max_size: Default::default(),
|
||||
},
|
||||
Op::MoveWorkspaceToOutput(2),
|
||||
];
|
||||
|
||||
let mut layout = Layout::default();
|
||||
for op in ops {
|
||||
op.apply(&mut layout);
|
||||
}
|
||||
|
||||
let MonitorSet::Normal {
|
||||
monitors,
|
||||
active_monitor_idx,
|
||||
..
|
||||
} = layout.monitor_set
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
assert_eq!(active_monitor_idx, 1);
|
||||
assert_eq!(monitors[0].workspaces.len(), 1);
|
||||
assert!(!monitors[0].workspaces[0].has_windows());
|
||||
assert_eq!(monitors[1].active_workspace_idx, 0);
|
||||
assert_eq!(monitors[1].workspaces.len(), 2);
|
||||
assert!(monitors[1].workspaces[0].has_windows());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen() {
|
||||
let ops = [
|
||||
Op::AddOutput(1),
|
||||
Op::AddWindow {
|
||||
id: 1,
|
||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
||||
},
|
||||
Op::FullscreenWindow(1),
|
||||
];
|
||||
|
||||
check_ops(&ops);
|
||||
}
|
||||
|
||||
fn arbitrary_spacing() -> impl Strategy<Value = u16> {
|
||||
// Give equal weight to:
|
||||
// - 0: the element is disabled
|
||||
|
||||
+19
-2
@@ -330,6 +330,10 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_size(&self) -> Size<i32, Logical> {
|
||||
self.view_size
|
||||
}
|
||||
|
||||
pub fn update_output_scale_transform(&mut self) {
|
||||
let Some(output) = self.output.as_ref() else {
|
||||
return;
|
||||
@@ -351,7 +355,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn configure_new_window(&self, window: &Window) {
|
||||
pub fn new_window_size(&self) -> Size<i32, Logical> {
|
||||
let width = if let Some(width) = self.options.default_width {
|
||||
let mut width = width.resolve(&self.options, self.working_area.size.w);
|
||||
if !self.options.border.off {
|
||||
@@ -367,8 +371,11 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
height -= self.options.border.width as i32 * 2;
|
||||
}
|
||||
|
||||
let size = Size::from((width, max(height, 1)));
|
||||
Size::from((width, max(height, 1)))
|
||||
}
|
||||
|
||||
pub fn configure_new_window(&self, window: &Window) {
|
||||
let size = self.new_window_size();
|
||||
let bounds = self.toplevel_bounds();
|
||||
|
||||
if let Some(output) = self.output.as_ref() {
|
||||
@@ -1151,8 +1158,14 @@ impl<W: LayoutElement> Column<W> {
|
||||
options,
|
||||
};
|
||||
|
||||
let is_pending_fullscreen = window.is_pending_fullscreen();
|
||||
|
||||
rv.add_window(window);
|
||||
|
||||
if is_pending_fullscreen {
|
||||
rv.set_fullscreen(true);
|
||||
}
|
||||
|
||||
rv
|
||||
}
|
||||
|
||||
@@ -1441,6 +1454,10 @@ impl<W: LayoutElement> Column<W> {
|
||||
if self.is_fullscreen {
|
||||
assert_eq!(self.tiles.len(), 1);
|
||||
}
|
||||
|
||||
for tile in &self.tiles {
|
||||
assert_eq!(self.is_fullscreen, tile.window().is_pending_fullscreen());
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_width(&mut self) {
|
||||
|
||||
+47
-4
@@ -15,6 +15,7 @@ mod input;
|
||||
mod ipc;
|
||||
mod layout;
|
||||
mod niri;
|
||||
mod protocols;
|
||||
mod render_helpers;
|
||||
mod screenshot_ui;
|
||||
mod utils;
|
||||
@@ -26,6 +27,8 @@ mod dummy_pw_utils;
|
||||
mod pw_utils;
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env, mem};
|
||||
@@ -86,7 +89,7 @@ enum Sub {
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Msg {
|
||||
pub enum Msg {
|
||||
/// List connected outputs.
|
||||
Outputs,
|
||||
}
|
||||
@@ -154,7 +157,44 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("starting version {}", &version());
|
||||
|
||||
// Load the config.
|
||||
let path = cli.config.or_else(default_config_path);
|
||||
let mut config_created = false;
|
||||
let path = cli.config.or_else(|| {
|
||||
let default_path = default_config_path()?;
|
||||
let default_parent = default_path.parent().unwrap();
|
||||
|
||||
if let Err(err) = fs::create_dir_all(default_parent) {
|
||||
warn!(
|
||||
"error creating config directories {:?}: {err:?}",
|
||||
default_parent
|
||||
);
|
||||
return Some(default_path);
|
||||
}
|
||||
|
||||
// Create the config and fill it with the default config if it doesn't exist.
|
||||
let new_file = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&default_path);
|
||||
match new_file {
|
||||
Ok(mut new_file) => {
|
||||
let default = include_bytes!("../resources/default-config.kdl");
|
||||
match new_file.write_all(default) {
|
||||
Ok(()) => {
|
||||
config_created = true;
|
||||
info!("wrote default config to {:?}", &default_path);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("error writing config file at {:?}: {err:?}", &default_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {}
|
||||
Err(err) => warn!("error creating config file at {:?}: {err:?}", &default_path),
|
||||
}
|
||||
|
||||
Some(default_path)
|
||||
});
|
||||
|
||||
let mut config_errored = false;
|
||||
let mut config = path
|
||||
@@ -180,7 +220,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
event_loop.handle(),
|
||||
event_loop.get_signal(),
|
||||
display,
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Set WAYLAND_DISPLAY for children.
|
||||
let socket_name = &state.niri.socket_name;
|
||||
@@ -218,7 +259,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
// Set up config file watcher.
|
||||
let _watcher = if let Some(path) = path {
|
||||
let _watcher = if let Some(path) = path.clone() {
|
||||
let (tx, rx) = calloop::channel::sync_channel(1);
|
||||
let watcher = Watcher::new(path.clone(), tx);
|
||||
event_loop
|
||||
@@ -243,6 +284,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Show the config error notification right away if needed.
|
||||
if config_errored {
|
||||
state.niri.config_error_notification.show();
|
||||
} else if config_created {
|
||||
state.niri.config_error_notification.show_created(path);
|
||||
}
|
||||
|
||||
// Run the compositor.
|
||||
|
||||
+102
-40
@@ -103,6 +103,7 @@ use crate::hotkey_overlay::HotkeyOverlay;
|
||||
use crate::input::{apply_libinput_settings, TabletData};
|
||||
use crate::ipc::server::IpcServer;
|
||||
use crate::layout::{Layout, MonitorRenderElement};
|
||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||
use crate::pw_utils::{Cast, PipeWire};
|
||||
use crate::render_helpers::NiriRenderer;
|
||||
use crate::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
|
||||
@@ -151,6 +152,7 @@ pub struct Niri {
|
||||
pub kde_decoration_state: KdeDecorationState,
|
||||
pub layer_shell_state: WlrLayerShellState,
|
||||
pub session_lock_state: SessionLockManagerState,
|
||||
pub foreign_toplevel_state: ForeignToplevelManagerState,
|
||||
pub shm_state: ShmState,
|
||||
pub output_manager_state: OutputManagerState,
|
||||
pub dmabuf_state: DmabufState,
|
||||
@@ -296,7 +298,7 @@ impl State {
|
||||
event_loop: LoopHandle<'static, State>,
|
||||
stop_signal: LoopSignal,
|
||||
display: Display<State>,
|
||||
) -> Self {
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let _span = tracy_client::span!("State::new");
|
||||
|
||||
let config = Rc::new(RefCell::new(config));
|
||||
@@ -305,15 +307,18 @@ impl State {
|
||||
env::var_os("WAYLAND_DISPLAY").is_some() || env::var_os("DISPLAY").is_some();
|
||||
|
||||
let mut backend = if has_display {
|
||||
Backend::Winit(Winit::new(config.clone(), event_loop.clone()))
|
||||
let winit = Winit::new(config.clone(), event_loop.clone())?;
|
||||
Backend::Winit(winit)
|
||||
} else {
|
||||
Backend::Tty(Tty::new(config.clone(), event_loop.clone()))
|
||||
let tty = Tty::new(config.clone(), event_loop.clone())
|
||||
.context("error initializing the TTY backend")?;
|
||||
Backend::Tty(tty)
|
||||
};
|
||||
|
||||
let mut niri = Niri::new(config.clone(), event_loop, stop_signal, display, &backend);
|
||||
backend.init(&mut niri);
|
||||
|
||||
Self { backend, niri }
|
||||
Ok(Self { backend, niri })
|
||||
}
|
||||
|
||||
pub fn refresh_and_flush_clients(&mut self) {
|
||||
@@ -327,6 +332,7 @@ impl State {
|
||||
self.refresh_popup_grab();
|
||||
self.update_keyboard_focus();
|
||||
self.refresh_pointer_focus();
|
||||
foreign_toplevel::refresh(self);
|
||||
|
||||
{
|
||||
let _span = tracy_client::span!("flush_clients");
|
||||
@@ -645,20 +651,26 @@ impl State {
|
||||
let mut resized_outputs = vec![];
|
||||
for output in self.niri.global_space.outputs() {
|
||||
let name = output.name();
|
||||
let scale = self
|
||||
.niri
|
||||
.config
|
||||
.borrow()
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name == name)
|
||||
.map(|c| c.scale)
|
||||
.unwrap_or(1.);
|
||||
let config = self.niri.config.borrow_mut();
|
||||
let config = config.outputs.iter().find(|o| o.name == name);
|
||||
|
||||
let scale = config.map(|c| c.scale).unwrap_or(1.);
|
||||
let scale = scale.clamp(1., 10.).ceil() as i32;
|
||||
if output.current_scale().integer_scale() != scale {
|
||||
|
||||
let mut transform = config
|
||||
.map(|c| c.transform.into())
|
||||
.unwrap_or(Transform::Normal);
|
||||
// FIXME: fix winit damage on other transforms.
|
||||
if name == "winit" {
|
||||
transform = Transform::Flipped180;
|
||||
}
|
||||
|
||||
if output.current_scale().integer_scale() != scale
|
||||
|| output.current_transform() != transform
|
||||
{
|
||||
output.change_current_state(
|
||||
None,
|
||||
None,
|
||||
Some(transform),
|
||||
Some(output::Scale::Integer(scale)),
|
||||
None,
|
||||
);
|
||||
@@ -855,6 +867,11 @@ impl Niri {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
|
||||
let foreign_toplevel_state =
|
||||
ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| {
|
||||
!client.get_data::<ClientState>().unwrap().restricted
|
||||
});
|
||||
|
||||
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
|
||||
seat.add_keyboard(
|
||||
config_.input.keyboard.xkb.to_xkb_config(),
|
||||
@@ -972,6 +989,7 @@ impl Niri {
|
||||
kde_decoration_state,
|
||||
layer_shell_state,
|
||||
session_lock_state,
|
||||
foreign_toplevel_state,
|
||||
text_input_state,
|
||||
input_method_state,
|
||||
virtual_keyboard_state,
|
||||
@@ -1163,18 +1181,25 @@ impl Niri {
|
||||
let global = output.create_global::<State>(&self.display_handle);
|
||||
|
||||
let name = output.name();
|
||||
let scale = self
|
||||
.config
|
||||
.borrow()
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name == name)
|
||||
.map(|c| c.scale)
|
||||
.unwrap_or(1.);
|
||||
let scale = scale.clamp(1., 10.).ceil() as i32;
|
||||
|
||||
// Set scale before adding to the layout since that will read the output size.
|
||||
output.change_current_state(None, None, Some(output::Scale::Integer(scale)), None);
|
||||
let config = self.config.borrow();
|
||||
let c = config.outputs.iter().find(|o| o.name == name);
|
||||
let scale = c.map(|c| c.scale).unwrap_or(1.);
|
||||
let scale = scale.clamp(1., 10.).ceil() as i32;
|
||||
let mut transform = c.map(|c| c.transform.into()).unwrap_or(Transform::Normal);
|
||||
// FIXME: fix winit damage on other transforms.
|
||||
if name == "winit" {
|
||||
transform = Transform::Flipped180;
|
||||
}
|
||||
drop(config);
|
||||
|
||||
// Set scale and transform before adding to the layout since that will read the output size.
|
||||
output.change_current_state(
|
||||
None,
|
||||
Some(transform),
|
||||
Some(output::Scale::Integer(scale)),
|
||||
None,
|
||||
);
|
||||
|
||||
self.layout.add_output(output.clone());
|
||||
|
||||
@@ -1291,14 +1316,16 @@ impl Niri {
|
||||
}
|
||||
|
||||
// If the output size changed with an open screenshot UI, close the screenshot UI.
|
||||
if let Some((old_size, old_scale)) = self.screenshot_ui.output_size(&output) {
|
||||
let output_transform = output.current_transform();
|
||||
if let Some((old_size, old_scale, old_transform)) = self.screenshot_ui.output_size(&output)
|
||||
{
|
||||
let transform = output.current_transform();
|
||||
let output_mode = output.current_mode().unwrap();
|
||||
let size = output_transform.transform_size(output_mode.size);
|
||||
let size = transform.transform_size(output_mode.size);
|
||||
let scale = output.current_scale().integer_scale();
|
||||
// FIXME: scale changes shouldn't matter but they currently do since I haven't quite
|
||||
// figured out how to draw the screenshot textures in physical coordinates.
|
||||
if old_size != size || old_scale != scale {
|
||||
// FIXME: scale changes and transform flips shouldn't matter but they currently do since
|
||||
// I haven't quite figured out how to draw the screenshot textures in
|
||||
// physical coordinates.
|
||||
if old_size != size || old_scale != scale || old_transform != transform {
|
||||
self.screenshot_ui.close();
|
||||
self.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::default_named());
|
||||
@@ -1310,7 +1337,7 @@ impl Niri {
|
||||
self.queue_redraw(output);
|
||||
}
|
||||
|
||||
pub fn deactivate_monitors(&mut self, backend: &Backend) {
|
||||
pub fn deactivate_monitors(&mut self, backend: &mut Backend) {
|
||||
if !self.monitors_active {
|
||||
return;
|
||||
}
|
||||
@@ -1319,7 +1346,7 @@ impl Niri {
|
||||
backend.set_monitors_active(false);
|
||||
}
|
||||
|
||||
pub fn activate_monitors(&mut self, backend: &Backend) {
|
||||
pub fn activate_monitors(&mut self, backend: &mut Backend) {
|
||||
if self.monitors_active {
|
||||
return;
|
||||
}
|
||||
@@ -1738,7 +1765,9 @@ impl Niri {
|
||||
// FIXME we basically need to pick the largest scale factor across the overlapping
|
||||
// outputs, this is how it's usually done in clients as well.
|
||||
let mut cursor_scale = 1;
|
||||
let mut cursor_transform = Transform::Normal;
|
||||
let mut dnd_scale = 1;
|
||||
let mut dnd_transform = Transform::Normal;
|
||||
for output in self.global_space.outputs() {
|
||||
let geo = self.global_space.output_geometry(output).unwrap();
|
||||
|
||||
@@ -1746,6 +1775,9 @@ impl Niri {
|
||||
if let Some(mut overlap) = geo.intersection(bbox) {
|
||||
overlap.loc -= surface_pos;
|
||||
cursor_scale = cursor_scale.max(output.current_scale().integer_scale());
|
||||
// FIXME: using the largest overlapping or "primary" output transform would
|
||||
// make more sense here.
|
||||
cursor_transform = output.current_transform();
|
||||
output_update(output, Some(overlap), surface);
|
||||
} else {
|
||||
output_update(output, None, surface);
|
||||
@@ -1756,6 +1788,9 @@ impl Niri {
|
||||
if let Some(mut overlap) = geo.intersection(bbox) {
|
||||
overlap.loc -= surface_pos;
|
||||
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
|
||||
// FIXME: using the largest overlapping or "primary" output transform
|
||||
// would make more sense here.
|
||||
dnd_transform = output.current_transform();
|
||||
output_update(output, Some(overlap), surface);
|
||||
} else {
|
||||
output_update(output, None, surface);
|
||||
@@ -1764,11 +1799,11 @@ impl Niri {
|
||||
}
|
||||
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, cursor_scale, Transform::Normal);
|
||||
send_surface_state(surface, data, cursor_scale, cursor_transform);
|
||||
});
|
||||
if let Some((surface, _)) = dnd {
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, dnd_scale, Transform::Normal);
|
||||
send_surface_state(surface, data, dnd_scale, dnd_transform);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1785,6 +1820,7 @@ impl Niri {
|
||||
};
|
||||
|
||||
let mut dnd_scale = 1;
|
||||
let mut dnd_transform = Transform::Normal;
|
||||
for output in self.global_space.outputs() {
|
||||
let geo = self.global_space.output_geometry(output).unwrap();
|
||||
|
||||
@@ -1806,15 +1842,18 @@ impl Niri {
|
||||
if let Some(mut overlap) = geo.intersection(bbox) {
|
||||
overlap.loc -= surface_pos;
|
||||
dnd_scale = dnd_scale.max(output.current_scale().integer_scale());
|
||||
// FIXME: using the largest overlapping or "primary" output transform would
|
||||
// make more sense here.
|
||||
dnd_transform = output.current_transform();
|
||||
output_update(output, Some(overlap), surface);
|
||||
} else {
|
||||
output_update(output, None, surface);
|
||||
}
|
||||
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, dnd_scale, Transform::Normal);
|
||||
});
|
||||
}
|
||||
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, dnd_scale, dnd_transform);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2400,9 +2439,13 @@ impl Niri {
|
||||
let _span = tracy_client::span!("Niri::render_for_screen_cast");
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
|
||||
let mut elements = None;
|
||||
let mut casts_to_stop = vec![];
|
||||
|
||||
let mut casts = mem::take(&mut self.casts);
|
||||
for cast in &mut casts {
|
||||
@@ -2414,6 +2457,12 @@ impl Niri {
|
||||
continue;
|
||||
}
|
||||
|
||||
if cast.size != size {
|
||||
debug!("stopping screencast due to output size change");
|
||||
casts_to_stop.push(cast.session_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let last = cast.last_frame_time;
|
||||
let min = cast.min_time_between_frames.get();
|
||||
if last.is_zero() {
|
||||
@@ -2468,6 +2517,10 @@ impl Niri {
|
||||
cast.last_frame_time = target_presentation_time;
|
||||
}
|
||||
self.casts = casts;
|
||||
|
||||
for id in casts_to_stop {
|
||||
self.stop_cast(id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
@@ -2522,6 +2575,9 @@ impl Niri {
|
||||
.cloned()
|
||||
.filter_map(|output| {
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let elements = self.render::<GlesRenderer>(renderer, &output, true);
|
||||
|
||||
@@ -2549,6 +2605,9 @@ impl Niri {
|
||||
let _span = tracy_client::span!("Niri::screenshot");
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let elements = self.render::<GlesRenderer>(renderer, output, true);
|
||||
let pixels = render_to_vec(renderer, size, scale, Fourcc::Abgr8888, &elements)?;
|
||||
@@ -2670,6 +2729,9 @@ impl Niri {
|
||||
let geom = geom.to_physical(output_scale);
|
||||
|
||||
let size = geom.size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
|
||||
let elements = self.render::<GlesRenderer>(renderer, &output, include_pointer);
|
||||
let pixels = render_to_vec(
|
||||
renderer,
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_protocols_wlr;
|
||||
use smithay::reexports::wayland_server::backend::ClientId;
|
||||
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::reexports::wayland_server::{
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||
};
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::shell::xdg::{
|
||||
ToplevelStateSet, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
||||
};
|
||||
use wayland_protocols_wlr::foreign_toplevel::v1::server::{
|
||||
zwlr_foreign_toplevel_handle_v1, zwlr_foreign_toplevel_manager_v1,
|
||||
};
|
||||
use zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1;
|
||||
use zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
|
||||
|
||||
use crate::niri::State;
|
||||
|
||||
const VERSION: u32 = 3;
|
||||
|
||||
pub struct ForeignToplevelManagerState {
|
||||
display: DisplayHandle,
|
||||
instances: Vec<ZwlrForeignToplevelManagerV1>,
|
||||
toplevels: HashMap<WlSurface, ToplevelData>,
|
||||
}
|
||||
|
||||
pub trait ForeignToplevelHandler {
|
||||
fn foreign_toplevel_manager_state(&mut self) -> &mut ForeignToplevelManagerState;
|
||||
fn activate(&mut self, wl_surface: WlSurface);
|
||||
fn close(&mut self, wl_surface: WlSurface);
|
||||
fn set_fullscreen(&mut self, wl_surface: WlSurface, wl_output: Option<WlOutput>);
|
||||
fn unset_fullscreen(&mut self, wl_surface: WlSurface);
|
||||
}
|
||||
|
||||
struct ToplevelData {
|
||||
title: Option<String>,
|
||||
app_id: Option<String>,
|
||||
states: ArrayVec<u32, 3>,
|
||||
output: Option<Output>,
|
||||
instances: HashMap<ZwlrForeignToplevelHandleV1, Vec<WlOutput>>,
|
||||
// FIXME: parent.
|
||||
}
|
||||
|
||||
pub struct ForeignToplevelGlobalData {
|
||||
filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
|
||||
}
|
||||
|
||||
impl ForeignToplevelManagerState {
|
||||
pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
|
||||
where
|
||||
D: GlobalDispatch<ZwlrForeignToplevelManagerV1, ForeignToplevelGlobalData>,
|
||||
D: Dispatch<ZwlrForeignToplevelManagerV1, ()>,
|
||||
D: 'static,
|
||||
F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let global_data = ForeignToplevelGlobalData {
|
||||
filter: Box::new(filter),
|
||||
};
|
||||
display.create_global::<D, ZwlrForeignToplevelManagerV1, _>(VERSION, global_data);
|
||||
Self {
|
||||
display: display.clone(),
|
||||
instances: Vec::new(),
|
||||
toplevels: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(state: &mut State) {
|
||||
let _span = tracy_client::span!("foreign_toplevel::refresh");
|
||||
|
||||
let protocol_state = &mut state.niri.foreign_toplevel_state;
|
||||
|
||||
// Handle closed windows.
|
||||
protocol_state.toplevels.retain(|surface, data| {
|
||||
if state.niri.layout.find_window_and_output(surface).is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
for instance in data.instances.keys() {
|
||||
instance.closed();
|
||||
}
|
||||
|
||||
false
|
||||
});
|
||||
|
||||
// Handle new and existing windows.
|
||||
//
|
||||
// Save the focused window for last, this way when the focus changes, we will first deactivate
|
||||
// the previous window and only then activate the newly focused window.
|
||||
let mut focused = None;
|
||||
state.niri.layout.with_windows(|window, output| {
|
||||
let wl_surface = window.toplevel().wl_surface();
|
||||
|
||||
with_states(wl_surface, |states| {
|
||||
let role = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
if state.niri.keyboard_focus.as_ref() == Some(wl_surface) {
|
||||
focused = Some((window.clone(), output.cloned()));
|
||||
} else {
|
||||
refresh_toplevel(protocol_state, wl_surface, &role, output, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Finally, refresh the focused window.
|
||||
if let Some((window, output)) = focused {
|
||||
let wl_surface = window.toplevel().wl_surface();
|
||||
|
||||
with_states(wl_surface, |states| {
|
||||
let role = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
refresh_toplevel(protocol_state, wl_surface, &role, output.as_ref(), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_output_bound(state: &mut State, output: &Output, wl_output: &WlOutput) {
|
||||
let _span = tracy_client::span!("foreign_toplevel::on_output_bound");
|
||||
|
||||
let Some(client) = wl_output.client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let protocol_state = &mut state.niri.foreign_toplevel_state;
|
||||
for data in protocol_state.toplevels.values_mut() {
|
||||
if data.output.as_ref() != Some(output) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (instance, outputs) in &mut data.instances {
|
||||
if instance.client().as_ref() != Some(&client) {
|
||||
continue;
|
||||
}
|
||||
|
||||
instance.output_enter(wl_output);
|
||||
instance.done();
|
||||
outputs.push(wl_output.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_toplevel(
|
||||
protocol_state: &mut ForeignToplevelManagerState,
|
||||
wl_surface: &WlSurface,
|
||||
role: &XdgToplevelSurfaceRoleAttributes,
|
||||
output: Option<&Output>,
|
||||
has_focus: bool,
|
||||
) {
|
||||
let states = to_state_vec(&role.current.states, has_focus);
|
||||
|
||||
match protocol_state.toplevels.entry(wl_surface.clone()) {
|
||||
Entry::Occupied(entry) => {
|
||||
// Existing window, check if anything changed.
|
||||
let data = entry.into_mut();
|
||||
|
||||
let mut new_title = None;
|
||||
if data.title != role.title {
|
||||
data.title = role.title.clone();
|
||||
new_title = role.title.as_deref();
|
||||
|
||||
if new_title.is_none() {
|
||||
error!("toplevel title changed to None");
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_app_id = None;
|
||||
if data.app_id != role.app_id {
|
||||
data.app_id = role.app_id.clone();
|
||||
new_app_id = role.app_id.as_deref();
|
||||
|
||||
if new_app_id.is_none() {
|
||||
error!("toplevel app_id changed to None");
|
||||
}
|
||||
}
|
||||
|
||||
let mut states_changed = false;
|
||||
if data.states != states {
|
||||
data.states = states;
|
||||
states_changed = true;
|
||||
}
|
||||
|
||||
let mut output_changed = false;
|
||||
if data.output.as_ref() != output {
|
||||
data.output = output.cloned();
|
||||
output_changed = true;
|
||||
}
|
||||
|
||||
let something_changed =
|
||||
new_title.is_some() || new_app_id.is_some() || states_changed || output_changed;
|
||||
|
||||
if something_changed {
|
||||
for (instance, outputs) in &mut data.instances {
|
||||
if let Some(new_title) = new_title {
|
||||
instance.title(new_title.to_owned());
|
||||
}
|
||||
if let Some(new_app_id) = new_app_id {
|
||||
instance.app_id(new_app_id.to_owned());
|
||||
}
|
||||
if states_changed {
|
||||
instance.state(data.states.iter().flat_map(|x| x.to_ne_bytes()).collect());
|
||||
}
|
||||
if output_changed {
|
||||
for wl_output in outputs.drain(..) {
|
||||
instance.output_leave(&wl_output);
|
||||
}
|
||||
if let Some(output) = &data.output {
|
||||
if let Some(client) = instance.client() {
|
||||
for wl_output in output.client_outputs(&client) {
|
||||
instance.output_enter(&wl_output);
|
||||
outputs.push(wl_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
instance.done();
|
||||
}
|
||||
}
|
||||
|
||||
for outputs in data.instances.values_mut() {
|
||||
// Clean up dead wl_outputs.
|
||||
outputs.retain(|x| x.is_alive());
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
// New window, start tracking it.
|
||||
let mut data = ToplevelData {
|
||||
title: role.title.clone(),
|
||||
app_id: role.app_id.clone(),
|
||||
states,
|
||||
output: output.cloned(),
|
||||
instances: HashMap::new(),
|
||||
};
|
||||
|
||||
for manager in &protocol_state.instances {
|
||||
if let Some(client) = manager.client() {
|
||||
data.add_instance::<State>(&protocol_state.display, &client, manager);
|
||||
}
|
||||
}
|
||||
|
||||
entry.insert(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToplevelData {
|
||||
fn add_instance<D>(
|
||||
&mut self,
|
||||
handle: &DisplayHandle,
|
||||
client: &Client,
|
||||
manager: &ZwlrForeignToplevelManagerV1,
|
||||
) where
|
||||
D: Dispatch<ZwlrForeignToplevelHandleV1, ()>,
|
||||
D: 'static,
|
||||
{
|
||||
let toplevel = client
|
||||
.create_resource::<ZwlrForeignToplevelHandleV1, _, D>(handle, manager.version(), ())
|
||||
.unwrap();
|
||||
manager.toplevel(&toplevel);
|
||||
|
||||
if let Some(title) = &self.title {
|
||||
toplevel.title(title.clone());
|
||||
}
|
||||
if let Some(app_id) = &self.app_id {
|
||||
toplevel.app_id(app_id.clone());
|
||||
}
|
||||
|
||||
toplevel.state(self.states.iter().flat_map(|x| x.to_ne_bytes()).collect());
|
||||
|
||||
let mut outputs = Vec::new();
|
||||
if let Some(output) = &self.output {
|
||||
for wl_output in output.client_outputs(client) {
|
||||
toplevel.output_enter(&wl_output);
|
||||
outputs.push(wl_output);
|
||||
}
|
||||
}
|
||||
|
||||
toplevel.done();
|
||||
|
||||
self.instances.insert(toplevel, outputs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> GlobalDispatch<ZwlrForeignToplevelManagerV1, ForeignToplevelGlobalData, D>
|
||||
for ForeignToplevelManagerState
|
||||
where
|
||||
D: GlobalDispatch<ZwlrForeignToplevelManagerV1, ForeignToplevelGlobalData>,
|
||||
D: Dispatch<ZwlrForeignToplevelManagerV1, ()>,
|
||||
D: Dispatch<ZwlrForeignToplevelHandleV1, ()>,
|
||||
D: ForeignToplevelHandler,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut D,
|
||||
handle: &DisplayHandle,
|
||||
client: &Client,
|
||||
resource: New<ZwlrForeignToplevelManagerV1>,
|
||||
_global_data: &ForeignToplevelGlobalData,
|
||||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let manager = data_init.init(resource, ());
|
||||
|
||||
let state = state.foreign_toplevel_manager_state();
|
||||
|
||||
for data in state.toplevels.values_mut() {
|
||||
data.add_instance::<D>(handle, client, &manager);
|
||||
}
|
||||
|
||||
state.instances.push(manager);
|
||||
}
|
||||
|
||||
fn can_view(client: Client, global_data: &ForeignToplevelGlobalData) -> bool {
|
||||
(global_data.filter)(&client)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZwlrForeignToplevelManagerV1, (), D> for ForeignToplevelManagerState
|
||||
where
|
||||
D: Dispatch<ZwlrForeignToplevelManagerV1, ()>,
|
||||
D: ForeignToplevelHandler,
|
||||
{
|
||||
fn request(
|
||||
state: &mut D,
|
||||
_client: &Client,
|
||||
resource: &ZwlrForeignToplevelManagerV1,
|
||||
request: <ZwlrForeignToplevelManagerV1 as Resource>::Request,
|
||||
_data: &(),
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
match request {
|
||||
zwlr_foreign_toplevel_manager_v1::Request::Stop => {
|
||||
resource.finished();
|
||||
|
||||
let state = state.foreign_toplevel_manager_state();
|
||||
state.instances.retain(|x| x != resource);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(
|
||||
state: &mut D,
|
||||
_client: ClientId,
|
||||
resource: &ZwlrForeignToplevelManagerV1,
|
||||
_data: &(),
|
||||
) {
|
||||
let state = state.foreign_toplevel_manager_state();
|
||||
state.instances.retain(|x| x != resource);
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ZwlrForeignToplevelHandleV1, (), D> for ForeignToplevelManagerState
|
||||
where
|
||||
D: Dispatch<ZwlrForeignToplevelHandleV1, ()>,
|
||||
D: ForeignToplevelHandler,
|
||||
{
|
||||
fn request(
|
||||
state: &mut D,
|
||||
_client: &Client,
|
||||
resource: &ZwlrForeignToplevelHandleV1,
|
||||
request: <ZwlrForeignToplevelHandleV1 as Resource>::Request,
|
||||
_data: &(),
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let protocol_state = state.foreign_toplevel_manager_state();
|
||||
|
||||
let Some((surface, _)) = protocol_state
|
||||
.toplevels
|
||||
.iter()
|
||||
.find(|(_, data)| data.instances.contains_key(resource))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let surface = surface.clone();
|
||||
|
||||
match request {
|
||||
zwlr_foreign_toplevel_handle_v1::Request::SetMaximized => (),
|
||||
zwlr_foreign_toplevel_handle_v1::Request::UnsetMaximized => (),
|
||||
zwlr_foreign_toplevel_handle_v1::Request::SetMinimized => (),
|
||||
zwlr_foreign_toplevel_handle_v1::Request::UnsetMinimized => (),
|
||||
zwlr_foreign_toplevel_handle_v1::Request::Activate { .. } => {
|
||||
state.activate(surface);
|
||||
}
|
||||
zwlr_foreign_toplevel_handle_v1::Request::Close => {
|
||||
state.close(surface);
|
||||
}
|
||||
zwlr_foreign_toplevel_handle_v1::Request::SetRectangle { .. } => (),
|
||||
zwlr_foreign_toplevel_handle_v1::Request::Destroy => (),
|
||||
zwlr_foreign_toplevel_handle_v1::Request::SetFullscreen { output } => {
|
||||
state.set_fullscreen(surface, output);
|
||||
}
|
||||
zwlr_foreign_toplevel_handle_v1::Request::UnsetFullscreen => {
|
||||
state.unset_fullscreen(surface);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(
|
||||
state: &mut D,
|
||||
_client: ClientId,
|
||||
resource: &ZwlrForeignToplevelHandleV1,
|
||||
_data: &(),
|
||||
) {
|
||||
let state = state.foreign_toplevel_manager_state();
|
||||
for data in state.toplevels.values_mut() {
|
||||
data.instances.retain(|instance, _| instance != resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_state_vec(states: &ToplevelStateSet, has_focus: bool) -> ArrayVec<u32, 3> {
|
||||
let mut rv = ArrayVec::new();
|
||||
if states.contains(xdg_toplevel::State::Maximized) {
|
||||
rv.push(zwlr_foreign_toplevel_handle_v1::State::Maximized as u32);
|
||||
}
|
||||
if states.contains(xdg_toplevel::State::Fullscreen) {
|
||||
rv.push(zwlr_foreign_toplevel_handle_v1::State::Fullscreen as u32);
|
||||
}
|
||||
|
||||
// HACK: wlr-foreign-toplevel-management states:
|
||||
//
|
||||
// These have the same meaning as the states with the same names defined in xdg-toplevel
|
||||
//
|
||||
// However, clients such as sfwbar and fcitx seem to treat the activated state as keyboard
|
||||
// focus, i.e. they don't expect multiple windows to have it set at once. Even Waybar which
|
||||
// handles multiple activated windows correctly uses it in its design in such a way that
|
||||
// keyboard focus would make more sense. Let's do what the clients expect.
|
||||
if has_focus {
|
||||
rv.push(zwlr_foreign_toplevel_handle_v1::State::Activated as u32);
|
||||
}
|
||||
|
||||
rv
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! delegate_foreign_toplevel {
|
||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1: $crate::protocols::foreign_toplevel::ForeignToplevelGlobalData
|
||||
] => $crate::protocols::foreign_toplevel::ForeignToplevelManagerState);
|
||||
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1: ()
|
||||
] => $crate::protocols::foreign_toplevel::ForeignToplevelManagerState);
|
||||
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: ()
|
||||
] => $crate::protocols::foreign_toplevel::ForeignToplevelManagerState);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod foreign_toplevel;
|
||||
@@ -27,6 +27,7 @@ use smithay::output::Output;
|
||||
use smithay::reexports::calloop::generic::Generic;
|
||||
use smithay::reexports::calloop::{self, Interest, LoopHandle, Mode, PostAction};
|
||||
use smithay::reexports::gbm::Modifier;
|
||||
use smithay::utils::{Physical, Size};
|
||||
use zbus::SignalContext;
|
||||
|
||||
use crate::dbus::mutter_screen_cast::{self, CursorMode, ScreenCastToNiri};
|
||||
@@ -43,6 +44,7 @@ pub struct Cast {
|
||||
_listener: StreamListener<()>,
|
||||
pub is_active: Rc<Cell<bool>>,
|
||||
pub output: Output,
|
||||
pub size: Size<i32, Physical>,
|
||||
pub cursor_mode: CursorMode,
|
||||
pub last_frame_time: Duration,
|
||||
pub min_time_between_frames: Rc<Cell<Duration>>,
|
||||
@@ -112,6 +114,8 @@ impl PipeWire {
|
||||
|
||||
let mode = output.current_mode().unwrap();
|
||||
let size = mode.size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
let refresh = mode.refresh;
|
||||
|
||||
let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new())
|
||||
@@ -383,6 +387,7 @@ impl PipeWire {
|
||||
_listener: listener,
|
||||
is_active,
|
||||
output,
|
||||
size,
|
||||
cursor_mode,
|
||||
last_frame_time: Duration::ZERO,
|
||||
min_time_between_frames,
|
||||
|
||||
@@ -42,6 +42,7 @@ pub enum ScreenshotUi {
|
||||
pub struct OutputData {
|
||||
size: Size<i32, Physical>,
|
||||
scale: i32,
|
||||
transform: Transform,
|
||||
texture: GlesTexture,
|
||||
texture_buffer: TextureBuffer<GlesTexture>,
|
||||
buffers: [SolidColorBuffer; 8],
|
||||
@@ -94,6 +95,7 @@ impl ScreenshotUi {
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let scale = selection.0.current_scale().integer_scale();
|
||||
let selection = (
|
||||
selection.0,
|
||||
@@ -104,9 +106,9 @@ impl ScreenshotUi {
|
||||
let output_data = screenshots
|
||||
.into_iter()
|
||||
.map(|(output, texture)| {
|
||||
let output_transform = output.current_transform();
|
||||
let transform = output.current_transform();
|
||||
let output_mode = output.current_mode().unwrap();
|
||||
let size = output_transform.transform_size(output_mode.size);
|
||||
let size = transform.transform_size(output_mode.size);
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let texture_buffer = TextureBuffer::from_texture(
|
||||
renderer,
|
||||
@@ -129,6 +131,7 @@ impl ScreenshotUi {
|
||||
let data = OutputData {
|
||||
size,
|
||||
scale,
|
||||
transform,
|
||||
texture,
|
||||
texture_buffer,
|
||||
buffers,
|
||||
@@ -333,10 +336,10 @@ impl ScreenshotUi {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_size(&self, output: &Output) -> Option<(Size<i32, Physical>, i32)> {
|
||||
pub fn output_size(&self, output: &Output) -> Option<(Size<i32, Physical>, i32, Transform)> {
|
||||
if let Self::Open { output_data, .. } = self {
|
||||
let data = output_data.get(output)?;
|
||||
Some((data.size, data.scale))
|
||||
Some((data.size, data.scale, data.transform))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user