git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commit360582618006d26fbe2fa1237bda90e174ffbd31
parent9d4463256b
authorLucas Galante <[email protected]>
date2026-05-25 00:04
Migrate from winit to smithay-client-toolkit and calloop

 Cargo.lock              |  809 +--------------------
 Cargo.toml              |    7 +-
 src/app.rs              |   10 +-
 src/main.rs             | 1793 ++++++++++++++++++++++++++++++-----------------
 src/pages/display.rs    |    4 +-
 src/pages/processors.rs |  264 ++++++-
 src/pages/typeface.rs   |  499 +++++++++++--
 7 files changed, 1885 insertions(+), 1501 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 37ca0db..812100a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,60 +2,6 @@
 # It is not intended for manual editing.
 version = 4
 
-[[package]]
-name = "ab_glyph"
-version = "0.2.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
-dependencies = [
- "ab_glyph_rasterizer",
- "owned_ttf_parser",
-]
-
-[[package]]
-name = "ab_glyph_rasterizer"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
-
-[[package]]
-name = "ahash"
-version = "0.8.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
-dependencies = [
- "cfg-if",
- "getrandom 0.3.4",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
-[[package]]
-name = "android-activity"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd"
-dependencies = [
- "android-properties",
- "bitflags 2.11.1",
- "cc",
- "jni",
- "libc",
- "log",
- "ndk",
- "ndk-context",
- "ndk-sys 0.6.0+11769913",
- "num_enum",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "android-properties"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
-
 [[package]]
 name = "android_system_properties"
 version = "0.1.5"
@@ -71,24 +17,12 @@ version = "1.0.102"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
 
-[[package]]
-name = "arrayref"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
-
 [[package]]
 name = "arrayvec"
 version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
 
-[[package]]
-name = "as-raw-xcb-connection"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
-
 [[package]]
 name = "ash"
 version = "0.38.0+1.3.281"
@@ -277,15 +211,6 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
 
-[[package]]
-name = "block2"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
-dependencies = [
- "objc2",
-]
-
 [[package]]
 name = "blocking"
 version = "1.6.2"
@@ -364,8 +289,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
 dependencies = [
  "find-msvc-tools",
- "jobserver",
- "libc",
  "shlex",
 ]
 
@@ -386,14 +309,19 @@ name = "clear-system-interface"
 version = "0.1.0"
 dependencies = [
  "bytemuck",
+ "calloop",
+ "calloop-wayland-source",
  "clear-ui",
  "futures",
  "glyphon",
  "pollster",
+ "raw-window-handle",
  "serde",
+ "smithay-client-toolkit",
  "tokio",
+ "wayland-client",
  "wgpu",
- "winit",
+ "xkeysym",
  "zbus",
 ]
 
@@ -402,11 +330,16 @@ name = "clear-ui"
 version = "0.1.0"
 dependencies = [
  "bytemuck",
+ "calloop",
+ "calloop-wayland-source",
  "glyphon",
  "pollster",
+ "raw-window-handle",
+ "smithay-client-toolkit",
  "tokio",
+ "wayland-client",
  "wgpu",
- "winit",
+ "xkeysym",
 ]
 
 [[package]]
@@ -419,16 +352,6 @@ dependencies = [
  "unicode-width",
 ]
 
-[[package]]
-name = "combine"
-version = "4.6.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
-dependencies = [
- "bytes",
- "memchr",
-]
-
 [[package]]
 name = "concurrent-queue"
 version = "2.5.0"
@@ -454,19 +377,6 @@ version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
-[[package]]
-name = "core-graphics"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
-dependencies = [
- "bitflags 1.3.2",
- "core-foundation",
- "core-graphics-types",
- "foreign-types",
- "libc",
-]
-
 [[package]]
 name = "core-graphics-types"
 version = "0.1.3"
@@ -532,12 +442,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
 
-[[package]]
-name = "dispatch"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
-
 [[package]]
 name = "dlib"
 version = "0.5.3"
@@ -562,12 +466,6 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
 
-[[package]]
-name = "dpi"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
-
 [[package]]
 name = "either"
 version = "1.15.0"
@@ -701,7 +599,7 @@ checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3"
 dependencies = [
  "fontconfig-parser",
  "log",
- "memmap2",
+ "memmap2 0.9.10",
  "slotmap",
  "tinyvec",
  "ttf-parser 0.20.0",
@@ -835,28 +733,6 @@ dependencies = [
  "slab",
 ]
 
-[[package]]
-name = "gethostname"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
-dependencies = [
- "rustix 1.1.4",
- "windows-link",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi 5.3.0",
- "wasip2",
-]
-
 [[package]]
 name = "getrandom"
 version = "0.4.2"
@@ -865,7 +741,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
 dependencies = [
  "cfg-if",
  "libc",
- "r-efi 6.0.0",
+ "r-efi",
  "wasip2",
  "wasip3",
 ]
@@ -1029,36 +905,6 @@ version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
 
-[[package]]
-name = "jni"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
-dependencies = [
- "cfg-if",
- "combine",
- "jni-macros",
- "jni-sys 0.4.1",
- "log",
- "simd_cesu8",
- "thiserror 2.0.18",
- "walkdir",
- "windows-link",
-]
-
-[[package]]
-name = "jni-macros"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
-dependencies = [
- "proc-macro2",
- "quote",
- "rustc_version",
- "simd_cesu8",
- "syn",
-]
-
 [[package]]
 name = "jni-sys"
 version = "0.3.1"
@@ -1087,16 +933,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "jobserver"
-version = "0.1.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
-dependencies = [
- "getrandom 0.3.4",
- "libc",
-]
-
 [[package]]
 name = "js-sys"
 version = "0.3.98"
@@ -1154,18 +990,6 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
 
-[[package]]
-name = "libredox"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
-dependencies = [
- "bitflags 2.11.1",
- "libc",
- "plain",
- "redox_syscall 0.7.5",
-]
-
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.15"
@@ -1220,6 +1044,15 @@ version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
 
+[[package]]
+name = "memmap2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "memmap2"
 version = "0.9.10"
@@ -1286,27 +1119,6 @@ dependencies = [
  "unicode-xid",
 ]
 
-[[package]]
-name = "ndk"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
-dependencies = [
- "bitflags 2.11.1",
- "jni-sys 0.3.1",
- "log",
- "ndk-sys 0.6.0+11769913",
- "num_enum",
- "raw-window-handle",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "ndk-context"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
-
 [[package]]
 name = "ndk-sys"
 version = "0.5.0+25.2.9519653"
@@ -1316,15 +1128,6 @@ dependencies = [
  "jni-sys 0.3.1",
 ]
 
-[[package]]
-name = "ndk-sys"
-version = "0.6.0+11769913"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
-dependencies = [
- "jni-sys 0.3.1",
-]
-
 [[package]]
 name = "num-traits"
 version = "0.2.19"
@@ -1334,28 +1137,6 @@ dependencies = [
  "autocfg",
 ]
 
-[[package]]
-name = "num_enum"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
-dependencies = [
- "num_enum_derive",
- "rustversion",
-]
-
-[[package]]
-name = "num_enum_derive"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "objc"
 version = "0.2.7"
@@ -1365,225 +1146,12 @@ dependencies = [
  "malloc_buf",
 ]
 
-[[package]]
-name = "objc-sys"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
-
-[[package]]
-name = "objc2"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
-dependencies = [
- "objc-sys",
- "objc2-encode",
-]
-
-[[package]]
-name = "objc2-app-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "libc",
- "objc2",
- "objc2-core-data",
- "objc2-core-image",
- "objc2-foundation",
- "objc2-quartz-core",
-]
-
-[[package]]
-name = "objc2-cloud-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-core-location",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-contacts"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-core-data"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-core-image"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
-]
-
-[[package]]
-name = "objc2-core-location"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
-dependencies = [
- "block2",
- "objc2",
- "objc2-contacts",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-encode"
-version = "4.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
-
-[[package]]
-name = "objc2-foundation"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "dispatch",
- "libc",
- "objc2",
-]
-
-[[package]]
-name = "objc2-link-presentation"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
-dependencies = [
- "block2",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-metal"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-quartz-core"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
-]
-
-[[package]]
-name = "objc2-symbols"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
-dependencies = [
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-ui-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-cloud-kit",
- "objc2-core-data",
- "objc2-core-image",
- "objc2-core-location",
- "objc2-foundation",
- "objc2-link-presentation",
- "objc2-quartz-core",
- "objc2-symbols",
- "objc2-uniform-type-identifiers",
- "objc2-user-notifications",
-]
-
-[[package]]
-name = "objc2-uniform-type-identifiers"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-user-notifications"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-core-location",
- "objc2-foundation",
-]
-
 [[package]]
 name = "once_cell"
 version = "1.21.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
 
-[[package]]
-name = "orbclient"
-version = "0.3.54"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a570f6bca41d29acb2139229a7c873ec99bc9a313bd10804081d89bfac8ff329"
-dependencies = [
- "libc",
- "libredox",
-]
-
 [[package]]
 name = "ordered-float"
 version = "4.6.0"
@@ -1603,15 +1171,6 @@ dependencies = [
  "pin-project-lite",
 ]
 
-[[package]]
-name = "owned_ttf_parser"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
-dependencies = [
- "ttf-parser 0.25.1",
-]
-
 [[package]]
 name = "parking"
 version = "2.2.1"
@@ -1636,7 +1195,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall 0.5.18",
+ "redox_syscall",
  "smallvec",
  "windows-link",
 ]
@@ -1647,32 +1206,6 @@ version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
-[[package]]
-name = "pin-project"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "pin-project-lite"
 version = "0.2.17"
@@ -1696,12 +1229,6 @@ version = "0.3.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
 
-[[package]]
-name = "plain"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
-
 [[package]]
 name = "polling"
 version = "3.11.0"
@@ -1780,12 +1307,6 @@ dependencies = [
  "proc-macro2",
 ]
 
-[[package]]
-name = "r-efi"
-version = "5.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-
 [[package]]
 name = "r-efi"
 version = "6.0.0"
@@ -1840,15 +1361,6 @@ dependencies = [
  "font-types",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
-dependencies = [
- "bitflags 1.3.2",
-]
-
 [[package]]
 name = "redox_syscall"
 version = "0.5.18"
@@ -1858,15 +1370,6 @@ dependencies = [
  "bitflags 2.11.1",
 ]
 
-[[package]]
-name = "redox_syscall"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b"
-dependencies = [
- "bitflags 2.11.1",
-]
-
 [[package]]
 name = "renderdoc-sys"
 version = "1.1.0"
@@ -1891,15 +1394,6 @@ version = "2.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
 
-[[package]]
-name = "rustc_version"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
-dependencies = [
- "semver",
-]
-
 [[package]]
 name = "rustix"
 version = "0.38.44"
@@ -1949,15 +1443,6 @@ dependencies = [
  "unicode-script",
 ]
 
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
 [[package]]
 name = "scoped-tls"
 version = "1.0.1"
@@ -1970,19 +1455,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
-[[package]]
-name = "sctk-adwaita"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
-dependencies = [
- "ab_glyph",
- "log",
- "memmap2",
- "smithay-client-toolkit",
- "tiny-skia",
-]
-
 [[package]]
 name = "self_cell"
 version = "1.2.2"
@@ -2065,22 +1537,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "simd_cesu8"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
-dependencies = [
- "rustc_version",
- "simdutf8",
-]
-
-[[package]]
-name = "simdutf8"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
-
 [[package]]
 name = "skrifa"
 version = "0.22.3"
@@ -2119,12 +1575,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
 dependencies = [
  "bitflags 2.11.1",
+ "bytemuck",
  "calloop",
  "calloop-wayland-source",
  "cursor-icon",
  "libc",
  "log",
- "memmap2",
+ "memmap2 0.9.10",
+ "pkg-config",
  "rustix 0.38.44",
  "thiserror 1.0.69",
  "wayland-backend",
@@ -2134,18 +1592,10 @@ dependencies = [
  "wayland-protocols",
  "wayland-protocols-wlr",
  "wayland-scanner",
+ "xkbcommon",
  "xkeysym",
 ]
 
-[[package]]
-name = "smol_str"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
-dependencies = [
- "serde",
-]
-
 [[package]]
 name = "socket2"
 version = "0.6.3"
@@ -2171,12 +1621,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
-[[package]]
-name = "strict-num"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
-
 [[package]]
 name = "strum"
 version = "0.26.3"
@@ -2243,7 +1687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
 dependencies = [
  "fastrand",
- "getrandom 0.4.2",
+ "getrandom",
  "once_cell",
  "rustix 1.1.4",
  "windows-sys 0.61.2",
@@ -2298,31 +1742,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "tiny-skia"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
-dependencies = [
- "arrayref",
- "arrayvec",
- "bytemuck",
- "cfg-if",
- "log",
- "tiny-skia-path",
-]
-
-[[package]]
-name = "tiny-skia-path"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
-dependencies = [
- "arrayref",
- "bytemuck",
- "strict-num",
-]
-
 [[package]]
 name = "tinyvec"
 version = "1.11.0"
@@ -2439,12 +1858,6 @@ version = "0.21.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
 
-[[package]]
-name = "ttf-parser"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
-
 [[package]]
 name = "uds_windows"
 version = "1.2.1"
@@ -2533,16 +1946,6 @@ version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
-[[package]]
-name = "walkdir"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
-dependencies = [
- "same-file",
- "winapi-util",
-]
-
 [[package]]
 name = "wasi"
 version = "0.11.1+wasi-snapshot-preview1"
@@ -2716,19 +2119,6 @@ dependencies = [
  "wayland-scanner",
 ]
 
-[[package]]
-name = "wayland-protocols-plasma"
-version = "0.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
-dependencies = [
- "bitflags 2.11.1",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-scanner",
-]
-
 [[package]]
 name = "wayland-protocols-wlr"
 version = "0.3.12"
@@ -2761,7 +2151,6 @@ checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
 dependencies = [
  "dlib",
  "log",
- "once_cell",
  "pkg-config",
 ]
 
@@ -2775,16 +2164,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "web-time"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
 [[package]]
 name = "wgpu"
 version = "24.0.5"
@@ -2863,7 +2242,7 @@ dependencies = [
  "log",
  "metal",
  "naga",
- "ndk-sys 0.5.0+25.2.9519653",
+ "ndk-sys",
  "objc",
  "once_cell",
  "ordered-float",
@@ -2973,15 +2352,6 @@ dependencies = [
  "windows-targets",
 ]
 
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets",
-]
-
 [[package]]
 name = "windows-sys"
 version = "0.59.0"
@@ -3064,58 +2434,6 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
-[[package]]
-name = "winit"
-version = "0.30.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
-dependencies = [
- "ahash",
- "android-activity",
- "atomic-waker",
- "bitflags 2.11.1",
- "block2",
- "bytemuck",
- "calloop",
- "cfg_aliases",
- "concurrent-queue",
- "core-foundation",
- "core-graphics",
- "cursor-icon",
- "dpi",
- "js-sys",
- "libc",
- "memmap2",
- "ndk",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
- "objc2-ui-kit",
- "orbclient",
- "percent-encoding",
- "pin-project",
- "raw-window-handle",
- "redox_syscall 0.4.1",
- "rustix 0.38.44",
- "sctk-adwaita",
- "smithay-client-toolkit",
- "smol_str",
- "tracing",
- "unicode-segmentation",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-protocols-plasma",
- "web-sys",
- "web-time",
- "windows-sys 0.52.0",
- "x11-dl",
- "x11rb",
- "xkbcommon-dl",
-]
-
 [[package]]
 name = "winnow"
 version = "1.0.3"
@@ -3219,38 +2537,6 @@ dependencies = [
  "wasmparser",
 ]
 
-[[package]]
-name = "x11-dl"
-version = "2.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
-dependencies = [
- "libc",
- "once_cell",
- "pkg-config",
-]
-
-[[package]]
-name = "x11rb"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
-dependencies = [
- "as-raw-xcb-connection",
- "gethostname",
- "libc",
- "libloading",
- "once_cell",
- "rustix 1.1.4",
- "x11rb-protocol",
-]
-
-[[package]]
-name = "x11rb-protocol"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
-
 [[package]]
 name = "xcursor"
 version = "0.3.10"
@@ -3258,15 +2544,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
 
 [[package]]
-name = "xkbcommon-dl"
-version = "0.4.2"
+name = "xkbcommon"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
+checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
 dependencies = [
- "bitflags 2.11.1",
- "dlib",
- "log",
- "once_cell",
+ "libc",
+ "memmap2 0.8.0",
  "xkeysym",
 ]
 
@@ -3275,6 +2559,9 @@ name = "xkeysym"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+dependencies = [
+ "bytemuck",
+]
 
 [[package]]
 name = "xml-rs"
@@ -3355,26 +2642,6 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
 
-[[package]]
-name = "zerocopy"
-version = "0.8.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "zmij"
 version = "1.0.21"
diff --git a/Cargo.toml b/Cargo.toml
index f996744..deae035 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,12 @@ edition = "2021"
 
 [dependencies]
 clear-ui = { path = "../clear-ui" }
-winit = "0.30"
+smithay-client-toolkit = "0.19.2"
+calloop = "0.13.0"
+calloop-wayland-source = "0.3.0"
+wayland-client = { version = "0.31", features = ["system"] }
+xkeysym = "0.2"
+raw-window-handle = "0.6"
 wgpu = "24"
 bytemuck = { version = "1", features = ["derive"] }
 pollster = "0.4"
diff --git a/src/app.rs b/src/app.rs
index 5e86034..5a52a7d 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -72,7 +72,7 @@ pub enum AppAction {
 
 pub struct PageContent {
     pub rects: Vec<([f32; 4], f32, f32, f32, f32)>,
-    pub texts: Vec<(String, f32, f32, f32, [f32; 4])>,
+    pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>)>,
     pub buttons: Vec<ContentButton>,
 }
 
@@ -97,7 +97,11 @@ impl PageContent {
     }
 
     pub fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
-        self.texts.push((content.to_string(), size, x, y, color));
+        self.texts.push((content.to_string(), size, x, y, color, None));
+    }
+
+    pub fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str) {
+        self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string())));
     }
 
     pub fn button(&mut self, label: &str, x: f32, y: f32, w: f32, h: f32,
@@ -117,6 +121,6 @@ impl RenderTarget for PageContent {
     }
 
     fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
-        self.texts.push((content.to_string(), size, x, y, color));
+        self.texts.push((content.to_string(), size, x, y, color, None));
     }
 }
diff --git a/src/main.rs b/src/main.rs
index 5960ebf..29b3a10 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,5 @@
 use std::sync::Arc;
 
-use winit::application::ApplicationHandler;
-use winit::event::{ElementState, MouseButton, WindowEvent};
-use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
-use winit::window::{Window, WindowAttributes};
-#[cfg(target_os = "linux")]
-use winit::platform::wayland::WindowAttributesExtWayland;
-
 use clear_ui::color;
 use clear_ui::widget::{Spinbox, Widget};
 use glyphon::{
@@ -17,6 +10,34 @@ use glyphon::{
 use clear_system_interface::app::{AppAction, AppState, ContentButton, PageContent};
 use clear_system_interface::pages::{self, Page};
 
+use smithay_client_toolkit::{
+    compositor::{CompositorHandler, CompositorState},
+    delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+    delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+    registry::{ProvidesRegistryState, RegistryState},
+    output::{OutputHandler, OutputState},
+    seat::{
+        keyboard::KeyboardHandler,
+        pointer::PointerHandler,
+        Capability, SeatHandler, SeatState,
+    },
+    shell::{
+        xdg::{
+            window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+            XdgShell,
+        },
+        WaylandSurface,
+    },
+    shm::{Shm, ShmHandler},
+};
+use wayland_client::{
+    globals::registry_queue_init,
+    protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+    Connection, QueueHandle, Proxy,
+};
+use calloop::EventLoop;
+use calloop_wayland_source::WaylandSource;
+
 #[repr(C)]
 #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
 struct Vertex {
@@ -62,6 +83,18 @@ fn make_text_buffer(fs: &mut FontSystem, text: &str, size: f32) -> Buffer {
     buf
 }
 
+fn make_text_buffer_with_font(fs: &mut FontSystem, text: &str, size: f32, font: Option<&str>) -> Buffer {
+    let metrics = Metrics::new(size, size * 1.4);
+    let mut buf = Buffer::new(fs, metrics);
+    let mut attrs = Attrs::new();
+    if let Some(font_name) = font {
+        attrs = attrs.family(glyphon::Family::Name(font_name));
+    }
+    buf.set_text(fs, text, attrs, glyphon::Shaping::Advanced);
+    buf.shape_until_scroll(fs, true);
+    buf
+}
+
 struct AppWidget {
     x: f32, y: f32, w: f32, h: f32,
     color: [f32; 4],
@@ -89,8 +122,9 @@ enum ColorSelectorAction {
 }
 
 struct SystemInterface {
-    window: Arc<Window>,
-    surface: wgpu::Surface<'static>,
+    window: XdgWindow,
+    surface: wl_surface::WlSurface,
+    wgpu_surface: wgpu::Surface<'static>,
     device: wgpu::Device,
     queue: wgpu::Queue,
     config: wgpu::SurfaceConfiguration,
@@ -144,19 +178,34 @@ struct SystemInterface {
 }
 
 impl SystemInterface {
-    async fn new(window: Arc<Window>) -> Self {
-        let size = window.inner_size();
-        let sw = size.width as f32;
-        let sh = size.height as f32;
+    async fn new(
+        conn: &Connection,
+        qh: &QueueHandle<App>,
+        compositor_state: &CompositorState,
+        xdg_shell_state: &XdgShell,
+        width: u32,
+        height: u32,
+    ) -> Self {
+        let surface = compositor_state.create_surface(qh);
+        let window = xdg_shell_state.create_window(surface.clone(), WindowDecorations::None, qh);
+        window.set_title("Clear System Interface");
+        window.set_app_id("clear-system-interface");
+        window.set_min_size(Some((820, 680)));
+        window.commit();
+
+        let wayland_handle = Box::leak(Box::new(clear_ui::wayland::WaylandSurfaceHandle {
+            display_ptr: conn.backend().display_id().as_ptr() as *mut std::ffi::c_void,
+            surface_ptr: surface.id().as_ptr() as *mut std::ffi::c_void,
+        }));
 
         let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
             backends: wgpu::Backends::VULKAN,
             ..Default::default()
         });
-        let surface = instance.create_surface(window.clone()).expect("surface");
+        let wgpu_surface = instance.create_surface(wayland_handle).expect("surface");
         let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
             power_preference: wgpu::PowerPreference::HighPerformance,
-            compatible_surface: Some(&surface),
+            compatible_surface: Some(&wgpu_surface),
             force_fallback_adapter: false,
         }).await.expect("adapter");
         let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
@@ -165,8 +214,8 @@ impl SystemInterface {
             required_limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
             memory_hints: wgpu::MemoryHints::MemoryUsage,
         }, None).await.expect("device");
-        let config = surface.get_default_config(&adapter, size.width.max(1), size.height.max(1)).expect("config");
-        surface.configure(&device, &config);
+        let config = wgpu_surface.get_default_config(&adapter, width.max(1), height.max(1)).expect("config");
+        wgpu_surface.configure(&device, &config);
 
         let shader_code = clear_ui::SHADER;
         let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
@@ -218,7 +267,7 @@ impl SystemInterface {
         let mut text_atlas = TextAtlas::new(&device, &queue, &cache, config.format);
         let text_renderer = TextRenderer::new(&mut text_atlas, &device, wgpu::MultisampleState::default(), None);
         let mut text_viewport = Viewport::new(&device, &cache);
-        text_viewport.update(&queue, Resolution { width: size.width, height: size.height });
+        text_viewport.update(&queue, Resolution { width, height });
 
         let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
             label: Some("Vertex Buffer"),
@@ -296,9 +345,10 @@ impl SystemInterface {
         let (tx_backup, rx_backup) = std::sync::mpsc::channel();
 
         let (tx_color_selector, rx_color_selector) = std::sync::mpsc::channel();
-        let scale_factor = (window.scale_factor() as f32).max(2.0) as f64;
+        let scale_factor = 2.0;
+
         let mut this = Self {
-            window, surface, device, queue, config, render_pipeline,
+            window, surface, wgpu_surface, device, queue, config, render_pipeline,
             vertex_buffer, vertex_count: 0,
             app,
             font_system, swash_cache, text_atlas, text_renderer, text_viewport,
@@ -310,12 +360,12 @@ impl SystemInterface {
             rx_processors, rx_system, rx_status, rx_storage, rx_notifications,
             rx_backup_state, rx_typeface, tx_backup, rx_backup,
             tx_color_selector, rx_color_selector,
-            width: size.width, height: size.height,
+            width, height,
             needs_rebuild: true,
             scroll_y: 0.0,
             max_scroll_y: 0.0,
         };
-        this.rebuild_layout(sw, sh);
+        this.rebuild_layout(width as f32, height as f32);
         this
     }
 
@@ -329,8 +379,6 @@ impl SystemInterface {
         let hdr_h = self.header_height * s;
         let st_h = self.status_height * s;
 
-
-
         // Sidebar bg
         widgets.push(AppWidget {
             x: 0.0, y: hdr_h, w: sb_w, h: sh - hdr_h - st_h,
@@ -365,7 +413,7 @@ impl SystemInterface {
             });
         }
 
-        // Content area (logical coords, scaled to physical later)
+        // Content area
         let lcx = self.sidebar_width;
         let lcy = self.header_height;
         let lcw = sw / s - self.sidebar_width;
@@ -387,7 +435,7 @@ impl SystemInterface {
         for (_, _, y, _, h) in &pc.rects {
             max_y = max_y.max(y + h);
         }
-        for (_, size, _, y, _) in &pc.texts {
+        for (_, size, _, y, _, _) in &pc.texts {
             max_y = max_y.max(y + size);
         }
         for btn in &pc.buttons {
@@ -405,9 +453,9 @@ impl SystemInterface {
                 hovering: false, kind: WidgetKind::Static,
             });
         }
-        for (t, size, x, y, tc) in &pc.texts {
+        for (t, size, x, y, tc, font_opt) in &pc.texts {
             text_items.push(TextItem {
-                buffer: make_text_buffer(&mut self.font_system, t, *size * s),
+                buffer: make_text_buffer_with_font(&mut self.font_system, t, *size * s, font_opt.as_deref()),
                 x: *x * s, y: (*y - scroll_offset_y) * s,
                 color: glyphon::Color::rgb(
                     (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
@@ -438,8 +486,6 @@ impl SystemInterface {
             page_buttons.push(cb);
         }
 
-
-
         self.widgets = widgets;
         self.text_items = text_items;
         self.page_buttons = page_buttons;
@@ -454,7 +500,7 @@ impl SystemInterface {
             Page::Display => display::view(&mut self.app.display, cx, cy, cw, ch),
             Page::Radios => network::view(&self.app.network, cx, cy, cw, ch),
             Page::Layout => layout::view(&mut self.app.layout, cx, cy, cw, ch),
-            Page::Processors => processors::view(&self.app.processors, cx, cy, cw, ch),
+            Page::Processors => processors::view(&mut self.app.processors, cx, cy, cw, ch),
             Page::Input => input::view(&mut self.app.input, cx, cy, cw, ch),
             Page::System => system_info::view(&self.app.system_info, cx, cy, cw, ch),
             Page::Status => status::view(&mut self.app.status, cx, cy, cw, ch),
@@ -513,11 +559,11 @@ impl SystemInterface {
         text_renderer.prepare(device, queue, font_system, text_atlas, text_viewport, areas, swash_cache).unwrap();
     }
 
-    fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
-        if size.width > 0 && size.height > 0 {
-            self.width = size.width; self.height = size.height;
-            self.config.width = size.width; self.config.height = size.height;
-            self.surface.configure(&self.device, &self.config);
+    fn resize(&mut self, width: u32, height: u32) {
+        if width > 0 && height > 0 {
+            self.width = width; self.height = height;
+            self.config.width = width; self.config.height = height;
+            self.wgpu_surface.configure(&self.device, &self.config);
             self.needs_rebuild = true;
         }
     }
@@ -596,45 +642,11 @@ impl SystemInterface {
     fn handle_action(&mut self, action: &AppAction) {
         use pages::*;
         match action {
-            AppAction::Layout(m) => match m {
-                layout::LayoutMessage::PickBackgroundColor => {
-                    let color = self.app.layout.background_color;
-                    let tx = self.tx_color_selector.clone();
-                    tokio::spawn(async move {
-                        let hex = format!("#{:02X}{:02X}{:02X}", color[0], color[1], color[2]);
-                        if let Ok(output) = tokio::process::Command::new("clear-colors").arg(&hex).output().await {
-                            let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
-                            if s.starts_with('#') && s.len() >= 7 {
-                                let r = u8::from_str_radix(&s[1..3], 16).unwrap_or(color[0]);
-                                let g = u8::from_str_radix(&s[3..5], 16).unwrap_or(color[1]);
-                                let b = u8::from_str_radix(&s[5..7], 16).unwrap_or(color[2]);
-                                let _ = tx.send(ColorSelectorAction::Background([r, g, b]));
-                            }
-                        }
-                    });
-                }
-                layout::LayoutMessage::PickBorderColor => {
-                    let color = self.app.layout.border_color;
-                    let tx = self.tx_color_selector.clone();
-                    tokio::spawn(async move {
-                        let hex = format!("#{:02X}{:02X}{:02X}", color[0], color[1], color[2]);
-                        if let Ok(output) = tokio::process::Command::new("clear-colors").arg(&hex).output().await {
-                            let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
-                            if s.starts_with('#') && s.len() >= 7 {
-                                let r = u8::from_str_radix(&s[1..3], 16).unwrap_or(color[0]);
-                                let g = u8::from_str_radix(&s[3..5], 16).unwrap_or(color[1]);
-                                let b = u8::from_str_radix(&s[5..7], 16).unwrap_or(color[2]);
-                                let _ = tx.send(ColorSelectorAction::Border([r, g, b]));
-                            }
-                        }
-                    });
-                }
-                _ => layout::update(&mut self.app.layout, m.clone()),
-            },
             AppAction::Power(m) => power::update(&mut self.app.power, m.clone()),
             AppAction::Audio(m) => audio::update(&mut self.app.audio, m.clone()),
             AppAction::Display(m) => display::update(&mut self.app.display, m.clone()),
             AppAction::Radios(m) => network::update(&mut self.app.network, m.clone()),
+            AppAction::Layout(m) => layout::update(&mut self.app.layout, m.clone()),
             AppAction::Input(m) => input::update(&mut self.app.input, m.clone()),
             AppAction::SystemInfo(m) => system_info::update(&mut self.app.system_info, m.clone()),
             AppAction::Processors(m) => processors::update(&mut self.app.processors, m.clone()),
@@ -656,595 +668,726 @@ impl SystemInterface {
         }
     }
 
-    fn handle_event(&mut self, event: &WindowEvent) -> bool {
-        match event {
-            WindowEvent::MouseWheel { delta, .. } => {
-                let s = self.scale_factor as f32;
-                if self.cursor_x >= self.sidebar_width * s {
-                    let scroll_speed = 24.0;
-                    let dy = match delta {
-                        winit::event::MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
-                        winit::event::MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
-                    };
-                    let old_scroll = self.scroll_y;
-                    self.scroll_y = (self.scroll_y + dy).max(0.0).min(self.max_scroll_y);
-                    if (self.scroll_y - old_scroll).abs() > 0.01 {
-                        self.needs_rebuild = true;
-                        return true;
-                    }
+    fn handle_cursor_moved(&mut self, x: f32, y: f32) -> bool {
+        self.cursor_x = x;
+        self.cursor_y = y;
+        let s = self.scale_factor as f32;
+        let lx = self.cursor_x / s;
+        let ly = self.cursor_y / s + self.scroll_y;
+        let mut changed = false;
+        for w in &mut self.widgets {
+            let was = w.hovering;
+            w.hovering = self.cursor_x >= w.x && self.cursor_x <= w.x + w.w
+                && self.cursor_y >= w.y && self.cursor_y <= w.y + w.h;
+            if w.hovering != was {
+                w.color = if w.hovering { w.hover_color } else { w.color };
+                changed = true;
+            }
+        }
+        if self.app.current_page == Page::Layout {
+            for sb in &mut self.app.layout.spinboxes {
+                if sb.cursor_moved(lx, ly) {
+                    changed = true;
                 }
-                false
-            }
-            WindowEvent::CursorMoved { position, .. } => {
-                self.cursor_x = position.x as f32;
-                self.cursor_y = position.y as f32;
-                let s = self.scale_factor as f32;
-                let lx = self.cursor_x / s;
-                let ly = self.cursor_y / s + self.scroll_y;
-                let mut changed = false;
-                for w in &mut self.widgets {
-                    let was = w.hovering;
-                    w.hovering = self.cursor_x >= w.x && self.cursor_x <= w.x + w.w
-                        && self.cursor_y >= w.y && self.cursor_y <= w.y + w.h;
-                    if w.hovering != was {
-                        w.color = if w.hovering { w.hover_color } else { w.color };
-                        changed = true;
-                    }
+            }
+            if self.app.layout.cascade_offset_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.layout.edge_gap_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            for cp in &mut self.app.layout.color_selectors {
+                if cp.cursor_moved(lx, ly) {
+                    changed = true;
                 }
-                if self.app.current_page == Page::Layout {
-                    for sb in &mut self.app.layout.spinboxes {
-                        if sb.cursor_moved(lx, ly) {
-                            changed = true;
-                        }
-                    }
-                    if self.app.layout.cascade_offset_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.layout.edge_gap_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    for cp in &mut self.app.layout.color_selectors {
-                        if cp.cursor_moved(lx, ly) {
-                            changed = true;
-                        }
-                    }
+            }
+        }
+        if self.app.current_page == Page::Input {
+            if self.app.input.rate_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.delay_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.tap_toggle.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.scroll_toggle.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.scroll_friction_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.pointer_toggle.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.pointer_friction_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.trackpad_toggle.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.trackpad_friction_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+        }
+        if self.app.current_page == Page::Audio {
+            for sb in &mut self.app.audio.sink_spinboxes {
+                if sb.cursor_moved(lx, ly) {
+                    changed = true;
                 }
-                if self.app.current_page == Page::Input {
-                    if self.app.input.rate_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.delay_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.tap_toggle.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.scroll_toggle.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.scroll_friction_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.pointer_toggle.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.pointer_friction_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.trackpad_toggle.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.input.trackpad_friction_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
+            }
+            for sb in &mut self.app.audio.source_spinboxes {
+                if sb.cursor_moved(lx, ly) {
+                    changed = true;
                 }
-                if self.app.current_page == Page::Audio {
-                    for sb in &mut self.app.audio.sink_spinboxes {
-                        if sb.cursor_moved(lx, ly) {
-                            changed = true;
-                        }
-                    }
-                    for sb in &mut self.app.audio.source_spinboxes {
-                        if sb.cursor_moved(lx, ly) {
-                            changed = true;
-                        }
-                    }
+            }
+        }
+        if self.app.current_page == Page::Display {
+            if self.app.display.brightness_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.display.night_light_label.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            for out in &mut self.app.display.outputs {
+                if out.name_label.cursor_moved(lx, ly) {
+                    changed = true;
                 }
-                if self.app.current_page == Page::Display {
-                    if self.app.display.brightness_spinbox.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.display.night_light_label.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    for out in &mut self.app.display.outputs {
-                        if out.name_label.cursor_moved(lx, ly) {
-                            changed = true;
-                        }
-                        if out.resolution_label.cursor_moved(lx, ly) {
-                            changed = true;
-                        }
-                        if let Some(ref mut scale_lbl) = out.scale_label {
-                            if scale_lbl.cursor_moved(lx, ly) {
-                                changed = true;
-                            }
-                        }
-                    }
+                if out.resolution_label.cursor_moved(lx, ly) {
+                    changed = true;
                 }
-                if self.app.current_page == Page::Notifications {
-                    if self.app.notifications.enable_toggle.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.notifications.bell_toggle.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.notifications.duration_spinbox.cursor_moved(lx, ly) {
+                if let Some(ref mut scale_lbl) = out.scale_label {
+                    if scale_lbl.cursor_moved(lx, ly) {
                         changed = true;
                     }
                 }
-                if self.app.current_page == Page::Status {
-                    if self.app.status.status_label.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.status.size_label.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
+            }
+        }
+        if self.app.current_page == Page::Notifications {
+            if self.app.notifications.enable_toggle.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.notifications.bell_toggle.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.notifications.duration_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+        }
+        if self.app.current_page == Page::Processors {
+            if self.app.processors.cpu_label.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            for gpu_lbl in &mut self.app.processors.gpu_labels {
+                if gpu_lbl.cursor_moved(lx, ly) {
+                    changed = true;
                 }
-                if self.app.current_page == Page::Typeface {
-                    if self.app.typeface.sans_box.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.typeface.serif_box.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
-                    if self.app.typeface.mono_box.cursor_moved(lx, ly) {
-                        changed = true;
-                    }
+            }
+            if self.app.processors.cpu_list_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+        }
+        if self.app.current_page == Page::Status {
+            if self.app.status.status_label.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.status.size_label.cursor_moved(lx, ly) {
+                changed = true;
+            }
+        }
+        if self.app.current_page == Page::Typeface {
+            if self.app.typeface.sans_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.serif_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.mono_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.borders_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.status_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.fuzzel_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.terminal_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.search_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.typeface.list_box.cursor_moved(lx, ly) {
+                changed = true;
+            }
+        }
+        if changed { self.needs_rebuild = true; }
+        changed
+    }
+
+    fn handle_mouse_input(&mut self, button: clear_ui::widget::MouseButton, state: clear_ui::widget::ElementState) -> bool {
+        if button != clear_ui::widget::MouseButton::Left { return false; }
+        if state == clear_ui::widget::ElementState::Released {
+            let (px, py) = (self.cursor_x, self.cursor_y);
+            for btn in &self.page_buttons.clone() {
+                if px >= btn.x && px <= btn.x + btn.w && py >= btn.y && py <= btn.y + btn.h {
+                    self.handle_action(&btn.action);
+                    self.needs_rebuild = true;
+                    return true;
                 }
-                if changed { self.needs_rebuild = true; }
-                changed
-            }
-            WindowEvent::KeyboardInput { event, .. } => {
-                if self.app.current_page == Page::Layout {
-                    let mut changed = false;
-                    let mut actions = Vec::new();
-                    for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
-                        let old = sb.value;
-                        if sb.keyboard_input(event) {
-                            if sb.value != old {
-                                actions.push(AppAction::Layout(
-                                    pages::layout::LayoutMessage::SetWidth(
-                                        pages::layout::WidthParam::ALL[i],
-                                        sb.value as u16,
-                                    )
-                                ));
-                            }
-                            changed = true;
-                        }
-                    }
-                    let sb = &mut self.app.layout.cascade_offset_spinbox;
-                    let old = sb.value;
-                    if sb.keyboard_input(event) {
-                        if sb.value != old {
-                            actions.push(AppAction::Layout(
-                                pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
-                            ));
-                        }
-                        changed = true;
-                    }
-                    let sb = &mut self.app.layout.edge_gap_spinbox;
-                    let old = sb.value;
-                    if sb.keyboard_input(event) {
-                        if sb.value != old {
-                            actions.push(AppAction::Layout(
-                                pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
-                            ));
-                        }
-                        changed = true;
-                    }
-                    let sb = &mut self.app.layout.top_gap_spinbox;
-                    let old = sb.value;
-                    if sb.keyboard_input(event) {
-                        if sb.value != old {
-                            actions.push(AppAction::Layout(
-                                pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
-                            ));
-                        }
-                        changed = true;
-                    }
-                    for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
-                        let old = cp.color;
-                        if cp.keyboard_input(event) {
-                            if cp.color != old {
-                                actions.push(AppAction::Layout(match i {
-                                    0 => pages::layout::LayoutMessage::SetBackground(cp.color),
-                                    _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
-                                }));
-                            }
-                            changed = true;
+            }
+            for w in &self.widgets {
+                if px >= w.x && px <= w.x + w.w && py >= w.y && py <= w.y + w.h {
+                    if let WidgetKind::PageButton(p) = &w.kind {
+                        if self.app.current_page != *p {
+                            self.app.current_page = *p;
+                            self.scroll_y = 0.0;
+                            self.needs_rebuild = true;
+                            return true;
                         }
                     }
-                    for a in &actions {
-                        self.handle_action(a);
-                    }
-                    if changed {
-                        self.needs_rebuild = true;
-                        return true;
-                    }
                 }
-                if self.app.current_page == Page::Notifications {
-                    let sb = &mut self.app.notifications.duration_spinbox;
-                    let old = sb.value;
-                    if sb.keyboard_input(event) {
-                        let new_val = sb.value;
-                        drop(sb);
-                        if new_val != old {
-                            self.handle_action(&AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(new_val)));
-                        }
-                        self.needs_rebuild = true;
-                        return true;
-                    }
+            }
+        }
+        let s = self.scale_factor as f32;
+        let lx = self.cursor_x / s;
+        let ly = self.cursor_y / s + self.scroll_y;
+        let mut actions = Vec::new();
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Layout {
+            for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
+                if !sb.hit_test(lx, ly) { sb.unfocus(); }
+                let old = sb.value;
+                if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                    actions.push(AppAction::Layout(
+                        pages::layout::LayoutMessage::SetWidth(
+                            pages::layout::WidthParam::ALL[i],
+                            sb.value as u16,
+                        )
+                    ));
                 }
-                if self.app.current_page == Page::Input {
-                    if self.app.input.rate_spinbox.keyboard_input(event) {
-                        self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
-                        self.needs_rebuild = true;
-                        return true;
-                    }
-                    if self.app.input.delay_spinbox.keyboard_input(event) {
-                        self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
-                        self.needs_rebuild = true;
-                        return true;
-                    }
-                    if self.app.input.scroll_friction_spinbox.keyboard_input(event) {
-                        self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
-                        self.needs_rebuild = true;
-                        return true;
-                    }
-                    if self.app.input.pointer_friction_spinbox.keyboard_input(event) {
-                        self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
-                        self.needs_rebuild = true;
-                        return true;
-                    }
-                    if self.app.input.trackpad_friction_spinbox.keyboard_input(event) {
-                        self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
-                        self.needs_rebuild = true;
-                        return true;
-                    }
+            }
+            let sb = &mut self.app.layout.cascade_offset_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Layout(
+                    pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
+                ));
+            }
+            let sb = &mut self.app.layout.edge_gap_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Layout(
+                    pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
+                ));
+            }
+            let sb = &mut self.app.layout.top_gap_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Layout(
+                    pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
+                ));
+            }
+            for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
+                let old = cp.color;
+                if !cp.hit_test(lx, ly) { cp.unfocus(); }
+                cp.mouse_input(button, state, lx, ly);
+                if cp.take_click() {
+                    actions.push(AppAction::Layout(match i {
+                        0 => pages::layout::LayoutMessage::PickBackgroundColor,
+                        _ => pages::layout::LayoutMessage::PickBorderColor,
+                    }));
                 }
-                if self.app.current_page == Page::Audio {
-                    let mut actions = Vec::new();
-                    for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
-                        let old = sb.value;
-                        if sb.keyboard_input(event) {
-                            if sb.value != old {
-                                let id = self.app.audio.sinks[i].id;
-                                actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
-                            }
-                        }
-                    }
-                    for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
-                        let old = sb.value;
-                        if sb.keyboard_input(event) {
-                            if sb.value != old {
-                                let id = self.app.audio.sources[i].id;
-                                actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
-                            }
-                        }
-                    }
-                    for a in &actions {
-                        self.handle_action(a);
-                    }
-                    if !actions.is_empty() {
-                        self.needs_rebuild = true;
-                        return true;
-                    }
+                if cp.color != old {
+                    actions.push(AppAction::Layout(match i {
+                        0 => pages::layout::LayoutMessage::SetBackground(cp.color),
+                        _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+                    }));
                 }
-                if self.app.current_page == Page::Display {
-                    let sb = &mut self.app.display.brightness_spinbox;
-                    let old = sb.value;
-                    if sb.keyboard_input(event) {
-                        let new_val = sb.value;
-                        drop(sb);
-                        if new_val != old {
-                            self.handle_action(&AppAction::Display(pages::display::DisplayMessage::BrightnessSet(new_val as u32)));
-                        }
-                        self.needs_rebuild = true;
-                        return true;
-                    }
+            }
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Input {
+            let sb = &mut self.app.input.rate_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+            }
+            let sb = &mut self.app.input.delay_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+            }
+            let sb = &mut self.app.input.scroll_friction_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
+            }
+            let sb = &mut self.app.input.pointer_friction_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
+            }
+            let sb = &mut self.app.input.trackpad_friction_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
+            }
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Notifications {
+            let sb = &mut self.app.notifications.duration_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(sb.value)));
+            }
+        }
+        if self.app.current_page == Page::Input {
+            let toggle = &mut self.app.input.tap_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ToggleTapToClick));
+            }
+            let toggle = &mut self.app.input.scroll_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialScroll));
+            }
+            let toggle = &mut self.app.input.pointer_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialPointer));
+            }
+            let toggle = &mut self.app.input.trackpad_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialTrackpad));
+            }
+        }
+        if self.app.current_page == Page::Notifications {
+            let toggle = &mut self.app.notifications.enable_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleEnable));
+            }
+            let toggle = &mut self.app.notifications.bell_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleBell));
+            }
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Audio {
+            for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
+                if !sb.hit_test(lx, ly) { sb.unfocus(); }
+                let old = sb.value;
+                if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                    let id = self.app.audio.sinks[i].id;
+                    actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
                 }
-                if self.app.current_page == Page::Typeface {
-                    let mut actions = Vec::new();
-                    let mut consumed = false;
-                    
-                    let tb = &mut self.app.typeface.sans_box;
-                    if tb.keyboard_input(event) {
-                        if tb.take_change() {
-                            actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
-                        }
-                        consumed = true;
-                    }
+            }
+            for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
+                if !sb.hit_test(lx, ly) { sb.unfocus(); }
+                let old = sb.value;
+                if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                    let id = self.app.audio.sources[i].id;
+                    actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
+                }
+            }
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Display {
+            let sb = &mut self.app.display.brightness_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Display(pages::display::DisplayMessage::BrightnessSet(sb.value as u32)));
+            }
+            let lbl = &mut self.app.display.night_light_label;
+            if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
+            lbl.mouse_input(button, state, lx, ly);
+
+            for out in &mut self.app.display.outputs {
+                let lbl = &mut out.name_label;
+                if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
+                lbl.mouse_input(button, state, lx, ly);
+
+                let lbl2 = &mut out.resolution_label;
+                if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
+                lbl2.mouse_input(button, state, lx, ly);
+
+                if let Some(ref mut scale_lbl) = out.scale_label {
+                    if !scale_lbl.hit_test(lx, ly) { scale_lbl.unfocus(); }
+                    scale_lbl.mouse_input(button, state, lx, ly);
+                }
+            }
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Status {
+            let lbl1 = &mut self.app.status.status_label;
+            if !lbl1.hit_test(lx, ly) { lbl1.unfocus(); }
+            lbl1.mouse_input(button, state, lx, ly);
+
+            let lbl2 = &mut self.app.status.size_label;
+            if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
+            lbl2.mouse_input(button, state, lx, ly);
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Typeface {
+            let tb = &mut self.app.typeface.sans_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
+            }
 
-                    let tb = &mut self.app.typeface.serif_box;
-                    if tb.keyboard_input(event) {
-                        if tb.take_change() {
-                            actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
-                        }
-                        consumed = true;
-                    }
+            let tb = &mut self.app.typeface.serif_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
+            }
 
-                    let tb = &mut self.app.typeface.mono_box;
-                    if tb.keyboard_input(event) {
-                        if tb.take_change() {
-                            actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
-                        }
-                        consumed = true;
-                    }
-                    
-                    for a in &actions {
-                        self.handle_action(a);
-                    }
-                    if consumed {
-                        self.needs_rebuild = true;
-                        return true;
-                    }
+            let tb = &mut self.app.typeface.mono_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
+            }
+
+            let tb = &mut self.app.typeface.borders_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetBorders(tb.text.clone())));
+            }
+
+            let tb = &mut self.app.typeface.status_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetStatus(tb.text.clone())));
+            }
+
+            let tb = &mut self.app.typeface.fuzzel_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetFuzzel(tb.text.clone())));
+            }
+
+            let tb = &mut self.app.typeface.terminal_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetTerminal(tb.text.clone())));
+            }
+
+            let tb = &mut self.app.typeface.search_box;
+            if !tb.hit_test(lx, ly) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if tb.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSearch(tb.text.clone())));
+            }
+        }
+        for a in &actions {
+            self.handle_action(a);
+        }
+        if !actions.is_empty() {
+            self.needs_rebuild = true;
+            return true;
+        }
+        self.needs_rebuild = true;
+        true
+    }
+
+    fn handle_mouse_wheel(&mut self, delta: &clear_ui::widget::MouseScrollDelta) -> bool {
+        let s = self.scale_factor as f32;
+        if self.cursor_x >= self.sidebar_width * s {
+            let lx = self.cursor_x / s;
+            let ly = self.cursor_y / s + self.scroll_y;
+            
+            if self.app.current_page == Page::Typeface {
+                let tf = &mut self.app.typeface;
+                if tf.list_box.mouse_wheel(delta, lx, ly) {
+                    self.needs_rebuild = true;
+                    return true;
                 }
-                false
-            }
-            WindowEvent::MouseInput { state, button, .. } => {
-                if *button != MouseButton::Left { return false; }
-                if *state == ElementState::Released {
-                    let (px, py) = (self.cursor_x, self.cursor_y);
-                    for btn in &self.page_buttons.clone() {
-                        if px >= btn.x && px <= btn.x + btn.w && py >= btn.y && py <= btn.y + btn.h {
-                            self.handle_action(&btn.action);
-                            self.needs_rebuild = true;
-                            return true;
-                        }
-                    }
-                    for w in &self.widgets {
-                        if px >= w.x && px <= w.x + w.w && py >= w.y && py <= w.y + w.h {
-                            if let WidgetKind::PageButton(p) = &w.kind {
-                                if self.app.current_page != *p {
-                                    self.app.current_page = *p;
-                                    self.scroll_y = 0.0;
-                                    self.needs_rebuild = true;
-                                    return true;
-                                }
-                            }
-                        }
-                    }
+            }
+            if self.app.current_page == Page::Processors {
+                let proc = &mut self.app.processors;
+                if proc.cpu_list_box.mouse_wheel(delta, lx, ly) {
+                    self.needs_rebuild = true;
+                    return true;
                 }
-                let s = self.scale_factor as f32;
-                let lx = self.cursor_x / s;
-                let ly = self.cursor_y / s + self.scroll_y;
-                let mut actions = Vec::new();
-                if *state == ElementState::Pressed && self.app.current_page == Page::Layout {
-                    for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
-                        if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                        let old = sb.value;
-                        if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                            actions.push(AppAction::Layout(
-                                pages::layout::LayoutMessage::SetWidth(
-                                    pages::layout::WidthParam::ALL[i],
-                                    sb.value as u16,
-                                )
-                            ));
-                        }
-                    }
-                    let sb = &mut self.app.layout.cascade_offset_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Layout(
-                            pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
-                        ));
-                    }
-                    let sb = &mut self.app.layout.edge_gap_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Layout(
-                            pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
-                        ));
-                    }
-                    let sb = &mut self.app.layout.top_gap_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
+            }
+
+            let scroll_speed = 24.0;
+            let dy = match delta {
+                clear_ui::widget::MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
+                clear_ui::widget::MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
+            };
+            let old_scroll = self.scroll_y;
+            self.scroll_y = (self.scroll_y + dy).max(0.0).min(self.max_scroll_y);
+            if (self.scroll_y - old_scroll).abs() > 0.01 {
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        false
+    }
+
+    fn handle_key_input(&mut self, event: &clear_ui::widget::KeyEvent) -> bool {
+        if self.app.current_page == Page::Layout {
+            let mut changed = false;
+            let mut actions = Vec::new();
+            for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
+                let old = sb.value;
+                if sb.keyboard_input(event) {
+                    if sb.value != old {
                         actions.push(AppAction::Layout(
-                            pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
+                            pages::layout::LayoutMessage::SetWidth(
+                                pages::layout::WidthParam::ALL[i],
+                                sb.value as u16,
+                            )
                         ));
                     }
-                    for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
-                        let old = cp.color;
-                        if !cp.hit_test(lx, ly) { cp.unfocus(); }
-                        cp.mouse_input(*button, *state, lx, ly);
-                        if cp.take_click() {
-                            actions.push(AppAction::Layout(match i {
-                                0 => pages::layout::LayoutMessage::PickBackgroundColor,
-                                _ => pages::layout::LayoutMessage::PickBorderColor,
-                            }));
-                        }
-                        if cp.color != old {
-                            actions.push(AppAction::Layout(match i {
-                                0 => pages::layout::LayoutMessage::SetBackground(cp.color),
-                                _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
-                            }));
-                        }
-                    }
+                    changed = true;
                 }
-                if *state == ElementState::Pressed && self.app.current_page == Page::Input {
-                    let sb = &mut self.app.input.rate_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
-                    }
-                    let sb = &mut self.app.input.delay_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
-                    }
-                    let sb = &mut self.app.input.scroll_friction_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
-                    }
-                    let sb = &mut self.app.input.pointer_friction_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
-                    }
-                    let sb = &mut self.app.input.trackpad_friction_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
-                    }
+            }
+            let sb = &mut self.app.layout.cascade_offset_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event) {
+                if sb.value != old {
+                    actions.push(AppAction::Layout(
+                        pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
+                    ));
                 }
-                if *state == ElementState::Pressed && self.app.current_page == Page::Notifications {
-                    let sb = &mut self.app.notifications.duration_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(sb.value)));
-                    }
+                changed = true;
+            }
+            let sb = &mut self.app.layout.edge_gap_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event) {
+                if sb.value != old {
+                    actions.push(AppAction::Layout(
+                        pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
+                    ));
                 }
-                if self.app.current_page == Page::Input {
-                    let toggle = &mut self.app.input.tap_toggle;
-                    toggle.mouse_input(*button, *state, lx, ly);
-                    if toggle.take_click() {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ToggleTapToClick));
-                    }
-                    let toggle = &mut self.app.input.scroll_toggle;
-                    toggle.mouse_input(*button, *state, lx, ly);
-                    if toggle.take_click() {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialScroll));
-                    }
-                    let toggle = &mut self.app.input.pointer_toggle;
-                    toggle.mouse_input(*button, *state, lx, ly);
-                    if toggle.take_click() {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialPointer));
-                    }
-                    let toggle = &mut self.app.input.trackpad_toggle;
-                    toggle.mouse_input(*button, *state, lx, ly);
-                    if toggle.take_click() {
-                        actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialTrackpad));
-                    }
+                changed = true;
+            }
+            let sb = &mut self.app.layout.top_gap_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event) {
+                if sb.value != old {
+                    actions.push(AppAction::Layout(
+                        pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
+                    ));
                 }
-                if self.app.current_page == Page::Notifications {
-                    let toggle = &mut self.app.notifications.enable_toggle;
-                    toggle.mouse_input(*button, *state, lx, ly);
-                    if toggle.take_click() {
-                        actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleEnable));
-                    }
-                    let toggle = &mut self.app.notifications.bell_toggle;
-                    toggle.mouse_input(*button, *state, lx, ly);
-                    if toggle.take_click() {
-                        actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleBell));
-                    }
+                changed = true;
+            }
+            for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
+                let old = cp.color;
+                if cp.keyboard_input(event) {
+                    if cp.color != old {
+                        actions.push(AppAction::Layout(match i {
+                            0 => pages::layout::LayoutMessage::SetBackground(cp.color),
+                            _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+                        }));
+                    }
+                    changed = true;
                 }
-                if *state == ElementState::Pressed && self.app.current_page == Page::Audio {
-                    for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
-                        if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                        let old = sb.value;
-                        if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                            let id = self.app.audio.sinks[i].id;
-                            actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
-                        }
-                    }
-                    for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
-                        if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                        let old = sb.value;
-                        if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                            let id = self.app.audio.sources[i].id;
-                            actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
-                        }
-                    }
+            }
+            for a in &actions {
+                self.handle_action(a);
+            }
+            if changed {
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        if self.app.current_page == Page::Notifications {
+            let sb = &mut self.app.notifications.duration_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event) {
+                let new_val = sb.value;
+                drop(sb);
+                if new_val != old {
+                    self.handle_action(&AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(new_val)));
                 }
-                if *state == ElementState::Pressed && self.app.current_page == Page::Display {
-                    let sb = &mut self.app.display.brightness_spinbox;
-                    if !sb.hit_test(lx, ly) { sb.unfocus(); }
-                    let old = sb.value;
-                    if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
-                        actions.push(AppAction::Display(pages::display::DisplayMessage::BrightnessSet(sb.value as u32)));
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        if self.app.current_page == Page::Input {
+            if self.app.input.rate_spinbox.keyboard_input(event) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+                self.needs_rebuild = true;
+                return true;
+            }
+            if self.app.input.delay_spinbox.keyboard_input(event) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+                self.needs_rebuild = true;
+                return true;
+            }
+            if self.app.input.scroll_friction_spinbox.keyboard_input(event) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
+                self.needs_rebuild = true;
+                return true;
+            }
+            if self.app.input.pointer_friction_spinbox.keyboard_input(event) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
+                self.needs_rebuild = true;
+                return true;
+            }
+            if self.app.input.trackpad_friction_spinbox.keyboard_input(event) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        if self.app.current_page == Page::Audio {
+            let mut actions = Vec::new();
+            for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
+                let old = sb.value;
+                if sb.keyboard_input(event) {
+                    if sb.value != old {
+                        let id = self.app.audio.sinks[i].id;
+                        actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
                     }
-                    let lbl = &mut self.app.display.night_light_label;
-                    if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
-                    lbl.mouse_input(*button, *state, lx, ly);
-
-                    for out in &mut self.app.display.outputs {
-                        let lbl = &mut out.name_label;
-                        if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
-                        lbl.mouse_input(*button, *state, lx, ly);
-
-                        let lbl2 = &mut out.resolution_label;
-                        if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
-                        lbl2.mouse_input(*button, *state, lx, ly);
-
-                        if let Some(ref mut scale_lbl) = out.scale_label {
-                            if !scale_lbl.hit_test(lx, ly) { scale_lbl.unfocus(); }
-                            scale_lbl.mouse_input(*button, *state, lx, ly);
-                        }
+                }
+            }
+            for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
+                let old = sb.value;
+                if sb.keyboard_input(event) {
+                    if sb.value != old {
+                        let id = self.app.audio.sources[i].id;
+                        actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
                     }
                 }
-                if *state == ElementState::Pressed && self.app.current_page == Page::Status {
-                    let lbl1 = &mut self.app.status.status_label;
-                    if !lbl1.hit_test(lx, ly) { lbl1.unfocus(); }
-                    lbl1.mouse_input(*button, *state, lx, ly);
-
-                    let lbl2 = &mut self.app.status.size_label;
-                    if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
-                    lbl2.mouse_input(*button, *state, lx, ly);
+            }
+            for a in &actions {
+                self.handle_action(a);
+            }
+            if !actions.is_empty() {
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        if self.app.current_page == Page::Display {
+            let sb = &mut self.app.display.brightness_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event) {
+                let new_val = sb.value;
+                drop(sb);
+                if new_val != old {
+                    self.handle_action(&AppAction::Display(pages::display::DisplayMessage::BrightnessSet(new_val as u32)));
                 }
-                if *state == ElementState::Pressed && self.app.current_page == Page::Typeface {
-                    let tb = &mut self.app.typeface.sans_box;
-                    if !tb.hit_test(lx, ly) { tb.unfocus(); }
-                    if tb.mouse_input(*button, *state, lx, ly) {
-                        self.needs_rebuild = true;
-                    }
-                    if tb.take_change() {
-                        actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
-                    }
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        if self.app.current_page == Page::Typeface {
+            let mut actions = Vec::new();
+            let mut consumed = false;
+            
+            let tb = &mut self.app.typeface.sans_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
+                }
+                consumed = true;
+            }
 
-                    let tb = &mut self.app.typeface.serif_box;
-                    if !tb.hit_test(lx, ly) { tb.unfocus(); }
-                    if tb.mouse_input(*button, *state, lx, ly) {
-                        self.needs_rebuild = true;
-                    }
-                    if tb.take_change() {
-                        actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
-                    }
+            let tb = &mut self.app.typeface.serif_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
+                }
+                consumed = true;
+            }
 
-                    let tb = &mut self.app.typeface.mono_box;
-                    if !tb.hit_test(lx, ly) { tb.unfocus(); }
-                    if tb.mouse_input(*button, *state, lx, ly) {
-                        self.needs_rebuild = true;
-                    }
-                    if tb.take_change() {
-                        actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
-                    }
+            let tb = &mut self.app.typeface.mono_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
                 }
-                for a in &actions {
-                    self.handle_action(a);
+                consumed = true;
+            }
+
+            let tb = &mut self.app.typeface.borders_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetBorders(tb.text.clone())));
                 }
-                if !actions.is_empty() {
-                    self.needs_rebuild = true;
-                    return true;
+                consumed = true;
+            }
+
+            let tb = &mut self.app.typeface.status_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetStatus(tb.text.clone())));
                 }
+                consumed = true;
+            }
+
+            let tb = &mut self.app.typeface.fuzzel_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetFuzzel(tb.text.clone())));
+                }
+                consumed = true;
+            }
+
+            let tb = &mut self.app.typeface.terminal_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetTerminal(tb.text.clone())));
+                }
+                consumed = true;
+            }
+
+            let tb = &mut self.app.typeface.search_box;
+            if tb.keyboard_input(event) {
+                if tb.take_change() {
+                    actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSearch(tb.text.clone())));
+                }
+                consumed = true;
+            }
+            
+            for a in &actions {
+                self.handle_action(a);
+            }
+            if consumed {
                 self.needs_rebuild = true;
-                self.needs_rebuild = true;
-                true
+                return true;
             }
-            _ => false,
         }
+        false
     }
 
     fn render(&mut self) {
@@ -1260,10 +1403,10 @@ impl SystemInterface {
 
         self.prepare_text();
 
-        let output = match self.surface.get_current_texture() {
+        let output = match self.wgpu_surface.get_current_texture() {
             Ok(t) => t,
             Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
-                self.surface.configure(&self.device, &self.config);
+                self.wgpu_surface.configure(&self.device, &self.config);
                 return;
             }
             Err(wgpu::SurfaceError::Timeout) => return,
@@ -1299,57 +1442,352 @@ impl SystemInterface {
         }
 
         self.queue.submit(std::iter::once(encoder.finish()));
-        self.window.pre_present_notify();
         output.present();
     }
 }
 
 struct App {
+    registry_state: RegistryState,
+    compositor_state: CompositorState,
+    xdg_shell_state: XdgShell,
+    shm_state: Shm,
+    seat_state: SeatState,
+    output_state: OutputState,
+
+    seats: Vec<wl_seat::WlSeat>,
+    pointer: Option<wl_pointer::WlPointer>,
+    keyboard: Option<wl_keyboard::WlKeyboard>,
+
     state: Option<SystemInterface>,
     initial_page: Page,
+    exit: bool,
+    redraw: bool,
 }
-impl App {
-    fn new(initial_page: Page) -> Self { Self { state: None, initial_page } }
+
+
+
+impl CompositorHandler for App {
+    fn scale_factor_changed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        scale_factor: i32,
+    ) {
+        if let Some(state) = &mut self.state {
+            state.scale_factor = (scale_factor as f32).max(2.0) as f64;
+            state.resize(state.width, state.height);
+        }
+        self.redraw = true;
+    }
+
+    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 ApplicationHandler for App {
-    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
-        if self.state.is_some() { return; }
-        let mut attributes = WindowAttributes::default()
-            .with_title("Clear System Interface")
-            .with_decorations(false)
-            .with_inner_size(winit::dpi::LogicalSize::new(820, 680));
-        #[cfg(target_os = "linux")]
-        {
-            attributes = attributes.with_name("clear-system-interface", "clear-system-interface");
-        }
-        let window = Arc::new(event_loop.create_window(attributes).unwrap());
-        let mut state = pollster::block_on(SystemInterface::new(window));
-        state.app.current_page = self.initial_page;
-        state.needs_rebuild = true;
-        self.state = Some(state);
-        self.state.as_ref().unwrap().window.request_redraw();
+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 SeatHandler for App {
+    fn seat_state(&mut self) -> &mut SeatState {
+        &mut self.seat_state
+    }
+
+    fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+        self.seats.push(seat);
+    }
+
+    fn new_capability(
+        &mut self,
+        _conn: &Connection,
+        qh: &QueueHandle<Self>,
+        seat: wl_seat::WlSeat,
+        capability: Capability,
+    ) {
+        if capability == Capability::Pointer && self.pointer.is_none() {
+            let pointer = self.seat_state.get_pointer(qh, &seat).unwrap();
+            self.pointer = Some(pointer);
+        }
+        if capability == Capability::Keyboard && self.keyboard.is_none() {
+            let keyboard = self.seat_state.get_keyboard(qh, &seat, None).unwrap();
+            self.keyboard = Some(keyboard);
+        }
+    }
+
+    fn remove_capability(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _seat: wl_seat::WlSeat,
+        capability: Capability,
+    ) {
+        if capability == Capability::Pointer {
+            self.pointer = None;
+        }
+        if capability == Capability::Keyboard {
+            self.keyboard = None;
+        }
     }
 
-    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: winit::window::WindowId, event: WindowEvent) {
-        let redraw = match &event {
-            WindowEvent::CloseRequested => { event_loop.exit(); true }
-            WindowEvent::Resized(s) => { if let Some(st) = &mut self.state { st.resize(*s); } true }
-            WindowEvent::RedrawRequested => {
-                if let Some(st) = &mut self.state { st.render(); st.window.request_redraw(); }
-                true
+    fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+        self.seats.retain(|s| s != &seat);
+    }
+}
+
+impl ShmHandler for App {
+    fn shm_state(&mut self) -> &mut Shm {
+        &mut self.shm_state
+    }
+}
+
+impl PointerHandler for App {
+    fn pointer_frame(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _pointer: &wl_pointer::WlPointer,
+        events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
+    ) {
+        use smithay_client_toolkit::seat::pointer::PointerEventKind;
+        for event in events {
+            let (x, y) = event.position;
+            match &event.kind {
+                PointerEventKind::Motion { .. } => {
+                    if let Some(st) = &mut self.state {
+                        if st.handle_cursor_moved(x as f32, y as f32) {
+                            self.redraw = true;
+                        }
+                    }
+                }
+                PointerEventKind::Press { button, .. } => {
+                    let custom_btn = match button {
+                        272 => clear_ui::widget::MouseButton::Left,
+                        273 => clear_ui::widget::MouseButton::Right,
+                        _ => continue,
+                    };
+                    if let Some(st) = &mut self.state {
+                        if st.handle_mouse_input(custom_btn, clear_ui::widget::ElementState::Pressed) {
+                            self.redraw = true;
+                        }
+                    }
+                }
+                PointerEventKind::Release { button, .. } => {
+                    let custom_btn = match button {
+                        272 => clear_ui::widget::MouseButton::Left,
+                        273 => clear_ui::widget::MouseButton::Right,
+                        _ => continue,
+                    };
+                    if let Some(st) = &mut self.state {
+                        if st.handle_mouse_input(custom_btn, clear_ui::widget::ElementState::Released) {
+                            self.redraw = true;
+                        }
+                    }
+                }
+                PointerEventKind::Axis { horizontal, vertical, .. } => {
+                    let h_scroll = horizontal.absolute as f32;
+                    let v_scroll = vertical.absolute as f32;
+                    let delta = clear_ui::widget::MouseScrollDelta::LineDelta(-h_scroll / 10.0, -v_scroll / 10.0);
+                    if let Some(st) = &mut self.state {
+                        if st.handle_mouse_wheel(&delta) {
+                            self.redraw = true;
+                        }
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+}
+
+impl KeyboardHandler for App {
+    fn enter(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _serial: u32,
+        _raw_modifiers: &[u32],
+        _keysyms: &[xkeysym::Keysym],
+    ) {}
+
+    fn leave(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _serial: u32,
+    ) {}
+
+    fn press_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+    ) {
+        self.handle_key(event, clear_ui::widget::ElementState::Pressed);
+    }
+
+    fn release_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+    ) {
+        self.handle_key(event, clear_ui::widget::ElementState::Released);
+    }
+
+    fn update_modifiers(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        _modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
+        _layout: u32,
+    ) {}
+}
+
+impl App {
+    fn handle_key(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent, state: clear_ui::widget::ElementState) {
+        use clear_ui::widget::{Key, KeyEvent, NamedKey};
+        let logical_key = match event.keysym {
+            xkeysym::Keysym::Escape => Key::Named(NamedKey::Escape),
+            xkeysym::Keysym::Return => Key::Named(NamedKey::Enter),
+            xkeysym::Keysym::BackSpace => Key::Named(NamedKey::Backspace),
+            xkeysym::Keysym::Down => Key::Named(NamedKey::ArrowDown),
+            xkeysym::Keysym::Up => Key::Named(NamedKey::ArrowUp),
+            xkeysym::Keysym::Left => Key::Named(NamedKey::ArrowLeft),
+            xkeysym::Keysym::Right => Key::Named(NamedKey::ArrowRight),
+            xkeysym::Keysym::Tab => Key::Named(NamedKey::Tab),
+            xkeysym::Keysym::Delete => Key::Named(NamedKey::Delete),
+            xkeysym::Keysym::space => Key::Named(NamedKey::Space),
+            _ => {
+                if let Some(ref text) = event.utf8 {
+                    Key::Character(text.clone())
+                } else {
+                    return;
+                }
             }
-            _ => self.state.as_mut().map(|st| st.handle_event(&event)).unwrap_or(false)
         };
-        if redraw { if let Some(st) = &mut self.state { st.window.request_redraw(); } }
+
+        let custom_event = KeyEvent {
+            state,
+            logical_key,
+            text: event.utf8.clone(),
+            repeat: false,
+        };
+
+        if let Some(st) = &mut self.state {
+            if st.handle_key_input(&custom_event) {
+                self.redraw = true;
+            }
+        }
+    }
+}
+
+impl WindowHandler for App {
+    fn configure(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _window: &XdgWindow,
+        configure: WindowConfigure,
+        _serial: u32,
+    ) {
+        let (w, h) = configure.new_size;
+        if let (Some(w), Some(h)) = (w, h) {
+            let width = w.get();
+            let height = h.get();
+            if let Some(state) = &mut self.state {
+                state.resize(width, height);
+            }
+        }
+        self.redraw = true;
+    }
+
+    fn request_close(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _window: &XdgWindow) {
+        self.exit = true;
     }
 }
 
+impl ProvidesRegistryState for App {
+    fn registry(&mut self) -> &mut RegistryState {
+        &mut self.registry_state
+    }
+    
+    fn runtime_add_global(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _name: u32,
+        _interface: &str,
+        _version: u32,
+    ) {}
+    
+    fn runtime_remove_global(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _name: u32,
+        _interface: &str,
+    ) {}
+}
+
+delegate_compositor!(App);
+delegate_xdg_shell!(App);
+delegate_xdg_window!(App);
+delegate_shm!(App);
+delegate_seat!(App);
+delegate_pointer!(App);
+delegate_keyboard!(App);
+delegate_registry!(App);
+delegate_output!(App);
+
 fn main() {
     let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
     let _guard = rt.enter();
-    let event_loop = EventLoop::new().unwrap();
-    event_loop.set_control_flow(ControlFlow::Poll);
 
     let mut initial_page = Page::ALL[0];
     let args: Vec<String> = std::env::args().collect();
@@ -1363,5 +1801,58 @@ fn main() {
         }
     }
 
-    event_loop.run_app(&mut App::new(initial_page)).unwrap();
+    let conn = Connection::connect_to_env().unwrap();
+    let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
+    let qh = event_queue.handle();
+
+    let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
+    let xdg_shell_state = XdgShell::bind(&globals, &qh).unwrap();
+    let shm_state = Shm::bind(&globals, &qh).unwrap();
+    let seat_state = SeatState::new(&globals, &qh);
+    let output_state = OutputState::new(&globals, &qh);
+
+    let mut app = App {
+        registry_state: RegistryState::new(&globals),
+        compositor_state,
+        xdg_shell_state,
+        shm_state,
+        seat_state,
+        output_state,
+        seats: Vec::new(),
+        pointer: None,
+        keyboard: None,
+        state: None,
+        initial_page,
+        exit: false,
+        redraw: true,
+    };
+
+    let state = pollster::block_on(SystemInterface::new(
+        &conn,
+        &qh,
+        &app.compositor_state,
+        &app.xdg_shell_state,
+        820,
+        680,
+    ));
+    app.state = Some(state);
+
+    let mut event_loop = EventLoop::try_new().unwrap();
+    let loop_handle = event_loop.handle();
+    WaylandSource::new(conn, event_queue).insert(loop_handle.clone()).unwrap();
+
+    loop {
+        event_loop
+            .dispatch(std::time::Duration::from_millis(16), &mut app)
+            .unwrap();
+        if app.exit {
+            break;
+        }
+        if app.redraw {
+            app.redraw = false;
+            if let Some(state) = &mut app.state {
+                state.render();
+            }
+        }
+    }
 }
diff --git a/src/pages/display.rs b/src/pages/display.rs
index 20d96d9..1ad3c68 100644
--- a/src/pages/display.rs
+++ b/src/pages/display.rs
@@ -61,7 +61,7 @@ impl Default for DisplayState {
             max_brightness: 0.0,
             outputs: Vec::new(),
             night_light: false,
-            brightness_spinbox: Spinbox::new(50, 0, 100, 5),
+            brightness_spinbox: Spinbox::new(50, 0, 100, 5).with_unit("%"),
             night_light_label: Label::new("Night Light: OFF").with_font_size(13.0).with_color([0xd4, 0xd4, 0xd4]),
         }
     }
@@ -83,7 +83,7 @@ pub async fn fetch_display_state() -> DisplayState {
     DisplayState {
         loaded: true,
         brightness, max_brightness, outputs, night_light,
-        brightness_spinbox: Spinbox::new(pct, 0, 100, 5),
+        brightness_spinbox: Spinbox::new(pct, 0, 100, 5).with_unit("%"),
         night_light_label: Label::new(if night_light { "Night Light: ON" } else { "Night Light: OFF" })
             .with_font_size(13.0)
             .with_color([0xd4, 0xd4, 0xd4]),
diff --git a/src/pages/processors.rs b/src/pages/processors.rs
index 4d45c9b..2c7f855 100644
--- a/src/pages/processors.rs
+++ b/src/pages/processors.rs
@@ -1,18 +1,98 @@
 use crate::app::PageContent;
 use clear_ui::layout::Section;
+use clear_ui::widget::{Label, ScrollBox};
 
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
 pub struct ProcessorsState {
     pub cpu_model: String,
     pub cpu_usage: f32,
     pub cpu_cores: u32,
-    pub gpu: String,
+    pub gpus: Vec<String>,
     pub loaded: bool,
+    pub cpu_label: Label,
+    pub gpu_labels: Vec<Label>,
+    pub processes: Vec<(String, String, String)>, // (pid, cpu, comm)
+    pub cpu_list_box: ScrollBox,
+}
+
+impl Default for ProcessorsState {
+    fn default() -> Self {
+        Self {
+            cpu_model: String::new(),
+            cpu_usage: 0.0,
+            cpu_cores: 0,
+            gpus: Vec::new(),
+            loaded: false,
+            cpu_label: Label::new("CPU Info"),
+            gpu_labels: Vec::new(),
+            processes: Vec::new(),
+            cpu_list_box: ScrollBox::new(),
+        }
+    }
 }
 
 #[derive(Debug, Clone)]
 pub enum ProcessorsMessage {
     Refreshed(ProcessorsState),
+    None,
+}
+
+fn read_cpu_temp() -> Option<f32> {
+    if let Ok(entries) = std::fs::read_dir("/sys/class/hwmon") {
+        for entry in entries.filter_map(|e| e.ok()) {
+            let path = entry.path();
+            if let Ok(name) = std::fs::read_to_string(path.join("name")) {
+                let name = name.trim();
+                if name == "thinkpad" {
+                    if let Ok(val) = std::fs::read_to_string(path.join("temp1_input")) {
+                        if let Ok(temp_milli) = val.trim().parse::<f32>() {
+                            return Some(temp_milli / 1000.0);
+                        }
+                    }
+                } else if name == "coretemp" {
+                    if let Ok(val) = std::fs::read_to_string(path.join("temp1_input")) {
+                        if let Ok(temp_milli) = val.trim().parse::<f32>() {
+                            return Some(temp_milli / 1000.0);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    None
+}
+
+fn read_thinkpad_gpu_temp() -> Option<f32> {
+    if let Ok(entries) = std::fs::read_dir("/sys/class/hwmon") {
+        for entry in entries.filter_map(|e| e.ok()) {
+            let path = entry.path();
+            if let Ok(name) = std::fs::read_to_string(path.join("name")) {
+                if name.trim() == "thinkpad" {
+                    for i in 1..=8 {
+                        let label_path = path.join(format!("temp{}_label", i));
+                        if let Ok(lbl) = std::fs::read_to_string(&label_path) {
+                            if lbl.trim() == "GPU" {
+                                if let Ok(val) = std::fs::read_to_string(path.join(format!("temp{}_input", i))) {
+                                    if let Ok(temp_milli) = val.trim().parse::<f32>() {
+                                        return Some(temp_milli / 1000.0);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    None
+}
+
+async fn read_nvidia_gpu_temp() -> Option<f32> {
+    let out = tokio::process::Command::new("nvidia-smi")
+        .args(["--query-gpu=temperature.gpu", "--format=csv,noheader,nounits"])
+        .output().await.ok()?;
+    let val_str = String::from_utf8_lossy(&out.stdout);
+    val_str.trim().parse::<f32>().ok()
 }
 
 pub async fn fetch_processors_state() -> ProcessorsState {
@@ -56,44 +136,178 @@ pub async fn fetch_processors_state() -> ProcessorsState {
         } else { 0.0 }
     } as f32;
 
-    let gpu = tokio::process::Command::new("lspci")
-        .output().await.ok()
-        .and_then(|o| {
-            String::from_utf8_lossy(&o.stdout).lines()
-                .find(|l| l.contains("VGA") || l.contains("3D"))
-                .and_then(|l| l.split(':').nth(2))
-                .map(|s| s.trim().to_string())
-        })
-        .unwrap_or_default();
-
-    ProcessorsState { cpu_model, cpu_usage, cpu_cores, gpu, loaded: true }
+    let mut gpus = Vec::new();
+    if let Some(o) = tokio::process::Command::new("lspci").output().await.ok() {
+        for line in String::from_utf8_lossy(&o.stdout).lines() {
+            if line.contains("VGA") || line.contains("3D") {
+                if let Some(name) = line.split(':').nth(2) {
+                    let trimmed = name.trim().to_string();
+                    if !trimmed.is_empty() {
+                        gpus.push(trimmed);
+                    }
+                }
+            }
+        }
+    }
+
+    let processes = {
+        let mut list = Vec::new();
+        if let Some(o) = tokio::process::Command::new("ps")
+            .args(["-eo", "pid,%cpu,comm", "--sort=-%cpu"])
+            .output().await.ok()
+        {
+            let text = String::from_utf8_lossy(&o.stdout);
+            for line in text.lines().skip(1) {
+                let parts: Vec<&str> = line.split_whitespace().collect();
+                if parts.len() >= 3 {
+                    let pid = parts[0].to_string();
+                    let cpu = parts[1].to_string();
+                    let comm = parts[2..].join(" ");
+                    list.push((pid, cpu, comm));
+                }
+            }
+        }
+        list
+    };
+
+    let cpu_temp = read_cpu_temp();
+    let tp_gpu_temp = read_thinkpad_gpu_temp();
+    let nv_gpu_temp = read_nvidia_gpu_temp().await;
+
+    let cpu_temp_str = cpu_temp.map(|t| format!("  —  {:.0}°C", t)).unwrap_or_default();
+    let cpu_label_text = format!("CPU  {}  ({} cores)  —  {:.0}%{}", cpu_model, cpu_cores, cpu_usage, cpu_temp_str);
+
+    let gpu_labels = gpus.iter().map(|gpu_name| {
+        let temp = if gpu_name.to_lowercase().contains("nvidia") {
+            nv_gpu_temp.or(tp_gpu_temp)
+        } else {
+            tp_gpu_temp
+        };
+        let temp_str = temp.map(|t| format!("  —  {:.0}°C", t)).unwrap_or_default();
+        let text = format!("GPU  {}{}", gpu_name, temp_str);
+        Label::new(&text).with_font_size(12.0).with_color([212, 212, 212])
+    }).collect();
+
+    ProcessorsState {
+        cpu_model,
+        cpu_usage,
+        cpu_cores,
+        gpus,
+        loaded: true,
+        cpu_label: Label::new(&cpu_label_text).with_font_size(12.0).with_color([212, 212, 212]),
+        gpu_labels,
+        processes,
+        cpu_list_box: ScrollBox::new(),
+    }
 }
 
 const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
+const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
 
-pub fn view(state: &ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
     let mut pc = PageContent::new();
-    let y = cy + 12.0;
+    let mut y = cy + 12.0;
 
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Processors");
+    // ── CPU Section ──
+    let mut sec = Section::new(&mut pc, cx, y, cw, "CPU");
     if !state.loaded {
-        sec.text(&mut pc, "Loading processor models and utilization...", 12.0, 0.0, 12.0, TEXT_FG);
+        sec.text(&mut pc, "Loading CPU model and utilization...", 12.0, 0.0, 12.0, TEXT_FG);
         sec.spacing(10.0);
     } else {
-        sec.text(&mut pc,
-            &format!("CPU  {}  ({} cores)  —  {:.0}%", state.cpu_model, state.cpu_cores, state.cpu_usage),
-            12.0, 0.0, 12.0, TEXT_FG,
-        );
-        sec.spacing(10.0);
-        sec.text(&mut pc, &format!("GPU  {}", state.gpu), 12.0, 0.0, 12.0, TEXT_FG);
+        // CPU Info Label
+        sec.widget(&mut pc, &mut state.cpu_label, 12.0, cw - 24.0, 26.0);
+        sec.spacing(12.0);
+
+        // Scrolling box configuration for process list
+        let list_box_x = cx + 12.0;
+        let list_box_y = sec.ay();
+        let list_box_w = cw - 24.0;
+        let list_box_h = 220.0;
+        
+        // Render the standardized ScrollBox widget
+        clear_ui::layout::render_widget(&mut pc, &mut state.cpu_list_box, list_box_x, list_box_y, list_box_w, list_box_h);
+
+        // Header for process list columns (drawn static on top of the ScrollBox background)
+        let header_h = 22.0;
+        pc.rect([0.12, 0.12, 0.16, 0.5], list_box_x + 1.0, list_box_y + 1.0, list_box_w - 2.0, header_h);
+        pc.rect([0.18, 0.18, 0.24, 1.0], list_box_x + 1.0, list_box_y + header_h, list_box_w - 2.0, 1.0); // Divider
+        
+        pc.text("PID", list_box_x + 12.0, list_box_y + 5.0, 11.0, [0.53, 0.53, 0.60, 1.0]);
+        pc.text("COMMAND", list_box_x + 80.0, list_box_y + 5.0, 11.0, [0.53, 0.53, 0.60, 1.0]);
+        pc.text("CPU %", list_box_x + list_box_w - 60.0, list_box_y + 5.0, 11.0, [0.53, 0.53, 0.60, 1.0]);
+
+        let row_h = 24.0;
+        let row_gap = 2.0;
+        let item_height_full = row_h + row_gap;
+        let content_h = state.processes.len() as f32 * item_height_full;
+        
+        // Update ScrollBox bounds for the scrollable viewport (which starts below the header)
+        state.cpu_list_box.update_bounds(content_h, list_box_y + header_h, list_box_h - header_h - 6.0);
+
+        // Visible process rows rendering (virtualized/clipped)
+        
+        for (idx, (pid, cpu, comm)) in state.processes.iter().enumerate() {
+            let virtual_y = idx as f32 * item_height_full + 4.0;
+            
+            if let Some(draw_y) = state.cpu_list_box.get_item_draw_y(virtual_y, row_h) {
+                // Standard row action button (transparent background, highlights on hover)
+                pc.button(
+                    "",
+                    list_box_x + 2.0,
+                    draw_y,
+                    list_box_w - 16.0,
+                    row_h,
+                    [0.0, 0.0, 0.0, 0.0],
+                    [1.0, 1.0, 1.0, 0.06],
+                    [0.0, 0.0, 0.0, 0.0],
+                    crate::app::AppAction::Processors(ProcessorsMessage::None),
+                );
+                
+                pc.text(pid, list_box_x + 12.0, draw_y + 6.0, 12.0, [0.80, 0.80, 0.85, 1.0]);
+                pc.text(comm, list_box_x + 80.0, draw_y + 6.0, 12.0, [0.80, 0.80, 0.85, 1.0]);
+                pc.text(&format!("{}%", cpu), list_box_x + list_box_w - 60.0, draw_y + 6.0, 12.0, [0.56, 0.83, 0.56, 1.0]);
+            }
+        }
+        
+        if state.processes.is_empty() {
+            pc.text("No active processes", list_box_x + 12.0, list_box_y + header_h + 16.0, 12.0, TEXT_DIM);
+        }
+
+        sec.content_y += list_box_h;
+    }
+    y = sec.finish(&mut pc);
+
+    // ── GPU Section ──
+    let mut sec_gpu = Section::new(&mut pc, cx, y, cw, "GPU");
+    if !state.loaded {
+        sec_gpu.text(&mut pc, "Loading GPU models...", 12.0, 0.0, 12.0, TEXT_FG);
+        sec_gpu.spacing(10.0);
+    } else {
+        for (i, gpu_lbl) in state.gpu_labels.iter_mut().enumerate() {
+            if i > 0 { sec_gpu.spacing(12.0); }
+            sec_gpu.widget(&mut pc, gpu_lbl, 12.0, cw - 24.0, 26.0);
+        }
     }
-    sec.finish(&mut pc);
+    sec_gpu.finish(&mut pc);
 
     pc
 }
 
 pub fn update(state: &mut ProcessorsState, msg: ProcessorsMessage) {
     match msg {
-        ProcessorsMessage::Refreshed(new) => { *state = new; }
+        ProcessorsMessage::Refreshed(new) => {
+            state.loaded = new.loaded;
+            state.cpu_model = new.cpu_model;
+            state.cpu_usage = new.cpu_usage;
+            state.cpu_cores = new.cpu_cores;
+            state.gpus = new.gpus;
+            state.cpu_label = new.cpu_label;
+            state.gpu_labels = new.gpu_labels;
+            state.processes = new.processes;
+            let old_scroll = state.cpu_list_box.scroll_y;
+            state.cpu_list_box = new.cpu_list_box;
+            state.cpu_list_box.scroll_y = old_scroll;
+        }
+        ProcessorsMessage::None => {}
     }
 }
diff --git a/src/pages/typeface.rs b/src/pages/typeface.rs
index ad6b5c6..0696163 100644
--- a/src/pages/typeface.rs
+++ b/src/pages/typeface.rs
@@ -1,9 +1,8 @@
 use std::fs;
 use crate::app::PageContent;
 use clear_ui::layout::Section;
-use clear_ui::widget::{Widget, TextLabel};
-use winit::event::{ElementState, KeyEvent, MouseButton};
-use winit::keyboard::{Key, NamedKey};
+use clear_ui::widget::{Widget, TextLabel, ScrollBox};
+use clear_ui::widget::{ElementState, KeyEvent, MouseButton, Key, NamedKey};
 
 const FONTS_CONF_PATH: &str = "/home/lsgalante/.config/fontconfig/fonts.conf";
 
@@ -17,6 +16,9 @@ pub struct TextBox {
     pub edit_buffer: String,
     hovered: bool,
     just_changed: bool,
+    label: Option<String>,
+    row_x: f32,
+    row_w: f32,
 }
 
 impl TextBox {
@@ -28,9 +30,21 @@ impl TextBox {
             edit_buffer: String::new(),
             hovered: false,
             just_changed: false,
+            label: None,
+            row_x: 0.0,
+            row_w: 0.0,
         }
     }
 
+    pub fn with_label(mut self, label: &str) -> Self {
+        self.label = Some(label.to_string());
+        self
+    }
+
+    pub fn set_label(&mut self, label: &str) {
+        self.label = Some(label.to_string());
+    }
+
     pub fn take_change(&mut self) -> bool {
         let changed = self.just_changed;
         self.just_changed = false;
@@ -47,6 +61,7 @@ impl Default for TextBox {
 impl Widget for TextBox {
     fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
     fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+    fn set_row_rect(&mut self, x: f32, w: f32) { self.row_x = x; self.row_w = w; }
     fn set_hovered(&mut self, v: bool) { self.hovered = v; }
     fn hovered(&self) -> bool { self.hovered }
 
@@ -54,6 +69,20 @@ impl Widget for TextBox {
         [0.10, 0.10, 0.16, 1.0]
     }
 
+    fn hit_test(&self, px: f32, py: f32) -> bool {
+        let (x, y, w, h) = self.rect();
+        let hx = if self.row_w > 0.0 { self.row_x } else { x };
+        let hw = if self.row_w > 0.0 { self.row_w } else { w };
+        let (hy, hh) = if self.label.is_some() {
+            (y - 18.0, h + 18.0)
+        } else {
+            (y, h)
+        };
+        px >= hx && px <= hx + hw && py >= hy && py <= hy + hh
+    }
+
+    fn top_room(&self) -> f32 { if self.label.is_some() { 18.0 } else { 0.0 } }
+
     fn cursor_moved(&mut self, px: f32, py: f32) -> bool {
         let was = self.hovered;
         self.hovered = self.hit_test(px, py);
@@ -119,16 +148,21 @@ impl Widget for TextBox {
     }
 
     fn hover_highlight(&self) -> Option<[f32; 4]> {
-        if self.editing {
-            Some([1.0, 1.0, 1.0, 0.12])
-        } else if self.hovered {
-            Some([1.0, 1.0, 1.0, 0.06])
-        } else {
-            None
-        }
+        None
     }
 
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        let mut quads = Vec::new();
+        if self.hovered {
+            let (hy, hh) = if self.label.is_some() {
+                (self.y - 18.0, self.h + 18.0)
+            } else {
+                (self.y, self.h)
+            };
+            let hx = if self.row_w > 0.0 { self.row_x } else { self.x };
+            let hw = if self.row_w > 0.0 { self.row_w } else { self.w };
+            quads.push((hx, hy, hw, hh, [1.0, 1.0, 1.0, 0.06]));
+        }
         let bg_color = if self.editing {
             [0.12, 0.12, 0.18, 1.0]
         } else {
@@ -141,25 +175,35 @@ impl Widget for TextBox {
         } else {
             [0.18, 0.18, 0.24, 1.0]
         };
-        vec![
-            (self.x, self.y, self.w, self.h, border_color),
-            (self.x + 1.0, self.y + 1.0, self.w - 2.0, self.h - 2.0, bg_color),
-        ]
+        quads.push((self.x, self.y, self.w, self.h, border_color));
+        quads.push((self.x + 1.0, self.y + 1.0, self.w - 2.0, self.h - 2.0, bg_color));
+        quads
     }
 
     fn text_labels(&self) -> Vec<TextLabel> {
+        let mut labels = Vec::new();
+        if let Some(ref label) = self.label {
+            labels.push(TextLabel {
+                text: label.clone(),
+                x: self.x + 4.0,
+                y: self.y - 14.0,
+                font_size: 12.0,
+                color: [0x83, 0x83, 0x8a],
+            });
+        }
         let val_text = if self.editing {
             format!("{}|", self.edit_buffer)
         } else {
             self.text.clone()
         };
-        vec![TextLabel {
+        labels.push(TextLabel {
             text: val_text,
             x: self.x + 8.0,
             y: self.y + (self.h - 12.0) / 2.0,
             font_size: 13.0,
             color: if self.editing { [0xee, 0xee, 0xf5] } else { [0xcc, 0xcc, 0xd4] },
-        }]
+        });
+        labels
     }
 }
 
@@ -171,11 +215,22 @@ pub struct TypefaceState {
     pub sans_serif: String,
     pub serif: String,
     pub monospace: String,
+    pub window_borders: String,
+    pub status_interface: String,
+    pub fuzzel: String,
+    pub terminal: String,
     pub all_fonts: Vec<String>,
     pub mono_fonts: Vec<String>,
     pub sans_box: TextBox,
     pub serif_box: TextBox,
     pub mono_box: TextBox,
+    pub borders_box: TextBox,
+    pub status_box: TextBox,
+    pub fuzzel_box: TextBox,
+    pub terminal_box: TextBox,
+    pub search_box: TextBox,
+    pub selected_font: Option<String>,
+    pub list_box: ScrollBox,
 }
 
 impl Default for TypefaceState {
@@ -185,11 +240,22 @@ impl Default for TypefaceState {
             sans_serif: String::new(),
             serif: String::new(),
             monospace: String::new(),
+            window_borders: String::new(),
+            status_interface: String::new(),
+            fuzzel: String::new(),
+            terminal: String::new(),
             all_fonts: Vec::new(),
             mono_fonts: Vec::new(),
             sans_box: TextBox::default(),
             serif_box: TextBox::default(),
             mono_box: TextBox::default(),
+            borders_box: TextBox::default(),
+            status_box: TextBox::default(),
+            fuzzel_box: TextBox::default(),
+            terminal_box: TextBox::default(),
+            search_box: TextBox::default(),
+            selected_font: None,
+            list_box: ScrollBox::new(),
         }
     }
 }
@@ -200,6 +266,12 @@ pub enum TypefaceMessage {
     SetSans(String),
     SetSerif(String),
     SetMono(String),
+    SetBorders(String),
+    SetStatus(String),
+    SetFuzzel(String),
+    SetTerminal(String),
+    SetSearch(String),
+    SelectFont(String),
 }
 
 fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
@@ -228,17 +300,29 @@ fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
     None
 }
 
-pub fn read_preferred_fonts() -> (String, String, String) {
+pub fn read_preferred_fonts() -> (String, String, String, String, String, String, String) {
     let content = fs::read_to_string(FONTS_CONF_PATH).unwrap_or_default();
     
     let sans = parse_font_for_alias(&content, "sans-serif").unwrap_or_else(|| "Noto Sans".to_string());
     let serif = parse_font_for_alias(&content, "serif").unwrap_or_else(|| "Noto Serif".to_string());
     let mono = parse_font_for_alias(&content, "monospace").unwrap_or_else(|| "Noto Sans Mono".to_string());
+    let borders = parse_font_for_alias(&content, "window-borders").unwrap_or_else(|| "Noto Sans".to_string());
+    let status = parse_font_for_alias(&content, "status-interface").unwrap_or_else(|| "Noto Sans".to_string());
+    let fuzzel_font = parse_font_for_alias(&content, "fuzzel").unwrap_or_else(|| "Noto Sans".to_string());
+    let term = parse_font_for_alias(&content, "terminal").unwrap_or_else(|| "Noto Sans Mono".to_string());
     
-    (sans, serif, mono)
+    (sans, serif, mono, borders, status, fuzzel_font, term)
 }
 
-pub fn save_preferred_fonts(sans: &str, serif: &str, mono: &str) {
+pub fn save_preferred_fonts(
+    sans: &str,
+    serif: &str,
+    mono: &str,
+    borders: &str,
+    status: &str,
+    fuzzel: &str,
+    terminal: &str,
+) {
     let content = fs::read_to_string(FONTS_CONF_PATH).unwrap_or_default();
     
     let mut dirs = Vec::new();
@@ -284,6 +368,38 @@ pub fn save_preferred_fonts(sans: &str, serif: &str, mono: &str) {
     new_content.push_str(&format!("            <string>{}</string>\n", mono));
     new_content.push_str("        </edit>\n");
     new_content.push_str("    </match>\n");
+
+    // Window Borders
+    new_content.push_str("    <match target=\"pattern\">\n");
+    new_content.push_str("        <test qual=\"any\" name=\"family\"><string>window-borders</string></test>\n");
+    new_content.push_str("        <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+    new_content.push_str(&format!("            <string>{}</string>\n", borders));
+    new_content.push_str("        </edit>\n");
+    new_content.push_str("    </match>\n");
+
+    // Status Interface
+    new_content.push_str("    <match target=\"pattern\">\n");
+    new_content.push_str("        <test qual=\"any\" name=\"family\"><string>status-interface</string></test>\n");
+    new_content.push_str("        <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+    new_content.push_str(&format!("            <string>{}</string>\n", status));
+    new_content.push_str("        </edit>\n");
+    new_content.push_str("    </match>\n");
+
+    // Fuzzel
+    new_content.push_str("    <match target=\"pattern\">\n");
+    new_content.push_str("        <test qual=\"any\" name=\"family\"><string>fuzzel</string></test>\n");
+    new_content.push_str("        <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+    new_content.push_str(&format!("            <string>{}</string>\n", fuzzel));
+    new_content.push_str("        </edit>\n");
+    new_content.push_str("    </match>\n");
+
+    // Terminal
+    new_content.push_str("    <match target=\"pattern\">\n");
+    new_content.push_str("        <test qual=\"any\" name=\"family\"><string>terminal</string></test>\n");
+    new_content.push_str("        <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+    new_content.push_str(&format!("            <string>{}</string>\n", terminal));
+    new_content.push_str("        </edit>\n");
+    new_content.push_str("    </match>\n");
     
     new_content.push_str("</fontconfig>\n");
     
@@ -316,7 +432,7 @@ fn parse_families(output: Option<std::process::Output>) -> Vec<String> {
 }
 
 pub async fn fetch_typeface_state() -> TypefaceState {
-    let (sans, serif, mono) = read_preferred_fonts();
+    let (sans, serif, mono, borders, status, fuzzel_font, term) = read_preferred_fonts();
     
     let all_output = tokio::process::Command::new("fc-list")
         .args([":", "family"])
@@ -328,16 +444,28 @@ pub async fn fetch_typeface_state() -> TypefaceState {
         .output().await.ok();
     let mono_fonts = parse_families(mono_output);
     
+    let selected_font = all_fonts.first().cloned();
     TypefaceState {
         loaded: true,
         sans_serif: sans.clone(),
         serif: serif.clone(),
         monospace: mono.clone(),
+        window_borders: borders.clone(),
+        status_interface: status.clone(),
+        fuzzel: fuzzel_font.clone(),
+        terminal: term.clone(),
         all_fonts,
         mono_fonts,
-        sans_box: TextBox::new(sans),
-        serif_box: TextBox::new(serif),
-        mono_box: TextBox::new(mono),
+        sans_box: TextBox::new(sans).with_label("Sans-Serif"),
+        serif_box: TextBox::new(serif).with_label("Serif"),
+        mono_box: TextBox::new(mono).with_label("Monospace"),
+        borders_box: TextBox::new(borders).with_label("Window Borders"),
+        status_box: TextBox::new(status).with_label("Status Interface"),
+        fuzzel_box: TextBox::new(fuzzel_font).with_label("Fuzzel"),
+        terminal_box: TextBox::new(term).with_label("Terminal"),
+        search_box: TextBox::new(String::new()).with_label("Filter Fonts"),
+        selected_font,
+        list_box: ScrollBox::new(),
     }
 }
 
@@ -350,42 +478,210 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
     let widget_w = cw - 24.0;
     let widget_h = 26.0;
 
-    // ── Sans-Serif ──
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Sans-Serif");
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Typeface Settings");
     sec.spacing(8.0);
+
     if !state.loaded {
         sec.text(&mut pc, "Loading typefaces...", 12.0, 0.0, 12.0, TEXT_DIM);
         sec.spacing(18.0);
     } else {
+        // Sans-Serif
         sec.widget(&mut pc, &mut state.sans_box, 12.0, widget_w, widget_h);
-        sec.spacing(8.0);
-    }
-    y = sec.finish(&mut pc);
+        sec.spacing(12.0);
 
-    // ── Serif ──
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Serif");
-    sec.spacing(8.0);
-    if !state.loaded {
-        sec.text(&mut pc, "Loading typefaces...", 12.0, 0.0, 12.0, TEXT_DIM);
-        sec.spacing(18.0);
-    } else {
+        // Serif
         sec.widget(&mut pc, &mut state.serif_box, 12.0, widget_w, widget_h);
+        sec.spacing(12.0);
+
+        // Monospace
+        sec.widget(&mut pc, &mut state.mono_box, 12.0, widget_w, widget_h);
+        sec.spacing(12.0);
+
+        // Window Borders
+        sec.widget(&mut pc, &mut state.borders_box, 12.0, widget_w, widget_h);
+        sec.spacing(12.0);
+
+        // Status Interface
+        sec.widget(&mut pc, &mut state.status_box, 12.0, widget_w, widget_h);
+        sec.spacing(12.0);
+
+        // Fuzzel
+        sec.widget(&mut pc, &mut state.fuzzel_box, 12.0, widget_w, widget_h);
+        sec.spacing(12.0);
+
+        // Terminal
+        sec.widget(&mut pc, &mut state.terminal_box, 12.0, widget_w, widget_h);
         sec.spacing(8.0);
     }
     y = sec.finish(&mut pc);
 
-    // ── Monospace ──
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Monospace");
-    sec.spacing(8.0);
+    // ── Typefaces Section (List & Preview) ──
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Typefaces");
+    sec.spacing(12.0);
+
     if !state.loaded {
-        sec.text(&mut pc, "Loading typefaces...", 12.0, 0.0, 12.0, TEXT_DIM);
+        sec.text(&mut pc, "Loading installed fonts...", 12.0, 0.0, 12.0, TEXT_DIM);
         sec.spacing(18.0);
     } else {
-        sec.widget(&mut pc, &mut state.mono_box, 12.0, widget_w, widget_h);
-        sec.spacing(8.0);
+        let start_y = sec.ay();
+        let usable_w = cw - 24.0;
+        let gap = 24.0;
+        let left_w = (usable_w - gap) * 0.40;
+        let right_w = (usable_w - gap) * 0.60;
+        let left_x = cx + 12.0;
+        let right_x = left_x + left_w + gap;
+
+        // 1. Render Left Column (Search + List Box)
+        let mut left_y = start_y;
+        
+        let top_room = state.search_box.top_room();
+        let search_box_h = widget_h + top_room;
+        state.search_box.set_row_rect(left_x, left_w);
+        clear_ui::layout::render_widget(
+            &mut pc,
+            &mut state.search_box,
+            left_x,
+            left_y + top_room,
+            left_w,
+            widget_h,
+        );
+        left_y += search_box_h + 12.0;
+
+        // Scrolling box configuration
+        let list_box_y = left_y;
+        let list_box_h = 320.0;
+        
+        // Render the standardized ScrollBox widget
+        clear_ui::layout::render_widget(&mut pc, &mut state.list_box, left_x, list_box_y, left_w, list_box_h);
+
+        let query = state.search_box.text.to_lowercase();
+        let matching_fonts: Vec<&String> = state.all_fonts.iter()
+            .filter(|font| font.to_lowercase().contains(&query))
+            .collect();
+
+        let btn_h = 24.0;
+        let btn_gap = 4.0;
+        let inner_x = left_x + 4.0;
+        let inner_w = left_w - 16.0; // leave room for scrollbar
+        let item_height_full = btn_h + btn_gap;
+        let content_h = matching_fonts.len() as f32 * item_height_full;
+        
+        // Update ScrollBox bounds to clamp and render correctly
+        state.list_box.update_bounds(content_h, list_box_y, list_box_h);
+
+        // Render visible buttons inside scroll region
+        
+        for (idx, font_name) in matching_fonts.iter().enumerate() {
+            let virtual_y = idx as f32 * item_height_full;
+            
+            // Only render buttons that are completely within the visible area
+            if let Some(draw_y) = state.list_box.get_item_draw_y(virtual_y, btn_h) {
+                let is_selected = state.selected_font.as_ref() == Some(*font_name);
+                
+                let (bg, hover_bg, text_color) = if is_selected {
+                    ([0.20, 0.40, 0.65, 0.4], [0.30, 0.52, 0.78, 0.6], [0.90, 0.90, 0.95, 1.0])
+                } else {
+                    ([0.0, 0.0, 0.0, 0.0], [0.20, 0.20, 0.25, 0.15], [0.70, 0.70, 0.75, 1.0])
+                };
+                
+                pc.button(
+                    font_name,
+                    inner_x,
+                    draw_y,
+                    inner_w,
+                    btn_h,
+                    bg,
+                    hover_bg,
+                    text_color,
+                    crate::app::AppAction::Typeface(TypefaceMessage::SelectFont((*font_name).clone())),
+                );
+            }
+        }
+        
+        if matching_fonts.is_empty() {
+            pc.text("No fonts match query", inner_x + 8.0, list_box_y + 16.0, 12.0, TEXT_DIM);
+        }
+
+        left_y += list_box_h;
+
+        // 2. Render Right Column (Live Preview)
+        let mut right_y = start_y;
+        
+        if let Some(ref font_name) = state.selected_font {
+            let card_h = 240.0;
+            pc.rect([0.10, 0.10, 0.14, 0.3], right_x, right_y, right_w, card_h);
+            pc.rect([0.25, 0.25, 0.35, 0.5], right_x, right_y, right_w, 1.0);
+            pc.rect([0.25, 0.25, 0.35, 0.5], right_x, right_y + card_h - 1.0, right_w, 1.0);
+            pc.rect([0.25, 0.25, 0.35, 0.5], right_x, right_y, 1.0, card_h);
+            pc.rect([0.25, 0.25, 0.35, 0.5], right_x + right_w - 1.0, right_y, 1.0, card_h);
+            
+            let text_padding_x = 16.0;
+            let mut text_y = right_y + 16.0;
+            
+            pc.text(&format!("Family: {}", font_name), right_x + text_padding_x, text_y, 15.0, [0.90, 0.90, 0.95, 1.0]);
+            text_y += 28.0;
+            
+            pc.rect([0.22, 0.22, 0.30, 0.8], right_x + text_padding_x, text_y, right_w - (text_padding_x * 2.0), 1.0);
+            text_y += 16.0;
+            
+            pc.text_with_font(
+                "abcdefghijklmnopqrstuvwxyz",
+                right_x + text_padding_x,
+                text_y,
+                13.0,
+                [0.75, 0.75, 0.80, 1.0],
+                font_name,
+            );
+            text_y += 22.0;
+            
+            pc.text_with_font(
+                "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+                right_x + text_padding_x,
+                text_y,
+                13.0,
+                [0.75, 0.75, 0.80, 1.0],
+                font_name,
+            );
+            text_y += 22.0;
+            
+            pc.text_with_font(
+                "0123456789 (!@#$%&*?)",
+                right_x + text_padding_x,
+                text_y,
+                13.0,
+                [0.75, 0.75, 0.80, 1.0],
+                font_name,
+            );
+            text_y += 26.0;
+            
+            pc.text_with_font(
+                "The quick brown fox jumps over the lazy dog.",
+                right_x + text_padding_x,
+                text_y,
+                18.0,
+                [0.90, 0.90, 0.95, 1.0],
+                font_name,
+            );
+            text_y += 32.0;
+
+            pc.text_with_font(
+                "Pack my box with five dozen liquor jugs.",
+                right_x + text_padding_x,
+                text_y,
+                24.0,
+                [0.95, 0.95, 1.0, 1.0],
+                font_name,
+            );
+            
+            right_y += card_h;
+        } else {
+            pc.text("Select a font to preview", right_x + 12.0, right_y + 20.0, 13.0, TEXT_DIM);
+            right_y += 40.0;
+        }
+
+        sec.content_y = left_y.max(right_y);
     }
     sec.finish(&mut pc);
-
     pc
 }
 
@@ -395,33 +691,140 @@ pub fn update(state: &mut TypefaceState, msg: TypefaceMessage) {
             state.loaded = new.loaded;
             state.all_fonts = new.all_fonts;
             state.mono_fonts = new.mono_fonts;
+            if state.selected_font.is_none() {
+                state.selected_font = new.selected_font.clone();
+            }
             if !state.sans_box.editing {
                 state.sans_serif = new.sans_serif.clone();
-                state.sans_box.text = new.sans_serif;
+                state.sans_box = new.sans_box;
             }
             if !state.serif_box.editing {
                 state.serif = new.serif.clone();
-                state.serif_box.text = new.serif;
+                state.serif_box = new.serif_box;
             }
             if !state.mono_box.editing {
                 state.monospace = new.monospace.clone();
-                state.mono_box.text = new.monospace;
+                state.mono_box = new.mono_box;
+            }
+            if !state.borders_box.editing {
+                state.window_borders = new.window_borders.clone();
+                state.borders_box = new.borders_box;
+            }
+            if !state.status_box.editing {
+                state.status_interface = new.status_interface.clone();
+                state.status_box = new.status_box;
             }
+            if !state.fuzzel_box.editing {
+                state.fuzzel = new.fuzzel.clone();
+                state.fuzzel_box = new.fuzzel_box;
+            }
+            if !state.terminal_box.editing {
+                state.terminal = new.terminal.clone();
+                state.terminal_box = new.terminal_box;
+            }
+            if !state.search_box.editing {
+                state.search_box = new.search_box;
+            }
+            let old_scroll = state.list_box.scroll_y;
+            state.list_box = new.list_box;
+            state.list_box.scroll_y = old_scroll;
         }
         TypefaceMessage::SetSans(sans) => {
             state.sans_serif = sans.clone();
             state.sans_box.text = sans;
-            save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace);
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
         }
         TypefaceMessage::SetSerif(serif) => {
             state.serif = serif.clone();
             state.serif_box.text = serif;
-            save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace);
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
         }
         TypefaceMessage::SetMono(mono) => {
             state.monospace = mono.clone();
             state.mono_box.text = mono;
-            save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace);
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetBorders(borders) => {
+            state.window_borders = borders.clone();
+            state.borders_box.text = borders;
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetStatus(status) => {
+            state.status_interface = status.clone();
+            state.status_box.text = status;
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetFuzzel(fuzzel) => {
+            state.fuzzel = fuzzel.clone();
+            state.fuzzel_box.text = fuzzel;
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetTerminal(term) => {
+            state.terminal = term.clone();
+            state.terminal_box.text = term;
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetSearch(search) => {
+            state.search_box.text = search;
+        }
+        TypefaceMessage::SelectFont(font) => {
+            state.selected_font = Some(font);
         }
     }
 }