From b7b58a038998cb5220849a026c56dabbd7617d36 Mon Sep 17 00:00:00 2001
From: Amit Prakash Ambasta <amit.prakash.ambasta@gmail.com>
Date: Sat, 28 Mar 2026 05:20:34 +0530
Subject: [PATCH] refactor: migrate to smithay-client-toolkit 0.20 and
 wayland-client 0.31

Upgrade all Wayland dependencies to their latest versions:
- smithay-client-toolkit 0.15 -> 0.20
- wayland-client 0.29 -> 0.31
- wayland-protocols 0.29 -> 0.32
- Add wayland-protocols-wlr 0.3 for layer shell support
- Bump nix, memmap2, os_pipe, lazy_static, and toml

Rewrite App to use the new smithay-client-toolkit delegate-based
architecture, replacing the old GlobalManager/SeatHandler pattern with
CompositorHandler, SeatHandler, KeyboardHandler, PointerHandler,
OutputHandler, LayerShellHandler, and ShmHandler trait implementations.
Move the event loop from main.rs into App::run(), eliminating the
manual poll/channel-based command proxy thread.

Update keyboard input API from raw keycodes (u32) and KeyState to
Keysym and Modifiers across Cmd, Widget trait, and Login widget.

Apply Rust 2021 edition idioms in login widget: use str::repeat(),
str::is_empty(), remove unnecessary borrows and format! args, and
simplify match arms with if-let.

Add unit tests for Color and Config modules.

Signed-off-by: Amit Prakash Ambasta <amit.prakash.ambasta@gmail.com>
---
 Cargo.lock           | 759 ++++++++++++++++++++++-----------
 Cargo.toml           |  22 +-
 src/app.rs           | 968 +++++++++++++++++++++++--------------------
 src/cmd.rs           |   7 +-
 src/color.rs         | 106 +++++
 src/config.rs        |  66 +++
 src/main.rs          |  95 +----
 src/widget.rs        |   7 +-
 src/widgets/login.rs |  47 +--
 9 files changed, 1248 insertions(+), 829 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2a323bf..3d37f50 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,18 +1,12 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "ab_glyph_rasterizer"
-version = "0.1.8"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
-
-[[package]]
-name = "android-tzdata"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
 
 [[package]]
 name = "android_system_properties"
@@ -25,72 +19,128 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.2.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
 [[package]]
 name = "bitflags"
-version = "1.3.2"
+version = "2.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
 
 [[package]]
 name = "bumpalo"
-version = "3.16.0"
+version = "3.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
 name = "calloop"
-version = "0.9.3"
+version = "0.14.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82"
+checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7"
 dependencies = [
- "log",
- "nix 0.22.3",
+ "bitflags",
+ "polling",
+ "rustix",
+ "slab",
+ "tracing",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
+dependencies = [
+ "calloop",
+ "rustix",
+ "wayland-backend",
+ "wayland-client",
 ]
 
 [[package]]
 name = "cc"
-version = "1.0.95"
+version = "1.2.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
+checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
 
 [[package]]
 name = "cfg-if"
-version = "1.0.0"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
 
 [[package]]
 name = "chrono"
-version = "0.4.38"
+version = "0.4.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
 dependencies = [
- "android-tzdata",
  "iana-time-zone",
  "js-sys",
  "num-traits",
  "wasm-bindgen",
- "windows-targets",
+ "windows-link",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
 ]
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.6"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
-name = "dlib"
-version = "0.5.2"
+name = "crossbeam-utils"
+version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
-dependencies = [
- "libloading",
-]
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "cursor-icon"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
 
 [[package]]
 name = "downcast-rs"
@@ -98,36 +148,71 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
 
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
 [[package]]
 name = "getopts"
-version = "0.2.21"
+version = "0.2.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
 dependencies = [
  "unicode-width",
 ]
 
 [[package]]
 name = "greetd_ipc"
-version = "0.10.0"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1600ad23798daf53f5c336ebca8a7c603696ef4455103e9c713fab574131eb35"
+checksum = "700d2e5bfc91a042d1d531b46de245085c6a179debedf149aa97c7ca71492078"
 dependencies = [
  "serde",
  "serde_json",
- "thiserror",
+ "thiserror 1.0.69",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
 [[package]]
 name = "iana-time-zone"
-version = "0.1.60"
+version = "0.1.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
  "iana-time-zone-haiku",
  "js-sys",
+ "log",
  "wasm-bindgen",
  "windows-core",
 ]
@@ -141,126 +226,103 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "indexmap"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
 [[package]]
 name = "itoa"
-version = "1.0.11"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
 
 [[package]]
 name = "js-sys"
-version = "0.3.69"
+version = "0.3.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
 [[package]]
 name = "lazy_static"
-version = "1.4.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.183"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
 
 [[package]]
-name = "libloading"
-version = "0.8.3"
+name = "linux-raw-sys"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
-dependencies = [
- "cfg-if",
- "windows-targets",
-]
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
 
 [[package]]
 name = "log"
-version = "0.4.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
-
-[[package]]
-name = "memmap2"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "memoffset"
-version = "0.6.5"
+version = "0.4.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
-dependencies = [
- "autocfg",
-]
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
 
 [[package]]
-name = "nix"
-version = "0.22.3"
+name = "memchr"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
-dependencies = [
- "bitflags",
- "cc",
- "cfg-if",
- "libc",
- "memoffset",
-]
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
 
 [[package]]
-name = "nix"
-version = "0.24.3"
+name = "memmap2"
+version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
 dependencies = [
- "bitflags",
- "cfg-if",
  "libc",
- "memoffset",
 ]
 
 [[package]]
 name = "nix"
-version = "0.25.1"
+version = "0.31.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
+checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
 dependencies = [
- "autocfg",
  "bitflags",
  "cfg-if",
+ "cfg_aliases",
  "libc",
- "memoffset",
- "pin-utils",
 ]
 
 [[package]]
 name = "num-traits"
-version = "0.2.18"
+version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 dependencies = [
  "autocfg",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.19.0"
+version = "1.21.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
 
 [[package]]
 name = "os_pipe"
-version = "1.1.5"
+version = "1.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
+checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
 dependencies = [
  "libc",
  "windows-sys",
@@ -276,35 +338,71 @@ dependencies = [
 ]
 
 [[package]]
-name = "pin-utils"
-version = "0.1.0"
+name = "pin-project-lite"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.30"
+version = "0.3.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys",
+]
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.81"
+version = "1.0.106"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
 dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "quick-xml"
+version = "0.39.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "quote"
-version = "1.0.36"
+version = "1.0.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
 dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
 [[package]]
 name = "rusttype"
 version = "0.9.3"
@@ -316,31 +414,35 @@ dependencies = [
 ]
 
 [[package]]
-name = "ryu"
-version = "1.0.17"
+name = "rustversion"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
 
 [[package]]
-name = "scoped-tls"
-version = "1.0.1"
+name = "serde"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
 
 [[package]]
-name = "serde"
-version = "1.0.198"
+name = "serde_core"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.198"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -349,45 +451,79 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.116"
+version = "1.0.149"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
 dependencies = [
  "itoa",
- "ryu",
+ "memchr",
  "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98"
+dependencies = [
+ "serde_core",
 ]
 
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
 [[package]]
 name = "smallvec"
-version = "1.13.2"
+version = "1.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
 
 [[package]]
 name = "smithay-client-toolkit"
-version = "0.15.4"
+version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3"
+checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
 dependencies = [
  "bitflags",
+ "bytemuck",
  "calloop",
- "dlib",
- "lazy_static",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
  "log",
  "memmap2",
- "nix 0.22.3",
  "pkg-config",
+ "rustix",
+ "thiserror 2.0.18",
+ "wayland-backend",
  "wayland-client",
+ "wayland-csd-frame",
  "wayland-cursor",
  "wayland-protocols",
+ "wayland-protocols-experimental",
+ "wayland-protocols-misc",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkbcommon",
+ "xkeysym",
 ]
 
 [[package]]
 name = "syn"
-version = "2.0.60"
+version = "2.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -396,18 +532,38 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.58"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
 dependencies = [
- "thiserror-impl",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.58"
+version = "2.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -416,13 +572,60 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.5.11"
+version = "1.1.0+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc"
 dependencies = [
- "serde",
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.0+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f"
+dependencies = [
+ "serde_core",
 ]
 
+[[package]]
+name = "toml_parser"
+version = "1.1.0+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.0+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+
 [[package]]
 name = "ttf-parser"
 version = "0.15.2"
@@ -431,46 +634,34 @@ checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.11"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.92"
+version = "0.2.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
 dependencies = [
  "cfg-if",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
-dependencies = [
- "bumpalo",
- "log",
  "once_cell",
- "proc-macro2",
- "quote",
- "syn",
+ "rustversion",
+ "wasm-bindgen-macro",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.92"
+version = "0.2.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -478,177 +669,217 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.92"
+version = "0.2.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
 dependencies = [
+ "bumpalo",
  "proc-macro2",
  "quote",
  "syn",
- "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.92"
+version = "0.2.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix",
+ "smallvec",
+ "wayland-sys",
+]
 
 [[package]]
 name = "wayland-client"
-version = "0.29.5"
+version = "0.31.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
+checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3"
 dependencies = [
  "bitflags",
- "downcast-rs",
- "libc",
- "nix 0.24.3",
- "scoped-tls",
- "wayland-commons",
+ "rustix",
+ "wayland-backend",
  "wayland-scanner",
- "wayland-sys",
 ]
 
 [[package]]
-name = "wayland-commons"
-version = "0.29.5"
+name = "wayland-csd-frame"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
+checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
 dependencies = [
- "nix 0.24.3",
- "once_cell",
- "smallvec",
- "wayland-sys",
+ "bitflags",
+ "cursor-icon",
+ "wayland-backend",
 ]
 
 [[package]]
 name = "wayland-cursor"
-version = "0.29.5"
+version = "0.31.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
+checksum = "4b3298683470fbdc6ca40151dfc48c8f2fd4c41a26e13042f801f85002384091"
 dependencies = [
- "nix 0.24.3",
+ "rustix",
  "wayland-client",
  "xcursor",
 ]
 
 [[package]]
 name = "wayland-protocols"
-version = "0.29.5"
+version = "0.32.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
+checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7"
 dependencies = [
  "bitflags",
+ "wayland-backend",
  "wayland-client",
- "wayland-commons",
  "wayland-scanner",
 ]
 
 [[package]]
-name = "wayland-scanner"
-version = "0.29.5"
+name = "wayland-protocols-experimental"
+version = "20250721.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
+checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
 dependencies = [
- "proc-macro2",
- "quote",
- "xml-rs",
+ "bitflags",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
 ]
 
 [[package]]
-name = "wayland-sys"
-version = "0.29.5"
+name = "wayland-protocols-misc"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
+checksum = "429b99200febaf95d4f4e46deff6fe4382bcff3280ee16a41cf887b3c3364984"
 dependencies = [
- "dlib",
- "lazy_static",
- "pkg-config",
+ "bitflags",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
 ]
 
 [[package]]
-name = "windows-core"
-version = "0.52.0"
+name = "wayland-protocols-wlr"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235"
 dependencies = [
- "windows-targets",
+ "bitflags",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
 ]
 
 [[package]]
-name = "windows-sys"
-version = "0.52.0"
+name = "wayland-scanner"
+version = "0.31.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3"
 dependencies = [
- "windows-targets",
+ "proc-macro2",
+ "quick-xml",
+ "quote",
 ]
 
 [[package]]
-name = "windows-targets"
-version = "0.52.5"
+name = "wayland-sys"
+version = "0.31.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "pkg-config",
 ]
 
 [[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.5"
+name = "windows-core"
+version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
 
 [[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.5"
+name = "windows-implement"
+version = "0.60.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
-name = "windows_i686_gnu"
-version = "0.52.5"
+name = "windows-interface"
+version = "0.59.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.5"
+name = "windows-link"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
 
 [[package]]
-name = "windows_i686_msvc"
-version = "0.52.5"
+name = "windows-result"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
 
 [[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.5"
+name = "windows-strings"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
 
 [[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.5"
+name = "windows-sys"
+version = "0.61.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
 
 [[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.5"
+name = "winnow"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
 
 [[package]]
 name = "wlgreet"
@@ -659,24 +890,46 @@ dependencies = [
  "greetd_ipc",
  "lazy_static",
  "memmap2",
- "nix 0.25.1",
+ "nix",
  "os_pipe",
  "rusttype",
  "serde",
+ "serde_json",
  "smithay-client-toolkit",
  "toml",
  "wayland-client",
  "wayland-protocols",
+ "wayland-protocols-wlr",
 ]
 
 [[package]]
 name = "xcursor"
-version = "0.3.5"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
+
+[[package]]
+name = "xkbcommon"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9"
+dependencies = [
+ "libc",
+ "memmap2",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
+checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+dependencies = [
+ "bytemuck",
+]
 
 [[package]]
-name = "xml-rs"
-version = "0.8.20"
+name = "zmij"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/Cargo.toml b/Cargo.toml
index e1b166a..2255aec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 name = "wlgreet"
 version = "0.5.0"
 authors = ["Kenny Levinsen <kl@kl.wtf>"]
-edition = "2018"
+edition = "2021"
 
 [profile.release]
 lto = "fat"
@@ -11,16 +11,20 @@ lto = "fat"
 damage_debug = []
 
 [dependencies]
-smithay-client-toolkit = "0.15.2"
+smithay-client-toolkit = "0.20.0"
 rusttype = "0.9"
 chrono = "0.4"
-nix = "0.25"
-memmap2 = "0.3"
-os_pipe = "1.1"
-wayland-client = { version = "0.29" }
-wayland-protocols = { version = "0.29", features = ["client", "unstable_protocols"] }
-lazy_static = "1.4"
+nix = { version = "0.31", features = ["poll"] }
+memmap2 = "0.9"
+os_pipe = "1.2"
+wayland-client = "0.31"
+wayland-protocols = { version = "0.32", features = ["client"] }
+wayland-protocols-wlr = { version = "0.3", features = ["client"] }
+lazy_static = "1.5"
 serde = { version = "1.0", features = ["derive"] }
 greetd_ipc = { version = "0.10", features = ["sync-codec"] }
 getopts = "0.2"
-toml = "0.5"
+toml = "1.1"
+
+[dev-dependencies]
+serde_json = "1.0"
diff --git a/src/app.rs b/src/app.rs
index a0987e8..f444e8b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,260 +1,226 @@
 use std::collections::VecDeque;
-use std::sync::mpsc::Sender;
 use std::sync::{Arc, Mutex};
 
 use chrono::Local;
 
-use smithay_client_toolkit::environment::MultiGlobalHandler;
-use smithay_client_toolkit::seat::{
-    keyboard::{keysyms, map_keyboard, Event as KbEvent, KeyState, ModifiersState},
-    SeatHandler,
+use smithay_client_toolkit::{
+    compositor::{CompositorHandler, CompositorState},
+    delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer,
+    delegate_registry, delegate_seat, delegate_shm,
+    output::{OutputHandler, OutputState},
+    registry::{ProvidesRegistryState, RegistryState},
+    registry_handlers,
+    seat::{
+        keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers, RawModifiers},
+        pointer::{PointerEvent, PointerEventKind, PointerHandler},
+        Capability, SeatHandler, SeatState,
+    },
+    shell::{
+        wlr_layer::{
+            Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
+            LayerSurfaceConfigure,
+        },
+        WaylandSurface,
+    },
+    shm::{slot::SlotPool, Shm, ShmHandler},
 };
-
-use wayland_client::protocol::{wl_compositor, wl_output, wl_pointer, wl_shm, wl_surface};
 use wayland_client::{
-    Attached, DispatchData, Display, EventQueue, GlobalEvent, GlobalManager, Main,
-};
-use wayland_protocols::wlr::unstable::layer_shell::v1::client::{
-    zwlr_layer_shell_v1, zwlr_layer_surface_v1,
+    globals::registry_queue_init,
+    protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+    Connection, EventQueue, QueueHandle,
 };
 
 use crate::buffer::Buffer;
-use crate::color::Color;
+use crate::cmd::Cmd;
 use crate::config::{Config, OutputMode};
 use crate::widget::{DrawContext, Widget};
 
-use crate::cmd::Cmd;
-use crate::doublemempool::DoubleMemPool;
-
-struct AppInner {
-    compositor: Option<Main<wl_compositor::WlCompositor>>,
-    surfaces: Vec<wl_surface::WlSurface>,
-    shell_surfaces: Vec<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>,
-    configured_surfaces: Arc<Mutex<usize>>,
-    outputs: Vec<(u32, Attached<wl_output::WlOutput>)>,
-    shell: Option<Main<zwlr_layer_shell_v1::ZwlrLayerShellV1>>,
-    seats: SeatHandler,
-    draw_tx: Sender<Cmd>,
-    output_mode: OutputMode,
-    visible: bool,
-    scale: u32,
+pub struct App {
+    // Wayland state
+    registry_state: RegistryState,
+    seat_state: SeatState,
+    output_state: OutputState,
+    compositor_state: CompositorState,
+    layer_shell: LayerShell,
+    shm: Shm,
+
+    // Surfaces
+    layers: Vec<LayerSurface>,
+    configured: usize,
+
+    // Input
+    keyboard: Option<wl_keyboard::WlKeyboard>,
+    pointer: Option<wl_pointer::WlPointer>,
+    pointer_pos: (u32, u32),
+    modifiers: Modifiers,
+
+    // Drawing
+    pool: SlotPool,
+
+    // App state
+    config: Config,
+    cmd_queue: Arc<Mutex<VecDeque<Cmd>>>,
+    widget: Option<Box<dyn Widget + Send>>,
+    exit: bool,
+    last_damage: Option<Vec<(i32, i32, i32, i32)>>,
+    last_dim: (u32, u32),
+
+    // Connection
+    conn: Connection,
+    event_queue: Option<EventQueue<Self>>,
 }
 
-impl AppInner {
-    fn new(tx: Sender<Cmd>, output_mode: OutputMode, scale: u32) -> AppInner {
-        AppInner {
-            compositor: None,
-            surfaces: Vec::new(),
-            shell_surfaces: Vec::new(),
-            configured_surfaces: Arc::new(Mutex::new(0)),
-            outputs: Vec::new(),
-            shell: None,
-            seats: SeatHandler::new(),
-            draw_tx: tx,
-            output_mode: output_mode,
-            visible: true,
-            scale: scale,
-        }
-    }
+impl App {
+    pub fn new(config: Config) -> App {
+        let conn = Connection::connect_to_env().unwrap();
+        let (globals, event_queue) = registry_queue_init(&conn).unwrap();
+        let qh = event_queue.handle();
 
-    fn add_shell_surface(
-        compositor: &wl_compositor::WlCompositor,
-        shell: &zwlr_layer_shell_v1::ZwlrLayerShellV1,
-        scale: u32,
-        configured_surfaces: Arc<Mutex<usize>>,
-        tx: Sender<Cmd>,
-        output: Option<&wl_output::WlOutput>,
-    ) -> (
-        wl_surface::WlSurface,
-        zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
-    ) {
-        let surface = compositor.create_surface();
-
-        let this_is_stupid = Arc::new(Mutex::new(false));
-
-        let shell_surface = shell.get_layer_surface(
-            &surface,
-            output,
-            zwlr_layer_shell_v1::Layer::Overlay,
-            "".to_string(),
-        );
-        shell_surface.quick_assign(move |layer, evt, _| match evt {
-            zwlr_layer_surface_v1::Event::Configure { serial, .. } => {
-                let mut x = this_is_stupid.lock().unwrap();
-                if !*x {
-                    *x = true;
-                    *(configured_surfaces.lock().unwrap()) += 1;
-                    layer.ack_configure(serial);
-                    tx.send(Cmd::ForceDraw).unwrap();
-                }
-            }
-            _ => unreachable!(),
-        });
+        let compositor_state =
+            CompositorState::bind(&globals, &qh).expect("wl_compositor not available");
+        let layer_shell =
+            LayerShell::bind(&globals, &qh).expect("zwlr_layer_shell_v1 not available");
+        let shm = Shm::bind(&globals, &qh).expect("wl_shm not available");
 
-        shell_surface
-            .set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive);
-        shell_surface.set_size(1, 1);
-        surface.set_buffer_scale(scale as i32);
-        surface.commit();
-        (surface.detach(), shell_surface.detach())
-    }
+        let pool = SlotPool::new(1024 * 128 * 4, &shm).expect("Failed to create pool");
 
-    fn outputs_changed(&mut self) {
-        let shell = match self.shell {
-            Some(ref shell) => shell.to_owned(),
-            None => return,
-        };
-        let compositor = match self.compositor {
-            Some(ref c) => c.to_owned(),
-            None => return,
-        };
+        let cmd_queue = Arc::new(Mutex::new(VecDeque::new()));
 
-        for shell_surface in self.shell_surfaces.iter() {
-            shell_surface.destroy();
-        }
-        for surface in self.surfaces.iter() {
-            surface.destroy();
-        }
+        let mut app = App {
+            registry_state: RegistryState::new(&globals),
+            seat_state: SeatState::new(&globals, &qh),
+            output_state: OutputState::new(&globals, &qh),
+            compositor_state,
+            layer_shell,
+            shm,
+
+            layers: Vec::new(),
+            configured: 0,
+
+            keyboard: None,
+            pointer: None,
+            pointer_pos: (0, 0),
+            modifiers: Modifiers {
+                ctrl: false,
+                alt: false,
+                shift: false,
+                caps_lock: false,
+                logo: false,
+                num_lock: false,
+            },
 
-        self.configured_surfaces = Arc::new(Mutex::new(0));
+            pool,
 
-        if self.visible {
-            match self.output_mode {
-                OutputMode::Active => {
-                    if self.shell_surfaces.len() > 0 {
-                        return;
-                    }
-                    let (surface, shell_surface) = AppInner::add_shell_surface(
-                        &compositor,
-                        &shell,
-                        self.scale,
-                        self.configured_surfaces.clone(),
-                        self.draw_tx.clone(),
+            config,
+            cmd_queue,
+            widget: None,
+            exit: false,
+            last_damage: None,
+            last_dim: (0, 0),
+
+            conn,
+            event_queue: Some(event_queue),
+        };
+
+        app.create_surfaces();
+        app
+    }
+
+    fn create_surfaces(&mut self) {
+        // Clean up old surfaces
+        for layer in self.layers.drain(..) {
+            drop(layer);
+        }
+        self.configured = 0;
+
+        let qh = self.event_queue.as_ref().unwrap().handle();
+
+        match self.config.output_mode {
+            OutputMode::Active => {
+                let surface = self.compositor_state.create_surface(&qh);
+                let layer = self.layer_shell.create_layer_surface(
+                    &qh,
+                    surface,
+                    Layer::Overlay,
+                    Some("wlgreet"),
+                    None,
+                );
+                layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
+                layer.set_size(1, 1);
+                layer.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
+                layer.commit();
+                self.layers.push(layer);
+            }
+            OutputMode::All => {
+                // Create one surface per output
+                let outputs: Vec<wl_output::WlOutput> = self
+                    .output_state
+                    .outputs()
+                    .collect();
+                if outputs.is_empty() {
+                    // No outputs yet, create one without an output binding
+                    let surface = self.compositor_state.create_surface(&qh);
+                    let layer = self.layer_shell.create_layer_surface(
+                        &qh,
+                        surface,
+                        Layer::Overlay,
+                        Some("wlgreet"),
                         None,
                     );
-                    self.surfaces = vec![surface];
-                    self.shell_surfaces = vec![shell_surface];
-                }
-                OutputMode::All => {
-                    let mut surfaces = Vec::new();
-                    let mut shell_surfaces = Vec::new();
-                    for output in self.outputs.iter() {
-                        let (surface, shell_surface) = AppInner::add_shell_surface(
-                            &compositor,
-                            &shell,
-                            self.scale,
-                            self.configured_surfaces.clone(),
-                            self.draw_tx.clone(),
-                            Some(&output.1),
+                    layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
+                    layer.set_size(1, 1);
+                    layer.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
+                    layer.commit();
+                    self.layers.push(layer);
+                } else {
+                    for output in outputs {
+                        let surface = self.compositor_state.create_surface(&qh);
+                        let layer = self.layer_shell.create_layer_surface(
+                            &qh,
+                            surface,
+                            Layer::Overlay,
+                            Some("wlgreet"),
+                            Some(&output),
                         );
-                        surfaces.push(surface);
-                        shell_surfaces.push(shell_surface);
+                        layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
+                        layer.set_size(1, 1);
+                        layer
+                            .set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
+                        layer.commit();
+                        self.layers.push(layer);
                     }
-                    self.surfaces = surfaces;
-                    self.shell_surfaces = shell_surfaces;
                 }
             }
-            self.draw_tx.send(Cmd::ForceDraw).unwrap();
-        } else {
-            self.surfaces = Vec::new();
-            self.shell_surfaces = Vec::new();
         }
     }
 
-    fn add_output(&mut self, id: u32, output: Attached<wl_output::WlOutput>) {
-        self.outputs.push((id, output));
-        self.outputs_changed();
-    }
-
-    fn remove_output(&mut self, id: u32) {
-        let old_output = self.outputs.iter().find(|(output_id, _)| *output_id == id);
-        if let Some(output) = old_output {
-            let new_outputs = self
-                .outputs
-                .iter()
-                .filter(|(output_id, _)| *output_id != id)
-                .map(|(x, y)| (x.clone(), y.clone()))
-                .collect();
-            if output.1.as_ref().version() >= 3 {
-                output.1.release()
-            }
-            self.outputs = new_outputs;
-            self.outputs_changed();
-        }
-    }
-
-    fn set_compositor(&mut self, compositor: Option<Main<wl_compositor::WlCompositor>>) {
-        self.compositor = compositor
-    }
-
-    fn set_shell(&mut self, shell: Option<Main<zwlr_layer_shell_v1::ZwlrLayerShellV1>>) {
-        self.shell = shell
-    }
-}
-
-pub struct App {
-    config: Config,
-    pools: DoubleMemPool,
-    display: Display,
-    event_queue: EventQueue,
-    cmd_queue: Arc<Mutex<VecDeque<Cmd>>>,
-    widget: Option<Box<dyn Widget + Send>>,
-    inner: Arc<Mutex<AppInner>>,
-    last_damage: Option<Vec<(i32, i32, i32, i32)>>,
-    last_dim: (u32, u32),
-}
-
-impl App {
     pub fn redraw(&mut self, mut force: bool) -> Result<(), ::std::io::Error> {
         let widget = match self.widget {
             Some(ref mut widget) => widget,
             None => return Ok(()),
         };
 
-        let inner = self.inner.lock().unwrap();
         let time = Local::now();
 
-        if inner.shell_surfaces.len() != *inner.configured_surfaces.lock().unwrap() {
-            // Not ready yet
+        if self.layers.is_empty() || self.configured < self.layers.len() {
             return Ok(());
         }
 
-        let (last, pool) = match self.pools.pool() {
-            Some((last, pool)) => (last, pool),
-            None => return Ok(()),
-        };
-
         let size = widget.size();
         let size_changed = self.last_dim != size;
+        let stride = 4 * size.0 as i32;
+        let buf_size = (4 * size.0 * size.1) as usize;
 
-        // resize the pool if relevant
-        pool.resize((4 * size.0 * size.1) as usize)
-            .expect("Failed to resize the memory pool.");
-        let mmap = pool.mmap();
-        let mut buf = Buffer::new(mmap, size);
+        // Render into a temporary buffer
+        let mut mmap = memmap2::MmapMut::map_anon(buf_size)
+            .expect("Failed to create anonymous mmap");
+        let mut buf = Buffer::new(&mut mmap, size);
 
-        // Copy old damage
-        if let Some(d) = &self.last_damage {
-            if !size_changed {
-                let lastmmap = last.mmap();
-                let last = Buffer::new(lastmmap, size);
-
-                if cfg!(feature = "damage_debug") {
-                    buf.memset(&Color::new(0.5, 0.75, 0.75, 1.0));
-                }
-                for d in d {
-                    last.copy_to(&mut buf, d.clone());
-                }
-            } else {
-                force = true;
-            }
-        } else {
+        if force || size_changed || self.last_damage.is_none() {
             force = true;
-        }
-
-        if force {
             buf.memset(&self.config.background);
         }
+
         let report = widget.draw(
             &mut DrawContext {
                 buf: &mut buf,
@@ -266,28 +232,32 @@ impl App {
             (0, 0),
         )?;
 
-        mmap.flush().unwrap();
-
-        if !size_changed && !report.full_damage && report.damage.len() == 0 {
-            // Nothing to do
+        if !size_changed && !report.full_damage && report.damage.is_empty() {
             return Ok(());
         }
 
-        // get a buffer and attach it
-        let new_buffer = pool.buffer(
-            0,
-            report.width as i32,
-            report.height as i32,
-            4 * size.0 as i32,
-            wl_shm::Format::Argb8888,
-        );
-        if size_changed {
-            for shell_surface in inner.shell_surfaces.iter() {
-                shell_surface.set_size(size.0 / inner.scale, size.1 / inner.scale);
+        // Create a separate wl_buffer per surface (each can only be attached once)
+        for layer in &self.layers {
+            let (wl_buffer, canvas) = self
+                .pool
+                .create_buffer(
+                    size.0 as i32,
+                    size.1 as i32,
+                    stride,
+                    wl_shm::Format::Argb8888,
+                )
+                .expect("create buffer");
+
+            canvas[..buf_size].copy_from_slice(&mmap[..buf_size]);
+
+            let surface = layer.wl_surface();
+            wl_buffer.attach_to(surface).expect("buffer attach");
+
+            if size_changed {
+                layer.set_size(size.0 / self.config.scale, size.1 / self.config.scale);
             }
-        }
-        for surface in inner.surfaces.iter() {
-            surface.attach(Some(&new_buffer), 0, 0);
+            surface.set_buffer_scale(self.config.scale as i32);
+
             if cfg!(feature = "damage_debug") || force || report.full_damage {
                 surface.damage_buffer(0, 0, size.0 as i32, size.1 as i32);
             } else {
@@ -295,8 +265,9 @@ impl App {
                     surface.damage_buffer(d.0, d.1, d.2, d.3);
                 }
             }
-            surface.commit();
+            layer.commit();
         }
+
         self.last_damage = if force || report.full_damage {
             Some(vec![(0, 0, size.0 as i32, size.1 as i32)])
         } else {
@@ -310,18 +281,6 @@ impl App {
         self.cmd_queue.clone()
     }
 
-    pub fn display(&mut self) -> &mut Display {
-        &mut self.display
-    }
-
-    pub fn flush_display(&mut self) {
-        self.display.flush().expect("unable to flush display");
-    }
-
-    pub fn event_queue(&mut self) -> &mut EventQueue {
-        &mut self.event_queue
-    }
-
     pub fn get_widget(&mut self) -> &mut Box<dyn Widget + Send> {
         self.widget.as_mut().unwrap()
     }
@@ -331,223 +290,354 @@ impl App {
         self.redraw(true)
     }
 
-    pub fn new(tx: Sender<Cmd>, config: Config) -> App {
-        let inner = Arc::new(Mutex::new(AppInner::new(
-            tx.clone(),
-            config.output_mode,
-            config.scale,
-        )));
-
-        //
-        // Set up modules
-        //
-
-        let cmd_queue = Arc::new(Mutex::new(VecDeque::new()));
+    pub fn run(&mut self) {
+        self.cmd_queue.lock().unwrap().push_back(Cmd::Draw);
 
-        let display = Display::connect_to_env().unwrap();
-
-        let mut event_queue = display.create_event_queue();
-
-        //
-        // Set up global manager and get seats
-        //
-        let inner_global = inner.clone();
-        let manager = GlobalManager::new_with_cb(
-            &display.attach(event_queue.token()),
-            move |evt, registry, ddata: DispatchData| match evt {
-                GlobalEvent::New {
-                    id,
-                    ref interface,
-                    version,
-                } => {
-                    if let "wl_output" = &interface[..] {
-                        let output =
-                            registry.bind::<wl_output::WlOutput>(std::cmp::min(version, 3), id);
-                        output.quick_assign(move |_, _, _| {});
-                        inner_global
-                            .lock()
-                            .unwrap()
-                            .add_output(id, (*output).clone());
-                    } else if let "wl_seat" = &interface[..] {
-                        inner_global
-                            .lock()
-                            .unwrap()
-                            .seats
-                            .created(registry, id, version, ddata);
+        loop {
+            // Process commands
+            let cmd = self.cmd_queue.lock().unwrap().pop_front();
+            match cmd {
+                Some(cmd) => match cmd {
+                    Cmd::Draw => {
+                        self.redraw(false).expect("Failed to draw");
                     }
-                }
-                GlobalEvent::Removed { id, ref interface } => {
-                    if let "wl_output" = &interface[..] {
-                        inner_global.lock().unwrap().remove_output(id);
-                    } else if let "wl_seat" = &interface[..] {
-                        inner_global.lock().unwrap().seats.removed(id, ddata);
+                    Cmd::ForceDraw => {
+                        self.redraw(true).expect("Failed to draw");
+                    }
+                    Cmd::MouseClick { btn, pos } => {
+                        self.get_widget().mouse_click(btn, pos);
+                        self.cmd_queue.lock().unwrap().push_back(Cmd::Draw);
+                    }
+                    Cmd::MouseScroll { scroll, pos } => {
+                        self.get_widget().mouse_scroll(scroll, pos);
+                        self.cmd_queue.lock().unwrap().push_back(Cmd::Draw);
+                    }
+                    Cmd::Keyboard {
+                        keysym,
+                        modifiers,
+                        interpreted,
+                    } => {
+                        self.get_widget()
+                            .keyboard_input(keysym, modifiers, interpreted);
+                        self.cmd_queue.lock().unwrap().push_back(Cmd::Draw);
+                    }
+                    Cmd::Exit => {
+                        return;
+                    }
+                },
+                None => {
+                    // Dispatch wayland events
+                    let mut eq = self.event_queue.take().unwrap();
+                    eq.blocking_dispatch(self).unwrap();
+                    self.event_queue = Some(eq);
+
+                    if self.exit {
+                        return;
                     }
                 }
-            },
-        );
-
-        // double sync to retrieve the global list
-        // and the globals metadata
-        event_queue
-            .sync_roundtrip(&mut (), |_, _, _| unreachable!())
-            .unwrap();
-        event_queue
-            .sync_roundtrip(&mut (), |_, _, _| unreachable!())
-            .unwrap();
-
-        // wl_compositor
-        let compositor: Main<wl_compositor::WlCompositor> = manager
-            .instantiate_range(1, 4)
-            .expect("server didn't advertise `wl_compositor`");
-
-        inner.lock().unwrap().set_compositor(Some(compositor));
-
-        // wl_shm
-        let shm_formats = Arc::new(Mutex::new(Vec::new()));
-        let shm_formats2 = shm_formats.clone();
-        let shm = manager
-            .instantiate_range::<wl_shm::WlShm>(1, 1)
-            .expect("server didn't advertise `wl_shm`");
-        shm.quick_assign(move |_, evt, _| {
-            if let wl_shm::Event::Format { format } = evt {
-                shm_formats2.lock().unwrap().push(format);
             }
-        });
+        }
+    }
+}
 
-        let pools = DoubleMemPool::new(shm).expect("Failed to create a memory pool !");
+impl CompositorHandler for App {
+    fn scale_factor_changed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _new_factor: i32,
+    ) {
+    }
 
-        //
-        // Keyboard processing
-        //
-        for seat in inner.lock().unwrap().seats.get_all() {
-            let kbd_clone = cmd_queue.clone();
-            let modifiers_state = Arc::new(Mutex::new(ModifiersState {
-                ctrl: false,
-                alt: false,
-                shift: false,
-                caps_lock: false,
-                logo: false,
-                num_lock: false,
-            }));
-            map_keyboard(&seat, None, move |event: KbEvent, _, _| match event {
-                KbEvent::Key {
-                    keysym,
-                    utf8,
-                    state,
-                    ..
-                } => match state {
-                    KeyState::Pressed => match keysym {
-                        keysyms::XKB_KEY_c if modifiers_state.lock().unwrap().ctrl => {
-                            kbd_clone.lock().unwrap().push_back(Cmd::Exit)
-                        }
-                        v => kbd_clone.lock().unwrap().push_back(Cmd::Keyboard {
-                            key: v,
-                            key_state: state,
-                            modifiers_state: modifiers_state.lock().unwrap().clone(),
-                            interpreted: utf8,
-                        }),
-                    },
-                    _ => (),
-                },
-                KbEvent::Modifiers { modifiers } => *modifiers_state.lock().unwrap() = modifiers,
-                _ => (),
-            })
-            .expect("Failed to map keyboard");
+    fn transform_changed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _new_transform: wl_output::Transform,
+    ) {
+    }
+
+    fn frame(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _time: u32,
+    ) {
+    }
+
+    fn surface_enter(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _output: &wl_output::WlOutput,
+    ) {
+    }
+
+    fn surface_leave(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _output: &wl_output::WlOutput,
+    ) {
+    }
+}
+
+impl OutputHandler for App {
+    fn output_state(&mut self) -> &mut OutputState {
+        &mut self.output_state
+    }
+
+    fn new_output(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _output: wl_output::WlOutput,
+    ) {
+    }
+
+    fn update_output(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _output: wl_output::WlOutput,
+    ) {
+    }
+
+    fn output_destroyed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _output: wl_output::WlOutput,
+    ) {
+    }
+}
+
+impl LayerShellHandler for App {
+    fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) {
+        self.exit = true;
+    }
+
+    fn configure(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _layer: &LayerSurface,
+        _configure: LayerSurfaceConfigure,
+        _serial: u32,
+    ) {
+        self.configured += 1;
+        self.cmd_queue.lock().unwrap().push_back(Cmd::ForceDraw);
+    }
+}
+
+impl SeatHandler for App {
+    fn seat_state(&mut self) -> &mut SeatState {
+        &mut self.seat_state
+    }
+
+    fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
+
+    fn new_capability(
+        &mut self,
+        _conn: &Connection,
+        qh: &QueueHandle<Self>,
+        seat: wl_seat::WlSeat,
+        capability: Capability,
+    ) {
+        if capability == Capability::Keyboard && self.keyboard.is_none() {
+            let keyboard = self
+                .seat_state
+                .get_keyboard(qh, &seat, None)
+                .expect("Failed to create keyboard");
+            self.keyboard = Some(keyboard);
         }
 
-        //
-        // Prepare shell so that we can create our shell surface
-        //
-        inner.lock().unwrap().set_shell(Some(
-            if let Ok(layer) = manager.instantiate_exact::<zwlr_layer_shell_v1::ZwlrLayerShellV1>(1)
-            {
-                layer.quick_assign(move |_, _, _| {});
-                layer
-            } else {
-                panic!("server didn't advertise `zwlr_layer_shell_v1`");
-            },
-        ));
-
-        inner.lock().unwrap().outputs_changed();
-        event_queue
-            .sync_roundtrip(&mut (), |_, _, _| () )
-            .unwrap();
-
-        //
-        // Cursor processing
-        //
-        for seat in inner.lock().unwrap().seats.get_all() {
-            let scale = config.scale;
-            let pointer_clone = cmd_queue.clone();
-            let mut pos: (u32, u32) = (0, 0);
-            let mut vert_scroll: f64 = 0.0;
-            let mut horiz_scroll: f64 = 0.0;
-            let mut btn: u32 = 0;
-            let mut btn_clicked = false;
-            let pointer = seat.get_pointer();
-            pointer.quick_assign(move |_, evt, _| match evt {
-                wl_pointer::Event::Enter {
-                    surface_x,
-                    surface_y,
-                    ..
-                } => {
-                    pos = (surface_x as u32, surface_y as u32);
+        if capability == Capability::Pointer && self.pointer.is_none() {
+            let pointer = self
+                .seat_state
+                .get_pointer(qh, &seat)
+                .expect("Failed to create pointer");
+            self.pointer = Some(pointer);
+        }
+    }
+
+    fn remove_capability(
+        &mut self,
+        _conn: &Connection,
+        _: &QueueHandle<Self>,
+        _: wl_seat::WlSeat,
+        capability: Capability,
+    ) {
+        if capability == Capability::Keyboard {
+            if let Some(kbd) = self.keyboard.take() {
+                kbd.release();
+            }
+        }
+        if capability == Capability::Pointer {
+            if let Some(ptr) = self.pointer.take() {
+                ptr.release();
+            }
+        }
+    }
+
+    fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
+}
+
+impl KeyboardHandler for App {
+    fn enter(
+        &mut self,
+        _: &Connection,
+        _: &QueueHandle<Self>,
+        _: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _: u32,
+        _: &[u32],
+        _keysyms: &[Keysym],
+    ) {
+    }
+
+    fn leave(
+        &mut self,
+        _: &Connection,
+        _: &QueueHandle<Self>,
+        _: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _: u32,
+    ) {
+    }
+
+    fn press_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _: &wl_keyboard::WlKeyboard,
+        _: u32,
+        event: KeyEvent,
+    ) {
+        if event.keysym == Keysym::c && self.modifiers.ctrl {
+            self.cmd_queue.lock().unwrap().push_back(Cmd::Exit);
+            return;
+        }
+
+        self.cmd_queue.lock().unwrap().push_back(Cmd::Keyboard {
+            keysym: event.keysym,
+            modifiers: self.modifiers.clone(),
+            interpreted: event.utf8,
+        });
+    }
+
+    fn repeat_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _: &wl_keyboard::WlKeyboard,
+        _: u32,
+        event: KeyEvent,
+    ) {
+        self.cmd_queue.lock().unwrap().push_back(Cmd::Keyboard {
+            keysym: event.keysym,
+            modifiers: self.modifiers.clone(),
+            interpreted: event.utf8,
+        });
+    }
+
+    fn release_key(
+        &mut self,
+        _: &Connection,
+        _: &QueueHandle<Self>,
+        _: &wl_keyboard::WlKeyboard,
+        _: u32,
+        _event: KeyEvent,
+    ) {
+    }
+
+    fn update_modifiers(
+        &mut self,
+        _: &Connection,
+        _: &QueueHandle<Self>,
+        _: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        modifiers: Modifiers,
+        _raw_modifiers: RawModifiers,
+        _layout: u32,
+    ) {
+        self.modifiers = modifiers;
+    }
+}
+
+impl PointerHandler for App {
+    fn pointer_frame(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _pointer: &wl_pointer::WlPointer,
+        events: &[PointerEvent],
+    ) {
+        use PointerEventKind::*;
+        let scale = self.config.scale;
+        for event in events {
+            match event.kind {
+                Enter { .. } => {
+                    self.pointer_pos = (event.position.0 as u32, event.position.1 as u32);
                 }
-                wl_pointer::Event::Leave { .. } => {
-                    pos = (0, 0);
+                Leave { .. } => {
+                    self.pointer_pos = (0, 0);
                 }
-                wl_pointer::Event::Motion {
-                    surface_x,
-                    surface_y,
-                    ..
-                } => {
-                    pos = (surface_x as u32 * scale, surface_y as u32 * scale);
+                Motion { .. } => {
+                    self.pointer_pos =
+                        (event.position.0 as u32 * scale, event.position.1 as u32 * scale);
                 }
-                wl_pointer::Event::Axis { axis, value, .. } => {
-                    if axis == wl_pointer::Axis::VerticalScroll {
-                        vert_scroll += value;
-                    }
+                Press { .. } => {
+                    // Intentionally empty - original only handled Release
                 }
-                wl_pointer::Event::Button { button, state, .. } => match state {
-                    wl_pointer::ButtonState::Released => {
-                        btn = button;
-                        btn_clicked = true;
-                    }
-                    _ => {}
-                },
-                wl_pointer::Event::Frame => {
-                    if vert_scroll != 0.0 || horiz_scroll != 0.0 {
-                        pointer_clone.lock().unwrap().push_back(Cmd::MouseScroll {
-                            scroll: (horiz_scroll, vert_scroll),
-                            pos: pos,
+                Release { button, .. } => {
+                    self.cmd_queue.lock().unwrap().push_back(Cmd::MouseClick {
+                        btn: button,
+                        pos: self.pointer_pos,
+                    });
+                }
+                Axis {
+                    horizontal,
+                    vertical,
+                    ..
+                } => {
+                    let h = horizontal.absolute;
+                    let v = vertical.absolute;
+                    if h != 0.0 || v != 0.0 {
+                        self.cmd_queue.lock().unwrap().push_back(Cmd::MouseScroll {
+                            scroll: (h, v),
+                            pos: self.pointer_pos,
                         });
-                        vert_scroll = 0.0;
-                        horiz_scroll = 0.0;
-                    }
-                    if btn_clicked {
-                        pointer_clone
-                            .lock()
-                            .unwrap()
-                            .push_back(Cmd::MouseClick { btn: btn, pos: pos });
-                        btn_clicked = false;
                     }
                 }
-                _ => {}
-            });
+            }
         }
+    }
+}
 
-        display.flush().unwrap();
+impl ShmHandler for App {
+    fn shm_state(&mut self) -> &mut Shm {
+        &mut self.shm
+    }
+}
 
-        App {
-            config,
-            display: display,
-            event_queue: event_queue,
-            cmd_queue: cmd_queue,
-            pools: pools,
-            widget: None,
-            inner: inner,
-            last_damage: None,
-            last_dim: (0, 0),
-        }
+delegate_compositor!(App);
+delegate_output!(App);
+delegate_shm!(App);
+delegate_seat!(App);
+delegate_keyboard!(App);
+delegate_pointer!(App);
+delegate_layer!(App);
+delegate_registry!(App);
+
+impl ProvidesRegistryState for App {
+    fn registry(&mut self) -> &mut RegistryState {
+        &mut self.registry_state
     }
+    registry_handlers![OutputState, SeatState];
 }
diff --git a/src/cmd.rs b/src/cmd.rs
index 7eee8fe..84da5b1 100644
--- a/src/cmd.rs
+++ b/src/cmd.rs
@@ -1,4 +1,4 @@
-use smithay_client_toolkit::seat::keyboard::{KeyState, ModifiersState};
+use smithay_client_toolkit::seat::keyboard::{Keysym, Modifiers};
 
 pub enum Cmd {
     Exit,
@@ -13,9 +13,8 @@ pub enum Cmd {
         pos: (u32, u32),
     },
     Keyboard {
-        key: u32,
-        key_state: KeyState,
-        modifiers_state: ModifiersState,
+        keysym: Keysym,
+        modifiers: Modifiers,
         interpreted: Option<String>,
     },
 }
diff --git a/src/color.rs b/src/color.rs
index 6273370..2c7a708 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -66,3 +66,109 @@ impl Color {
             | ((255.0 * self.blue) as u32 & 0xFF)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn color_clamps_values() {
+        let c = Color::new(2.0, -1.0, 0.5, 1.5);
+        assert_eq!(c.red, 1.0);
+        assert_eq!(c.green, 0.0);
+        assert_eq!(c.blue, 0.5);
+        assert_eq!(c.opacity, 1.0);
+    }
+
+    #[test]
+    fn color_normal_values() {
+        let c = Color::new(0.5, 0.3, 0.7, 0.9);
+        assert_eq!(c.red, 0.5);
+        assert_eq!(c.green, 0.3);
+        assert_eq!(c.blue, 0.7);
+        assert_eq!(c.opacity, 0.9);
+    }
+
+    #[test]
+    fn blend_ratio_zero_returns_self() {
+        let a = Color::new(1.0, 0.0, 0.0, 1.0);
+        let b = Color::new(0.0, 1.0, 0.0, 1.0);
+        let result = a.blend(&b, 0.0);
+        assert_eq!(result.red, 1.0);
+        assert_eq!(result.green, 0.0);
+        assert_eq!(result.blue, 0.0);
+    }
+
+    #[test]
+    fn blend_ratio_one_returns_other() {
+        let a = Color::new(1.0, 0.0, 0.0, 1.0);
+        let b = Color::new(0.0, 1.0, 0.0, 1.0);
+        let result = a.blend(&b, 1.0);
+        assert_eq!(result.red, 0.0);
+        assert_eq!(result.green, 1.0);
+        assert_eq!(result.blue, 0.0);
+    }
+
+    #[test]
+    fn blend_ratio_half() {
+        let a = Color::new(1.0, 0.0, 0.0, 1.0);
+        let b = Color::new(0.0, 1.0, 0.0, 1.0);
+        let result = a.blend(&b, 0.5);
+        assert_eq!(result.red, 0.5);
+        assert_eq!(result.green, 0.5);
+    }
+
+    #[test]
+    fn blend_clamps_ratio() {
+        let a = Color::new(1.0, 0.0, 0.0, 1.0);
+        let b = Color::new(0.0, 1.0, 0.0, 1.0);
+        let result = a.blend(&b, 2.0);
+        assert_eq!(result.red, 0.0);
+        assert_eq!(result.green, 1.0);
+
+        let result = a.blend(&b, -1.0);
+        assert_eq!(result.red, 1.0);
+        assert_eq!(result.green, 0.0);
+    }
+
+    #[test]
+    fn as_argb8888_white() {
+        let c = Color::new(1.0, 1.0, 1.0, 1.0);
+        assert_eq!(c.as_argb8888(), 0xFFFFFFFF);
+    }
+
+    #[test]
+    fn as_argb8888_black_opaque() {
+        let c = Color::new(0.0, 0.0, 0.0, 1.0);
+        assert_eq!(c.as_argb8888(), 0xFF000000);
+    }
+
+    #[test]
+    fn as_argb8888_red() {
+        let c = Color::new(1.0, 0.0, 0.0, 1.0);
+        assert_eq!(c.as_argb8888(), 0xFFFF0000);
+    }
+
+    #[test]
+    fn as_argb8888_transparent() {
+        let c = Color::new(0.0, 0.0, 0.0, 0.0);
+        assert_eq!(c.as_argb8888(), 0x00000000);
+    }
+
+    #[test]
+    fn default_color() {
+        let c = Color::default();
+        assert_eq!(c.as_argb8888(), 0x00000000);
+    }
+
+    #[test]
+    fn serde_roundtrip() {
+        let c = Color::new(0.5, 0.3, 0.7, 0.9);
+        let json = serde_json::to_string(&c).unwrap();
+        let deserialized: Color = serde_json::from_str(&json).unwrap();
+        assert_eq!(deserialized.red, c.red);
+        assert_eq!(deserialized.green, c.green);
+        assert_eq!(deserialized.blue, c.blue);
+        assert_eq!(deserialized.opacity, c.opacity);
+    }
+}
diff --git a/src/config.rs b/src/config.rs
index 4994933..ee9f886 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -117,3 +117,69 @@ pub fn read_config() -> Config {
 
     config
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn default_config() {
+        let config = Config::default();
+        assert!(matches!(config.output_mode, OutputMode::All));
+        assert_eq!(config.scale, 1);
+        assert!(config.command.is_empty());
+    }
+
+    #[test]
+    fn parse_full_config() {
+        let toml_str = r#"
+            command = "sway"
+            outputMode = "active"
+            scale = 2
+
+            [background]
+            red = 0.1
+            green = 0.2
+            blue = 0.3
+            opacity = 0.9
+
+            [headline]
+            red = 1.0
+            green = 1.0
+            blue = 1.0
+            opacity = 1.0
+        "#;
+        let config: Config = toml::from_str(toml_str).unwrap();
+        assert_eq!(config.command, "sway");
+        assert!(matches!(config.output_mode, OutputMode::Active));
+        assert_eq!(config.scale, 2);
+    }
+
+    #[test]
+    fn parse_minimal_config() {
+        let config: Config = toml::from_str("").unwrap();
+        assert!(matches!(config.output_mode, OutputMode::All));
+        assert_eq!(config.scale, 1);
+        assert!(config.command.is_empty());
+    }
+
+    #[test]
+    fn parse_output_mode_all() {
+        let config: Config = toml::from_str("outputMode = \"all\"").unwrap();
+        assert!(matches!(config.output_mode, OutputMode::All));
+    }
+
+    #[test]
+    fn parse_output_mode_active() {
+        let config: Config = toml::from_str("outputMode = \"active\"").unwrap();
+        assert!(matches!(config.output_mode, OutputMode::Active));
+    }
+
+    #[test]
+    fn color_defaults_applied() {
+        let config: Config = toml::from_str("").unwrap();
+        // Background default is (0, 0, 0, 0.9)
+        let bg = config.background;
+        assert_eq!(bg.as_argb8888() >> 24, 0xE5); // ~0.9 * 255 = 229 = 0xE5
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index a67a0d9..cbca669 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,111 +1,20 @@
-use std::io::{Read, Write};
-use std::os::unix::io::AsRawFd;
-use std::sync::mpsc::channel;
-
-use nix::poll::{poll, PollFd, PollFlags};
-use os_pipe::pipe;
-
 mod app;
 mod buffer;
 mod cmd;
 mod color;
 mod config;
-mod doublemempool;
 mod draw;
 mod widget;
 mod widgets;
 
 use app::App;
-use cmd::Cmd;
 
 fn main() {
     let config = config::read_config();
 
-    let (tx_draw, rx_draw) = channel();
-    let mut app = App::new(tx_draw, config.clone());
+    let mut app = App::new(config.clone());
     app.set_widget(widgets::login::Login::new(config.command))
         .unwrap();
 
-    let (mut rx_pipe, mut tx_pipe) = pipe().unwrap();
-
-    let worker_queue = app.cmd_queue();
-    let _ = std::thread::Builder::new()
-        .name("cmd_proxy".to_string())
-        .spawn(move || loop {
-            let cmd = rx_draw.recv().unwrap();
-            worker_queue.lock().unwrap().push_back(cmd);
-            tx_pipe.write_all(&[0x1]).unwrap();
-        });
-
-    let mut fds = [
-        PollFd::new(app.display().get_connection_fd(), PollFlags::POLLIN),
-        PollFd::new(rx_pipe.as_raw_fd(), PollFlags::POLLIN),
-    ];
-
-    app.cmd_queue().lock().unwrap().push_back(Cmd::Draw);
-
-    let q = app.cmd_queue();
-    loop {
-        let cmd = q.lock().unwrap().pop_front();
-        match cmd {
-            Some(cmd) => match cmd {
-                Cmd::Draw => {
-                    app.redraw(false).expect("Failed to draw");
-                    app.flush_display();
-                }
-                Cmd::ForceDraw => {
-                    app.redraw(true).expect("Failed to draw");
-                    app.flush_display();
-                }
-                Cmd::MouseClick { btn, pos } => {
-                    app.get_widget().mouse_click(btn, pos);
-                    q.lock().unwrap().push_back(Cmd::Draw);
-                }
-                Cmd::MouseScroll { scroll, pos } => {
-                    app.get_widget().mouse_scroll(scroll, pos);
-                    q.lock().unwrap().push_back(Cmd::Draw);
-                }
-                Cmd::Keyboard {
-                    key,
-                    key_state,
-                    modifiers_state,
-                    interpreted,
-                } => {
-                    app.get_widget()
-                        .keyboard_input(key, modifiers_state, key_state, interpreted);
-                    q.lock().unwrap().push_back(Cmd::Draw);
-                }
-                Cmd::Exit => {
-                    return;
-                }
-            },
-            None => {
-                app.flush_display();
-
-                poll(&mut fds, -1).unwrap();
-
-                if fds[0].revents().unwrap().contains(PollFlags::POLLIN) {
-                    if let Some(guard) = app.event_queue().prepare_read() {
-                        if let Err(e) = guard.read_events() {
-                            if e.kind() != ::std::io::ErrorKind::WouldBlock {
-                                eprintln!(
-                                    "Error while trying to read from the wayland socket: {:?}",
-                                    e
-                                );
-                            }
-                        }
-                    }
-
-                    app.event_queue()
-                        .dispatch_pending(&mut (), |_, _, _| {})
-                        .expect("Failed to dispatch all messages.");
-                }
-
-                if fds[1].revents().unwrap().contains(PollFlags::POLLIN) {
-                    let mut v = [0x00];
-                    rx_pipe.read_exact(&mut v).unwrap();
-                }
-            }
-        }
-    }
+    app.run();
 }
diff --git a/src/widget.rs b/src/widget.rs
index 165b34a..43d3471 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -2,7 +2,7 @@ use crate::buffer::Buffer;
 use crate::color::Color;
 use crate::config::Config;
 use chrono::{DateTime, Local};
-pub use smithay_client_toolkit::seat::keyboard::{KeyState, ModifiersState};
+pub use smithay_client_toolkit::seat::keyboard::{Keysym, Modifiers};
 
 pub struct DrawContext<'a> {
     pub buf: &'a mut Buffer<'a>,
@@ -41,9 +41,8 @@ pub trait Widget {
 
     fn keyboard_input(
         &mut self,
-        keysym: u32,
-        modifier_state: ModifiersState,
-        key_state: KeyState,
+        keysym: Keysym,
+        modifiers: Modifiers,
         interpreted: Option<String>,
     );
     fn mouse_click(&mut self, button: u32, pos: (u32, u32));
diff --git a/src/widgets/login.rs b/src/widgets/login.rs
index 4aab575..399df78 100644
--- a/src/widgets/login.rs
+++ b/src/widgets/login.rs
@@ -1,12 +1,10 @@
 use crate::draw::{draw_box, Font, DEJAVUSANS_MONO};
-use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget};
+use crate::widget::{DrawContext, DrawReport, Keysym, Modifiers, Widget};
 
 use std::env;
 use std::error::Error;
 use std::os::unix::net::UnixStream;
 
-use smithay_client_toolkit::seat::keyboard::keysyms;
-
 use greetd_ipc::{codec::SyncCodec, AuthMessageType, ErrorType, Request, Response};
 
 pub trait Scrambler {
@@ -174,19 +172,19 @@ impl Widget for Login {
         }
         self.dirty = false;
         let mut buf = ctx.buf.subdimensions((0, 0, width, height))?;
-        buf.memset(&ctx.bg);
+        buf.memset(ctx.bg);
         draw_box(&mut buf, &ctx.config.border, (width, height))?;
 
         self.headline_font.auto_draw_text(
             &mut buf.offset((32, 24))?,
-            &ctx.bg,
+            ctx.bg,
             &ctx.config.headline,
             "Login",
         )?;
 
         let (w, _) = self.prompt_font.auto_draw_text(
             &mut buf.offset((256, 24))?,
-            &ctx.bg,
+            ctx.bg,
             &ctx.config.prompt,
             &self.question,
         )?;
@@ -195,19 +193,16 @@ impl Widget for Login {
             None | Some(AuthMessageType::Visible) => {
                 self.prompt_font.auto_draw_text(
                     &mut buf.subdimensions((256 + w + 16, 24, width - 416 - 32, 64))?,
-                    &ctx.bg,
+                    ctx.bg,
                     &ctx.config.prompt,
                     &format!("{}", self.answer),
                 )?;
             }
             Some(AuthMessageType::Secret) => {
-                let mut stars = "".to_string();
-                for _ in 0..self.answer.len() {
-                    stars += "*";
-                }
+                let stars: String = "*".repeat(self.answer.len());
                 self.prompt_font.auto_draw_text(
                     &mut buf.subdimensions((256 + w + 16, 24, width - 416 - 32, 64))?,
-                    &ctx.bg,
+                    ctx.bg,
                     &ctx.config.prompt,
                     &stars,
                 )?;
@@ -215,10 +210,10 @@ impl Widget for Login {
             _ => (),
         }
 
-        if self.error.len() > 0 {
+        if !self.error.is_empty() {
             self.prompt_font.auto_draw_text(
                 &mut buf.offset((256, 64))?,
-                &ctx.bg,
+                ctx.bg,
                 &ctx.config.prompt_err,
                 &self.error,
             )?;
@@ -234,13 +229,12 @@ impl Widget for Login {
 
     fn keyboard_input(
         &mut self,
-        key: u32,
-        modifiers: ModifiersState,
-        _: KeyState,
+        keysym: Keysym,
+        modifiers: Modifiers,
         interpreted: Option<String>,
     ) {
-        match key {
-            keysyms::XKB_KEY_u if modifiers.ctrl => {
+        match keysym {
+            Keysym::u if modifiers.ctrl => {
                 if self.mode.is_some() {
                     self.cancel().expect("unable to cancel");
                     self.mode = None;
@@ -250,7 +244,7 @@ impl Widget for Login {
                 self.reset();
                 self.dirty = true;
             }
-            keysyms::XKB_KEY_c if modifiers.ctrl => {
+            Keysym::c if modifiers.ctrl => {
                 if self.mode.is_some() {
                     self.cancel().expect("unable to cancel");
                     self.mode = None;
@@ -260,14 +254,14 @@ impl Widget for Login {
                 self.reset();
                 self.dirty = true;
             }
-            keysyms::XKB_KEY_BackSpace => {
+            Keysym::BackSpace => {
                 self.answer.truncate(self.answer.len().saturating_sub(1));
                 self.dirty = true;
             }
-            keysyms::XKB_KEY_Return => match self.answer.chars().next() {
+            Keysym::Return => match self.answer.chars().next() {
                 Some('!') => {
                     self.error =
-                        format!("Command set to: {}", self.answer[1..].to_string()).to_string();
+                        format!("Command set to: {}", &self.answer[1..]);
                     self.command = self.answer[1..].to_string();
                     self.answer.clear();
                     self.dirty = true;
@@ -288,13 +282,12 @@ impl Widget for Login {
                     }
                 }
             },
-            _ => match interpreted {
-                Some(v) => {
+            _ => {
+                if let Some(v) = interpreted {
                     self.answer += &v;
                     self.dirty = true;
                 }
-                None => {}
-            },
+            }
         }
     }
     fn mouse_click(&mut self, _: u32, _: (u32, u32)) {}
-- 
2.53.0

