oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git

Zig port.

Commit
e95e4afb2dadb5a66fc604cf7485262a2a8d63b6
Parent
115d06e
Author
tonybanters <tonyoutoften@gmail.com>
Date
2026-02-02 07:14:48

Diff

diff --git a/.gitignore b/.gitignore
index 8bd4198..c558b7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,11 @@
-# Rust / Cargo
-target/
+# Zig
+zig-out/
+.zig-cache/
 .direnv/
 
 # Nix
 result
 
-# User config
-src/config.rs
-
 # Notes
 notes/
 *.md
diff --git a/Cargo.lock b/Cargo.lock
deleted file mode 100644
index c793f6d..0000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,730 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "android_system_properties"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "autocfg"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-
-[[package]]
-name = "bitflags"
-version = "2.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
-
-[[package]]
-name = "bstr"
-version = "1.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
-dependencies = [
- "memchr",
- "serde",
-]
-
-[[package]]
-name = "bumpalo"
-version = "3.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
-
-[[package]]
-name = "cc"
-version = "1.2.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
-dependencies = [
- "find-msvc-tools",
- "shlex",
-]
-
-[[package]]
-name = "cfg-if"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
-
-[[package]]
-name = "chrono"
-version = "0.4.42"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
-dependencies = [
- "iana-time-zone",
- "js-sys",
- "num-traits",
- "wasm-bindgen",
- "windows-link",
-]
-
-[[package]]
-name = "core-foundation-sys"
-version = "0.8.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-
-[[package]]
-name = "dirs"
-version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
-dependencies = [
- "dirs-sys",
-]
-
-[[package]]
-name = "dirs-sys"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
-dependencies = [
- "libc",
- "option-ext",
- "redox_users",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "either"
-version = "1.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
-
-[[package]]
-name = "env_home"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
-
-[[package]]
-name = "errno"
-version = "0.3.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
-dependencies = [
- "libc",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "find-msvc-tools"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
-
-[[package]]
-name = "gethostname"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
-dependencies = [
- "rustix",
- "windows-link",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "iana-time-zone"
-version = "0.1.64"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
-dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "js-sys"
-version = "0.3.81"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
-dependencies = [
- "once_cell",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.177"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
-
-[[package]]
-name = "libredox"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
-dependencies = [
- "bitflags",
- "libc",
-]
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
-
-[[package]]
-name = "lock_api"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
-dependencies = [
- "scopeguard",
-]
-
-[[package]]
-name = "log"
-version = "0.4.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
-
-[[package]]
-name = "lua-src"
-version = "547.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "luajit-src"
-version = "210.5.12+a4f56a4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671"
-dependencies = [
- "cc",
- "which",
-]
-
-[[package]]
-name = "memchr"
-version = "2.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
-
-[[package]]
-name = "mlua"
-version = "0.10.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0"
-dependencies = [
- "bstr",
- "either",
- "mlua-sys",
- "num-traits",
- "parking_lot",
- "rustc-hash",
- "rustversion",
-]
-
-[[package]]
-name = "mlua-sys"
-version = "0.6.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93"
-dependencies = [
- "cc",
- "cfg-if",
- "lua-src",
- "luajit-src",
- "pkg-config",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
-
-[[package]]
-name = "option-ext"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
-
-[[package]]
-name = "oxwm"
-version = "0.10.2"
-dependencies = [
- "chrono",
- "dirs",
- "mlua",
- "serde",
- "x11",
- "x11rb",
-]
-
-[[package]]
-name = "parking_lot"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-link",
-]
-
-[[package]]
-name = "pkg-config"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.101"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.5.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "redox_users"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
-dependencies = [
- "getrandom",
- "libredox",
- "thiserror",
-]
-
-[[package]]
-name = "rustc-hash"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
-
-[[package]]
-name = "rustix"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
-dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "rustversion"
-version = "1.0.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-
-[[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
-[[package]]
-name = "serde"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
-dependencies = [
- "serde_core",
- "serde_derive",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
-[[package]]
-name = "smallvec"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
-name = "syn"
-version = "2.0.107"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "thiserror"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
-
-[[package]]
-name = "wasi"
-version = "0.11.1+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
-dependencies = [
- "bumpalo",
- "log",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "which"
-version = "7.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
-dependencies = [
- "either",
- "env_home",
- "rustix",
- "winsafe",
-]
-
-[[package]]
-name = "windows-core"
-version = "0.62.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
-dependencies = [
- "windows-implement",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-implement"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-interface"
-version = "0.59.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-link"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-
-[[package]]
-name = "windows-result"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
-[[package]]
-name = "winsafe"
-version = "0.0.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
-
-[[package]]
-name = "x11"
-version = "2.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
-dependencies = [
- "libc",
- "pkg-config",
-]
-
-[[package]]
-name = "x11rb"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
-dependencies = [
- "gethostname",
- "rustix",
- "x11rb-protocol",
- "xcursor",
-]
-
-[[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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
diff --git a/Cargo.toml b/Cargo.toml
deleted file mode 100644
index 1453242..0000000
--- a/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-name = "oxwm"
-version = "0.10.2"
-edition = "2024"
-
-[lib]
-name = "oxwm"
-path = "src/lib.rs"
-
-[[bin]]
-name = "oxwm"
-path = "src/bin/main.rs"
-
-[dependencies]
-x11 = { version = "2.21", features = ["xlib", "xft"] }
-x11rb = { version = "0.13", features = ["cursor", "xinerama"] }
-chrono = "0.4"
-dirs = "5.0"
-serde = { version = "1.0", features = ["derive"] }
-mlua = { version = "0.10", features = ["lua54", "vendored"] }
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..493ee08
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,76 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+    const target = b.standardTargetOptions(.{});
+    const optimize = b.standardOptimizeOption(.{});
+
+    const exe = b.addExecutable(.{
+        .name = "oxwm",
+        .root_module = b.createModule(.{
+            .root_source_file = b.path("src/main.zig"),
+            .target = target,
+            .optimize = optimize,
+        }),
+    });
+
+    exe.linkSystemLibrary("lua");
+    exe.linkSystemLibrary("X11");
+    exe.linkSystemLibrary("Xinerama");
+    exe.linkSystemLibrary("Xft");
+    exe.linkSystemLibrary("fontconfig");
+    exe.linkLibC();
+
+    b.installArtifact(exe);
+
+    const run_step = b.step("run", "Run oxwm");
+    const run_cmd = b.addRunArtifact(exe);
+    run_step.dependOn(&run_cmd.step);
+    run_cmd.step.dependOn(b.getInstallStep());
+    if (b.args) |args| {
+        run_cmd.addArgs(args);
+    }
+
+    const test_step = b.step("test", "Run unit tests");
+    const unit_tests = b.addTest(.{
+        .root_module = b.createModule(.{
+            .root_source_file = b.path("tests/main_tests.zig"),
+            .target = target,
+            .optimize = optimize,
+        }),
+    });
+    test_step.dependOn(&b.addRunArtifact(unit_tests).step);
+
+    const xephyr_step = b.step("xephyr", "Run in Xephyr (1280x800 on :2)");
+    xephyr_step.dependOn(&add_xephyr_run(b, exe, false).step);
+
+    const xephyr_multi_step = b.step("xephyr-multi", "Run in Xephyr multi-monitor on :2");
+    xephyr_multi_step.dependOn(&add_xephyr_run(b, exe, true).step);
+
+    const multimon_step = b.step("multimon", "Alias for xephyr-multi");
+    multimon_step.dependOn(&add_xephyr_run(b, exe, true).step);
+
+    const kill_step = b.step("kill", "Kill Xephyr and oxwm");
+    kill_step.dependOn(&b.addSystemCommand(&.{ "sh", "-c", "pkill -9 Xephyr || true; pkill -9 oxwm || true" }).step);
+
+    const fmt_step = b.step("fmt", "Format source files");
+    fmt_step.dependOn(&b.addFmt(.{ .paths = &.{"src/"} }).step);
+
+    const clean_step = b.step("clean", "Remove build artifacts");
+    clean_step.dependOn(&b.addSystemCommand(&.{ "rm", "-rf", "zig-out", ".zig-cache" }).step);
+}
+
+fn add_xephyr_run(b: *std.Build, exe: *std.Build.Step.Compile, multimon: bool) *std.Build.Step.Run {
+    const kill_cmd = if (multimon)
+        "pkill -9 Xephyr || true; Xephyr +xinerama -glamor -screen 640x480 -screen 640x480 :2 & sleep 1"
+    else
+        "pkill -9 Xephyr || true; Xephyr -screen 1280x800 :2 & sleep 1";
+
+    const setup = b.addSystemCommand(&.{ "sh", "-c", kill_cmd });
+
+    const run_wm = b.addRunArtifact(exe);
+    run_wm.step.dependOn(&setup.step);
+    run_wm.setEnvironmentVariable("DISPLAY", ":2");
+    run_wm.addArgs(&.{ "-c", "resources/config.lua" });
+
+    return run_wm;
+}
diff --git a/default.nix b/default.nix
index 51969c2..46e2a2d 100644
--- a/default.nix
+++ b/default.nix
@@ -1,45 +1,54 @@
 {
   lib,
-  rustPlatform,
+  stdenv,
+  zig,
   pkg-config,
-  libX11,
-  libXft,
-  libXrender,
+  xorg,
+  lua5_4,
   freetype,
   fontconfig,
-  gitRev ? "unkown",
+  gitRev ? "unknown",
 }:
-rustPlatform.buildRustPackage (finalAttrs: {
+stdenv.mkDerivation (finalAttrs: {
   pname = "oxwm";
   version = "${lib.substring 0 8 gitRev}";
 
   src = ./.;
 
-  cargoLock.lockFile = ./Cargo.lock;
-
-  nativeBuildInputs = [pkg-config];
+  nativeBuildInputs = [zig pkg-config];
 
   buildInputs = [
-    libX11
-    libXft
-    libXrender
+    xorg.libX11
+    xorg.libXinerama
+    xorg.libXft
+    lua5_4
     freetype
     fontconfig
   ];
 
-  # tests require a running X server
-  doCheck = false;
+  dontConfigure = true;
+
+  buildPhase = ''
+    runHook preBuild
+    zig build -Doptimize=ReleaseSafe --prefix $out
+    runHook postBuild
+  '';
 
-  postInstall = ''
+  installPhase = ''
+    runHook preInstall
     install resources/oxwm.desktop -Dt $out/share/xsessions
     install -Dm644 resources/oxwm.1 -t $out/share/man/man1
     install -Dm644 templates/oxwm.lua -t $out/share/oxwm
+    runHook postInstall
   '';
 
+  # tests require a running X server
+  doCheck = false;
+
   passthru.providedSessions = ["oxwm"];
 
   meta = {
-    description = "Dynamic window manager written in Rust, inspired by dwm";
+    description = "Dynamic window manager written in Zig, inspired by dwm";
     homepage = "https://github.com/tonybanters/oxwm";
     license = lib.licenses.gpl3Only;
     platforms = lib.platforms.linux;
diff --git a/flake.nix b/flake.nix
index d266f94..39e64d0 100644
--- a/flake.nix
+++ b/flake.nix
@@ -23,9 +23,8 @@
       default = pkgs.mkShell {
         inputsFrom = [self.packages.${pkgs.stdenv.hostPlatform.system}.oxwm];
         packages = [
-          pkgs.rustc
-          pkgs.cargo
-          pkgs.clippy
+          pkgs.zig
+          pkgs.zls
           pkgs.alacritty
           pkgs.just
           pkgs.xorg.xorgserver
@@ -33,7 +32,6 @@
         shellHook = ''
           export PS1="(oxwm-dev) $PS1"
         '';
-        env.RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}";
       };
     });
 
diff --git a/justfile b/justfile
index 3befb6f..178a5c1 100644
--- a/justfile
+++ b/justfile
@@ -1,52 +1,43 @@
 build:
-    cargo build --release
+    zig build -Doptimize=ReleaseSafe
 
 install: build
-    cp target/release/oxwm /usr/bin/oxwm
-    cp resources/oxwm.desktop /usr/share/xsessions/oxwm.desktop
-    chmod +x /usr/bin/oxwm
-    @echo "✓ oxwm installed to /usr/bin/oxwm"
-    @echo "  Run 'oxwm --init' to create your config"
+    sudo cp zig-out/bin/oxwm /usr/bin/oxwm
+    sudo cp resources/oxwm.desktop /usr/share/xsessions/oxwm.desktop
+    sudo chmod +x /usr/bin/oxwm
+    @echo "oxwm installed to /usr/bin/oxwm"
 
 checkinstall:
     checkinstall --pkgname oxwm --exclude /root -y just install
 
 uninstall:
-    rm -f /usr/bin/oxwm
-    @echo "✓ oxwm uninstalled"
-    @echo "  Your config at ~/.config/oxwm/ is preserved"
+    sudo rm -f /usr/bin/oxwm
+    @echo "oxwm uninstalled"
+    @echo "Your config at ~/.config/oxwm/ is preserved"
 
 clean:
-    cargo clean
+    rm -rf zig-out .zig-cache
 
 test-clean:
     pkill Xephyr || true
     rm -rf ~/.config/oxwm
     Xephyr -screen 1280x800 :1 & sleep 1
-    DISPLAY=:1 cargo run --release -- --config resources/test-config.lua
+    DISPLAY=:1 zig build run -- -c resources/config.lua
 
 test:
-    pkill Xephyr || true
-    Xephyr -screen 1280x800 :2 & sleep 1
-    DISPLAY=:2 cargo run --release -- --config resources/test-config.lua
+    zig build xephyr
 
 test-multimon:
-    pkill Xephyr || true
-    Xephyr +xinerama -screen 640x480 -screen 640x480 :1 & sleep 1
-    DISPLAY=:1 cargo run --release -- --config resources/test-config.lua
-
-init:
-    cargo run --release -- --init
+    zig build xephyr-multi
 
 edit:
     $EDITOR ~/.config/oxwm/config.lua
 
-check:
-    cargo clippy -- -W clippy::all
-    cargo fmt -- --check
-
 fmt:
-    cargo fmt
+    zig build fmt
+
+pre-commit: fmt build
+    @echo "All checks passed!"
 
-pre-commit: fmt check build
-    @echo "✓ All checks passed!"
+run:
+    zig build run
diff --git a/src/animations.zig b/src/animations.zig
new file mode 100644
index 0000000..383be90
--- /dev/null
+++ b/src/animations.zig
@@ -0,0 +1,76 @@
+const std = @import("std");
+
+pub const Easing = enum {
+    linear,
+    ease_out,
+    ease_in_out,
+
+    pub fn apply(self: Easing, t: f64) f64 {
+        return switch (self) {
+            .linear => t,
+            .ease_out => 1.0 - std.math.pow(f64, 1.0 - t, 3),
+            .ease_in_out => if (t < 0.5)
+                4.0 * t * t * t
+            else
+                1.0 - std.math.pow(f64, -2.0 * t + 2.0, 3) / 2.0,
+        };
+    }
+};
+
+pub const Animation_Config = struct {
+    duration_ms: u64 = 150,
+    easing: Easing = .ease_out,
+};
+
+pub const Scroll_Animation = struct {
+    start_value: i32 = 0,
+    end_value: i32 = 0,
+    start_time: i64 = 0,
+    duration_ms: u64 = 150,
+    easing: Easing = .ease_out,
+    active: bool = false,
+
+    pub fn start(self: *Scroll_Animation, from: i32, to: i32, config: Animation_Config) void {
+        if (from == to) {
+            self.active = false;
+            return;
+        }
+        self.start_value = from;
+        self.end_value = to;
+        self.start_time = std.time.milliTimestamp();
+        self.duration_ms = config.duration_ms;
+        self.easing = config.easing;
+        self.active = true;
+    }
+
+    pub fn update(self: *Scroll_Animation) ?i32 {
+        if (!self.active) return null;
+
+        const now = std.time.milliTimestamp();
+        const elapsed = now - self.start_time;
+
+        if (elapsed >= @as(i64, @intCast(self.duration_ms))) {
+            self.active = false;
+            return self.end_value;
+        }
+
+        const t = @as(f64, @floatFromInt(elapsed)) / @as(f64, @floatFromInt(self.duration_ms));
+        const eased = self.easing.apply(t);
+        const diff = @as(f64, @floatFromInt(self.end_value - self.start_value));
+        const current = @as(f64, @floatFromInt(self.start_value)) + (diff * eased);
+
+        return @intFromFloat(current);
+    }
+
+    pub fn is_active(self: *const Scroll_Animation) bool {
+        return self.active;
+    }
+
+    pub fn target(self: *const Scroll_Animation) i32 {
+        return self.end_value;
+    }
+
+    pub fn stop(self: *Scroll_Animation) void {
+        self.active = false;
+    }
+};
diff --git a/src/animations/mod.rs b/src/animations/mod.rs
deleted file mode 100644
index 4b9ae69..0000000
--- a/src/animations/mod.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-mod scroll;
-
-pub use scroll::ScrollAnimation;
-
-use std::time::Duration;
-
-#[derive(Debug, Clone, Copy)]
-pub enum Easing {
-    Linear,
-    EaseOut,
-    EaseInOut,
-}
-
-impl Easing {
-    pub fn apply(&self, t: f64) -> f64 {
-        match self {
-            Easing::Linear => t,
-            Easing::EaseOut => 1.0 - (1.0 - t).powi(3),
-            Easing::EaseInOut => {
-                if t < 0.5 {
-                    4.0 * t * t * t
-                } else {
-                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
-                }
-            }
-        }
-    }
-}
-
-pub struct AnimationConfig {
-    pub duration: Duration,
-    pub easing: Easing,
-}
-
-impl Default for AnimationConfig {
-    fn default() -> Self {
-        Self {
-            duration: Duration::from_millis(150),
-            easing: Easing::EaseOut,
-        }
-    }
-}
diff --git a/src/animations/scroll.rs b/src/animations/scroll.rs
deleted file mode 100644
index 808b0eb..0000000
--- a/src/animations/scroll.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use std::time::Instant;
-use super::{AnimationConfig, Easing};
-
-pub struct ScrollAnimation {
-    start_value: i32,
-    end_value: i32,
-    start_time: Instant,
-    duration_ms: u64,
-    easing: Easing,
-    active: bool,
-}
-
-impl ScrollAnimation {
-    pub fn new() -> Self {
-        Self {
-            start_value: 0,
-            end_value: 0,
-            start_time: Instant::now(),
-            duration_ms: 150,
-            easing: Easing::EaseOut,
-            active: false,
-        }
-    }
-
-    pub fn start(&mut self, from: i32, to: i32, config: &AnimationConfig) {
-        if from == to {
-            self.active = false;
-            return;
-        }
-        self.start_value = from;
-        self.end_value = to;
-        self.start_time = Instant::now();
-        self.duration_ms = config.duration.as_millis() as u64;
-        self.easing = config.easing;
-        self.active = true;
-    }
-
-    pub fn update(&mut self) -> Option<i32> {
-        if !self.active {
-            return None;
-        }
-
-        let elapsed = self.start_time.elapsed().as_millis() as u64;
-
-        if elapsed >= self.duration_ms {
-            self.active = false;
-            return Some(self.end_value);
-        }
-
-        let t = elapsed as f64 / self.duration_ms as f64;
-        let eased_t = self.easing.apply(t);
-
-        let delta = (self.end_value - self.start_value) as f64;
-        let current = self.start_value as f64 + delta * eased_t;
-
-        Some(current.round() as i32)
-    }
-
-    pub fn is_active(&self) -> bool {
-        self.active
-    }
-
-    pub fn target(&self) -> i32 {
-        self.end_value
-    }
-
-    pub fn cancel(&mut self) {
-        self.active = false;
-    }
-}
-
-impl Default for ScrollAnimation {
-    fn default() -> Self {
-        Self::new()
-    }
-}
diff --git a/src/bar/bar.rs b/src/bar/bar.rs
deleted file mode 100644
index 9f74896..0000000
--- a/src/bar/bar.rs
+++ /dev/null
@@ -1,453 +0,0 @@
-use super::blocks::Block;
-use super::font::{DrawingSurface, Font};
-use crate::Config;
-use crate::errors::X11Error;
-use std::time::Instant;
-use x11rb::COPY_DEPTH_FROM_PARENT;
-use x11rb::connection::Connection;
-use x11rb::protocol::xproto::*;
-use x11rb::rust_connection::RustConnection;
-
-pub struct Bar {
-    window: Window,
-    width: u16,
-    height: u16,
-    graphics_context: Gcontext,
-    surface: DrawingSurface,
-
-    tag_widths: Vec<u16>,
-    needs_redraw: bool,
-
-    blocks: Vec<Box<dyn Block>>,
-    block_last_updates: Vec<Instant>,
-    block_underlines: Vec<bool>,
-    status_text: String,
-
-    tags: Vec<String>,
-    scheme_normal: crate::ColorScheme,
-    scheme_occupied: crate::ColorScheme,
-    scheme_selected: crate::ColorScheme,
-    scheme_urgent: crate::ColorScheme,
-    hide_vacant_tags: bool,
-    last_occupied_tags: u32,
-    last_current_tags: u32,
-}
-
-impl Bar {
-    pub fn new(
-        connection: &RustConnection,
-        screen: &Screen,
-        screen_num: usize,
-        config: &Config,
-        display: *mut x11::xlib::Display,
-        font: &Font,
-        x: i16,
-        y: i16,
-        width: u16,
-        cursor: u32,
-    ) -> Result<Self, X11Error> {
-        let window = connection.generate_id()?;
-        let graphics_context = connection.generate_id()?;
-
-        let height = (font.height() as f32 * 1.4) as u16;
-
-        connection.create_window(
-            COPY_DEPTH_FROM_PARENT,
-            window,
-            screen.root,
-            x,
-            y,
-            width,
-            height,
-            0,
-            WindowClass::INPUT_OUTPUT,
-            screen.root_visual,
-            &CreateWindowAux::new()
-                .background_pixel(config.scheme_normal.background)
-                .event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS)
-                .override_redirect(1),
-        )?;
-
-        connection.create_gc(
-            graphics_context,
-            window,
-            &CreateGCAux::new()
-                .foreground(config.scheme_normal.foreground)
-                .background(config.scheme_normal.background),
-        )?;
-
-        unsafe {
-            x11::xlib::XDefineCursor(display, window as u64, cursor as u64);
-        }
-
-        connection.map_window(window)?;
-        connection.flush()?;
-
-        let visual = unsafe { x11::xlib::XDefaultVisual(display, screen_num as i32) };
-        let colormap = unsafe { x11::xlib::XDefaultColormap(display, screen_num as i32) };
-
-        let surface = DrawingSurface::new(
-            display,
-            window as x11::xlib::Drawable,
-            width as u32,
-            height as u32,
-            visual,
-            colormap,
-        )?;
-
-        let horizontal_padding = (font.height() as f32 * 0.4) as u16;
-
-        let tag_widths = config
-            .tags
-            .iter()
-            .map(|tag| {
-                let text_width = font.text_width(tag);
-                text_width + (horizontal_padding * 2)
-            })
-            .collect();
-
-        let blocks: Vec<Box<dyn Block>> = config
-            .status_blocks
-            .iter()
-            .map(|block_config| block_config.to_block())
-            .collect();
-
-        let block_underlines: Vec<bool> = config
-            .status_blocks
-            .iter()
-            .map(|block_config| block_config.underline)
-            .collect();
-
-        let block_last_updates = vec![Instant::now(); blocks.len()];
-
-        Ok(Bar {
-            window,
-            width,
-            height,
-            graphics_context,
-            surface,
-            tag_widths,
-            needs_redraw: true,
-            blocks,
-            block_last_updates,
-            block_underlines,
-            status_text: String::new(),
-            tags: config.tags.clone(),
-            scheme_normal: config.scheme_normal,
-            scheme_occupied: config.scheme_occupied,
-            scheme_selected: config.scheme_selected,
-            scheme_urgent: config.scheme_urgent,
-            hide_vacant_tags: config.hide_vacant_tags,
-            last_occupied_tags: 0,
-            last_current_tags: 0,
-        })
-    }
-
-    pub fn window(&self) -> Window {
-        self.window
-    }
-
-    pub fn height(&self) -> u16 {
-        self.height
-    }
-
-    pub fn invalidate(&mut self) {
-        self.needs_redraw = true;
-    }
-
-    pub fn update_blocks(&mut self) {
-        let now = Instant::now();
-        let mut changed = false;
-
-        for (i, block) in self.blocks.iter_mut().enumerate() {
-            let elapsed = now.duration_since(self.block_last_updates[i]);
-
-            if elapsed >= block.interval() && block.content().is_ok() {
-                self.block_last_updates[i] = now;
-                changed = true;
-            }
-        }
-
-        if changed {
-            let mut parts = Vec::new();
-            for block in &mut self.blocks {
-                if let Ok(text) = block.content() {
-                    parts.push(text);
-                }
-            }
-            self.status_text = parts.join("");
-            self.needs_redraw = true;
-        }
-    }
-
-    pub fn draw(
-        &mut self,
-        connection: &RustConnection,
-        font: &Font,
-        display: *mut x11::xlib::Display,
-        current_tags: u32,
-        occupied_tags: u32,
-        urgent_tags: u32,
-        draw_blocks: bool,
-        layout_symbol: &str,
-        keychord_indicator: Option<&str>,
-    ) -> Result<(), X11Error> {
-        if !self.needs_redraw {
-            return Ok(());
-        }
-
-        connection.change_gc(
-            self.graphics_context,
-            &ChangeGCAux::new().foreground(self.scheme_normal.background),
-        )?;
-        connection.flush()?;
-
-        unsafe {
-            let gc = x11::xlib::XCreateGC(display, self.surface.pixmap(), 0, std::ptr::null_mut());
-            x11::xlib::XSetForeground(display, gc, self.scheme_normal.background as u64);
-            x11::xlib::XFillRectangle(
-                display,
-                self.surface.pixmap(),
-                gc,
-                0,
-                0,
-                self.width as u32,
-                self.height as u32,
-            );
-            x11::xlib::XFreeGC(display, gc);
-        }
-
-        self.last_occupied_tags = occupied_tags;
-        self.last_current_tags = current_tags;
-
-        let mut x_position: i16 = 0;
-
-        for (tag_index, tag) in self.tags.iter().enumerate() {
-            let tag_mask = 1 << tag_index;
-            let is_selected = (current_tags & tag_mask) != 0;
-            let is_occupied = (occupied_tags & tag_mask) != 0;
-            let is_urgent = (urgent_tags & tag_mask) != 0;
-
-            if self.hide_vacant_tags && !is_occupied && !is_selected {
-                continue;
-            }
-
-            let tag_width = self.tag_widths[tag_index];
-
-            let scheme = if is_selected {
-                &self.scheme_selected
-            } else if is_urgent {
-                &self.scheme_urgent
-            } else if is_occupied {
-                &self.scheme_occupied
-            } else {
-                &self.scheme_normal
-            };
-
-            let text_width = font.text_width(tag);
-            let text_x = x_position + ((tag_width - text_width) / 2) as i16;
-
-            let top_padding = 4;
-            let text_y = top_padding + font.ascent();
-
-            self.surface
-                .font_draw()
-                .draw_text(font, scheme.foreground, text_x, text_y, tag);
-
-            if is_selected || is_urgent {
-                let font_height = font.height();
-                let underline_height = font_height / 8;
-                let bottom_gap = 3;
-                let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
-
-                let underline_padding = 4;
-                let underline_width = tag_width - underline_padding;
-                let underline_x = x_position + (underline_padding / 2) as i16;
-
-                unsafe {
-                    let gc = x11::xlib::XCreateGC(
-                        display,
-                        self.surface.pixmap(),
-                        0,
-                        std::ptr::null_mut(),
-                    );
-                    x11::xlib::XSetForeground(display, gc, scheme.underline as u64);
-                    x11::xlib::XFillRectangle(
-                        display,
-                        self.surface.pixmap(),
-                        gc,
-                        underline_x as i32,
-                        underline_y as i32,
-                        underline_width as u32,
-                        underline_height as u32,
-                    );
-                    x11::xlib::XFreeGC(display, gc);
-                }
-            }
-
-            x_position += tag_width as i16;
-        }
-
-        x_position += 10;
-
-        let text_x = x_position;
-        let top_padding = 4;
-        let text_y = top_padding + font.ascent();
-
-        self.surface.font_draw().draw_text(
-            font,
-            self.scheme_normal.foreground,
-            text_x,
-            text_y,
-            layout_symbol,
-        );
-
-        x_position += font.text_width(layout_symbol) as i16;
-
-        if let Some(indicator) = keychord_indicator {
-            x_position += 10;
-
-            let text_x = x_position;
-            let text_y = top_padding + font.ascent();
-
-            self.surface.font_draw().draw_text(
-                font,
-                self.scheme_selected.foreground,
-                text_x,
-                text_y,
-                indicator,
-            );
-        }
-
-        if draw_blocks && !self.status_text.is_empty() {
-            let padding = 10;
-            let mut x_position = self.width as i16 - padding;
-
-            for (i, block) in self.blocks.iter_mut().enumerate().rev() {
-                if let Ok(text) = block.content() {
-                    let text_width = font.text_width(&text);
-                    x_position -= text_width as i16;
-
-                    let top_padding = 4;
-                    let text_y = top_padding + font.ascent();
-
-                    self.surface.font_draw().draw_text(
-                        font,
-                        block.color(),
-                        x_position,
-                        text_y,
-                        &text,
-                    );
-
-                    if self.block_underlines[i] {
-                        let font_height = font.height();
-                        let underline_height = font_height / 8;
-                        let bottom_gap = 3;
-                        let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
-
-                        let underline_padding = 8;
-                        let underline_width = text_width + underline_padding;
-                        let underline_x = x_position - (underline_padding / 2) as i16;
-
-                        unsafe {
-                            let gc = x11::xlib::XCreateGC(
-                                display,
-                                self.surface.pixmap(),
-                                0,
-                                std::ptr::null_mut(),
-                            );
-                            x11::xlib::XSetForeground(display, gc, block.color() as u64);
-                            x11::xlib::XFillRectangle(
-                                display,
-                                self.surface.pixmap(),
-                                gc,
-                                underline_x as i32,
-                                underline_y as i32,
-                                underline_width as u32,
-                                underline_height as u32,
-                            );
-                            x11::xlib::XFreeGC(display, gc);
-                        }
-                    }
-                }
-            }
-        }
-
-        unsafe {
-            let gc = x11::xlib::XCreateGC(
-                display,
-                self.window as x11::xlib::Drawable,
-                0,
-                std::ptr::null_mut(),
-            );
-            x11::xlib::XCopyArea(
-                display,
-                self.surface.pixmap(),
-                self.window as x11::xlib::Drawable,
-                gc,
-                0,
-                0,
-                self.width as u32,
-                self.height as u32,
-                0,
-                0,
-            );
-            x11::xlib::XFreeGC(display, gc);
-            x11::xlib::XSync(display, 1);
-        }
-
-        self.needs_redraw = false;
-
-        Ok(())
-    }
-
-    pub fn handle_click(&self, click_x: i16) -> Option<usize> {
-        let mut current_x_position = 0;
-
-        for (tag_index, &tag_width) in self.tag_widths.iter().enumerate() {
-            let tag_mask = 1 << tag_index;
-            let is_selected = (self.last_current_tags & tag_mask) != 0;
-            let is_occupied = (self.last_occupied_tags & tag_mask) != 0;
-
-            if self.hide_vacant_tags && !is_occupied && !is_selected {
-                continue;
-            }
-
-            if click_x >= current_x_position && click_x < current_x_position + tag_width as i16 {
-                return Some(tag_index);
-            }
-            current_x_position += tag_width as i16;
-        }
-        None
-    }
-
-    pub fn needs_redraw(&self) -> bool {
-        self.needs_redraw
-    }
-
-    pub fn update_from_config(&mut self, config: &Config) {
-        self.blocks = config
-            .status_blocks
-            .iter()
-            .map(|block_config| block_config.to_block())
-            .collect();
-
-        self.block_underlines = config
-            .status_blocks
-            .iter()
-            .map(|block_config| block_config.underline)
-            .collect();
-
-        self.block_last_updates = vec![Instant::now(); self.blocks.len()];
-
-        self.tags = config.tags.clone();
-        self.scheme_normal = config.scheme_normal;
-        self.scheme_occupied = config.scheme_occupied;
-        self.scheme_selected = config.scheme_selected;
-        self.scheme_urgent = config.scheme_urgent;
-        self.hide_vacant_tags = config.hide_vacant_tags;
-
-        self.status_text.clear();
-        self.needs_redraw = true;
-    }
-}
diff --git a/src/bar/bar.zig b/src/bar/bar.zig
new file mode 100644
index 0000000..6b0e86d
--- /dev/null
+++ b/src/bar/bar.zig
@@ -0,0 +1,359 @@
+const std = @import("std");
+const xlib = @import("../x11/xlib.zig");
+const monitor_mod = @import("../monitor.zig");
+const client_mod = @import("../client.zig");
+const blocks_mod = @import("blocks/blocks.zig");
+const config_mod = @import("../config/config.zig");
+
+const Monitor = monitor_mod.Monitor;
+const Block = blocks_mod.Block;
+
+fn get_layout_symbol(layout_index: u32) []const u8 {
+    const cfg = config_mod.get_config();
+    if (cfg) |conf| {
+        return switch (layout_index) {
+            0 => conf.layout_tile_symbol,
+            1 => conf.layout_monocle_symbol,
+            2 => conf.layout_floating_symbol,
+            3 => "[S]",
+            else => "[?]",
+        };
+    }
+    return switch (layout_index) {
+        0 => "[]=",
+        1 => "[M]",
+        2 => "><>",
+        3 => "[S]",
+        else => "[?]",
+    };
+}
+
+pub const ColorScheme = struct {
+    foreground: c_ulong,
+    background: c_ulong,
+    border: c_ulong,
+};
+
+pub const Bar = struct {
+    window: xlib.Window,
+    pixmap: xlib.Pixmap,
+    graphics_context: xlib.GC,
+    xft_draw: ?*xlib.XftDraw,
+    width: i32,
+    height: i32,
+    monitor: *Monitor,
+
+    font: ?*xlib.XftFont,
+    font_height: i32,
+
+    scheme_normal: ColorScheme,
+    scheme_selected: ColorScheme,
+    scheme_occupied: ColorScheme,
+    scheme_urgent: ColorScheme,
+
+    allocator: std.mem.Allocator,
+    blocks: std.ArrayList(Block),
+    needs_redraw: bool,
+    next: ?*Bar,
+
+    pub fn create(
+        allocator: std.mem.Allocator,
+        display: *xlib.Display,
+        screen: c_int,
+        monitor: *Monitor,
+        font_name: []const u8,
+    ) ?*Bar {
+        const bar = allocator.create(Bar) catch return null;
+
+        const visual = xlib.XDefaultVisual(display, screen);
+        const colormap = xlib.XDefaultColormap(display, screen);
+        const depth = xlib.XDefaultDepth(display, screen);
+        const root = xlib.XRootWindow(display, screen);
+
+        const font_name_z = allocator.dupeZ(u8, font_name) catch return null;
+        defer allocator.free(font_name_z);
+
+        const font = xlib.XftFontOpenName(display, screen, font_name_z);
+        if (font == null) {
+            allocator.destroy(bar);
+            return null;
+        }
+
+        const font_height = font.*.ascent + font.*.descent;
+        const bar_height: i32 = @intCast(@as(i32, font_height) + 8);
+
+        const window = xlib.c.XCreateSimpleWindow(
+            display,
+            root,
+            monitor.mon_x,
+            monitor.mon_y,
+            @intCast(monitor.mon_w),
+            @intCast(bar_height),
+            0,
+            0,
+            0x1a1b26,
+        );
+
+        _ = xlib.c.XSetWindowAttributes{};
+        var attributes: xlib.c.XSetWindowAttributes = undefined;
+        attributes.override_redirect = xlib.True;
+        attributes.event_mask = xlib.c.ExposureMask | xlib.c.ButtonPressMask;
+        _ = xlib.c.XChangeWindowAttributes(display, window, xlib.c.CWOverrideRedirect | xlib.c.CWEventMask, &attributes);
+
+        const pixmap = xlib.XCreatePixmap(
+            display,
+            window,
+            @intCast(monitor.mon_w),
+            @intCast(bar_height),
+            @intCast(depth),
+        );
+
+        const graphics_context = xlib.XCreateGC(display, pixmap, 0, null);
+
+        const xft_draw = xlib.XftDrawCreate(display, pixmap, visual, colormap);
+
+        _ = xlib.XMapWindow(display, window);
+
+        const cfg = config_mod.get_config();
+        const scheme_normal = if (cfg) |c| ColorScheme{ .foreground = c.scheme_normal.fg, .background = c.scheme_normal.bg, .border = c.scheme_normal.border } else ColorScheme{ .foreground = 0xbbbbbb, .background = 0x1a1b26, .border = 0x444444 };
+        const scheme_selected = if (cfg) |c| ColorScheme{ .foreground = c.scheme_selected.fg, .background = c.scheme_selected.bg, .border = c.scheme_selected.border } else ColorScheme{ .foreground = 0x0db9d7, .background = 0x1a1b26, .border = 0xad8ee6 };
+        const scheme_occupied = if (cfg) |c| ColorScheme{ .foreground = c.scheme_occupied.fg, .background = c.scheme_occupied.bg, .border = c.scheme_occupied.border } else ColorScheme{ .foreground = 0x0db9d7, .background = 0x1a1b26, .border = 0x0db9d7 };
+        const scheme_urgent = if (cfg) |c| ColorScheme{ .foreground = c.scheme_urgent.fg, .background = c.scheme_urgent.bg, .border = c.scheme_urgent.border } else ColorScheme{ .foreground = 0xf7768e, .background = 0x1a1b26, .border = 0xf7768e };
+
+        bar.* = Bar{
+            .window = window,
+            .pixmap = pixmap,
+            .graphics_context = graphics_context,
+            .xft_draw = xft_draw,
+            .width = monitor.mon_w,
+            .height = bar_height,
+            .monitor = monitor,
+            .font = font,
+            .font_height = font_height,
+            .scheme_normal = scheme_normal,
+            .scheme_selected = scheme_selected,
+            .scheme_occupied = scheme_occupied,
+            .scheme_urgent = scheme_urgent,
+            .allocator = allocator,
+            .blocks = .{},
+            .needs_redraw = true,
+            .next = null,
+        };
+
+        monitor.bar_win = window;
+        monitor.win_y = monitor.mon_y + bar_height;
+        monitor.win_h = monitor.mon_h - bar_height;
+
+        return bar;
+    }
+
+    pub fn destroy(self: *Bar, allocator: std.mem.Allocator, display: *xlib.Display) void {
+        if (self.xft_draw) |xft_draw| {
+            xlib.XftDrawDestroy(xft_draw);
+        }
+        if (self.font) |font| {
+            xlib.XftFontClose(display, font);
+        }
+        _ = xlib.XFreeGC(display, self.graphics_context);
+        _ = xlib.XFreePixmap(display, self.pixmap);
+        _ = xlib.c.XDestroyWindow(display, self.window);
+        self.blocks.deinit(self.allocator);
+        allocator.destroy(self);
+    }
+
+    pub fn add_block(self: *Bar, block: Block) void {
+        self.blocks.append(self.allocator, block) catch {};
+    }
+
+    pub fn invalidate(self: *Bar) void {
+        self.needs_redraw = true;
+    }
+
+    pub fn draw(self: *Bar, display: *xlib.Display, tags: []const []const u8) void {
+        if (!self.needs_redraw) return;
+
+        self.fill_rect(display, 0, 0, self.width, self.height, self.scheme_normal.background);
+
+        var x_position: i32 = 0;
+        const padding: i32 = 8;
+        const monitor = self.monitor;
+        const current_tags = monitor.tagset[monitor.sel_tags];
+
+        for (tags, 0..) |tag, index| {
+            const tag_mask: u32 = @as(u32, 1) << @intCast(index);
+            const is_selected = (current_tags & tag_mask) != 0;
+            const is_occupied = has_clients_on_tag(monitor, tag_mask);
+
+            const scheme = if (is_selected) self.scheme_selected else if (is_occupied) self.scheme_occupied else self.scheme_normal;
+
+            const tag_text_width = self.text_width(display, tag);
+            const tag_width = tag_text_width + padding * 2;
+
+            if (is_selected) {
+                self.fill_rect(display, x_position, self.height - 3, tag_width, 3, scheme.border);
+            }
+
+            const text_y = @divTrunc(self.height + self.font_height, 2) - 4;
+            self.draw_text(display, x_position + padding, text_y, tag, scheme.foreground);
+
+            x_position += tag_width;
+        }
+
+        x_position += padding;
+
+        const layout_symbol = get_layout_symbol(monitor.sel_lt);
+        self.draw_text(display, x_position, @divTrunc(self.height + self.font_height, 2) - 4, layout_symbol, self.scheme_normal.foreground);
+        x_position += self.text_width(display, layout_symbol) + padding;
+
+        var block_x: i32 = self.width - padding;
+        var block_index: usize = self.blocks.items.len;
+        while (block_index > 0) {
+            block_index -= 1;
+            const block = &self.blocks.items[block_index];
+            const content = block.get_content();
+            const content_width = self.text_width(display, content);
+            block_x -= content_width;
+            self.draw_text(display, block_x, @divTrunc(self.height + self.font_height, 2) - 4, content, block.color());
+            if (block.underline) {
+                self.fill_rect(display, block_x, self.height - 2, content_width, 2, block.color());
+            }
+            block_x -= padding;
+        }
+
+        _ = xlib.XCopyArea(display, self.pixmap, self.window, self.graphics_context, 0, 0, @intCast(self.width), @intCast(self.height), 0, 0);
+        _ = xlib.XSync(display, xlib.False);
+
+        self.needs_redraw = false;
+    }
+
+    fn fill_rect(self: *Bar, display: *xlib.Display, x: i32, y: i32, width: i32, height: i32, color: c_ulong) void {
+        _ = xlib.XSetForeground(display, self.graphics_context, color);
+        _ = xlib.XFillRectangle(display, self.pixmap, self.graphics_context, x, y, @intCast(width), @intCast(height));
+    }
+
+    fn draw_text(self: *Bar, display: *xlib.Display, x: i32, y: i32, text: []const u8, color: c_ulong) void {
+        if (self.xft_draw == null or self.font == null) return;
+
+        var xft_color: xlib.XftColor = undefined;
+        var render_color: xlib.XRenderColor = undefined;
+        render_color.red = @intCast((color >> 16 & 0xff) * 257);
+        render_color.green = @intCast((color >> 8 & 0xff) * 257);
+        render_color.blue = @intCast((color & 0xff) * 257);
+        render_color.alpha = 0xffff;
+
+        const visual = xlib.XDefaultVisual(display, 0);
+        const colormap = xlib.XDefaultColormap(display, 0);
+
+        _ = xlib.XftColorAllocValue(display, visual, colormap, &render_color, &xft_color);
+
+        xlib.XftDrawStringUtf8(self.xft_draw, &xft_color, self.font, x, y, text.ptr, @intCast(text.len));
+
+        xlib.XftColorFree(display, visual, colormap, &xft_color);
+    }
+
+    fn text_width(self: *Bar, display: *xlib.Display, text: []const u8) i32 {
+        if (self.font == null) return 0;
+
+        var extents: xlib.XGlyphInfo = undefined;
+        xlib.XftTextExtentsUtf8(display, self.font, text.ptr, @intCast(text.len), &extents);
+        return extents.xOff;
+    }
+
+    pub fn handle_click(self: *Bar, click_x: i32, tags: []const []const u8) ?usize {
+        var x_position: i32 = 0;
+        const padding: i32 = 8;
+        const display = xlib.c.XOpenDisplay(null) orelse return null;
+        defer _ = xlib.XCloseDisplay(display);
+
+        for (tags, 0..) |tag, index| {
+            const tag_text_width = self.text_width(display, tag);
+            const tag_width = tag_text_width + padding * 2;
+
+            if (click_x >= x_position and click_x < x_position + tag_width) {
+                return index;
+            }
+            x_position += tag_width;
+        }
+        return null;
+    }
+
+    pub fn update_blocks(self: *Bar) void {
+        var changed = false;
+        for (self.blocks.items) |*block| {
+            if (block.update()) {
+                changed = true;
+            }
+        }
+        if (changed) {
+            self.needs_redraw = true;
+        }
+    }
+
+    pub fn clear_blocks(self: *Bar) void {
+        self.blocks.clearRetainingCapacity();
+    }
+};
+
+fn has_clients_on_tag(monitor: *Monitor, tag_mask: u32) bool {
+    var current = monitor.clients;
+    while (current) |client| {
+        if ((client.tags & tag_mask) != 0) {
+            return true;
+        }
+        current = client.next;
+    }
+    return false;
+}
+
+pub var bars: ?*Bar = null;
+
+pub fn create_bars(allocator: std.mem.Allocator, display: *xlib.Display, screen: c_int) void {
+    var current_monitor = monitor_mod.monitors;
+    while (current_monitor) |monitor| {
+        const bar = Bar.create(allocator, display, screen, monitor, "monospace:size=10");
+        if (bar) |created_bar| {
+            bars = created_bar;
+        }
+        current_monitor = monitor.next;
+    }
+}
+
+pub fn draw_bars(display: *xlib.Display, tags: []const []const u8) void {
+    var current_monitor = monitor_mod.monitors;
+    while (current_monitor) |monitor| {
+        _ = monitor;
+        if (bars) |bar| {
+            bar.draw(display, tags);
+        }
+        current_monitor = if (current_monitor) |m| m.next else null;
+    }
+}
+
+pub fn invalidate_bars() void {
+    var current = bars;
+    while (current) |bar| {
+        bar.invalidate();
+        current = bar.next;
+    }
+}
+
+pub fn destroy_bars(allocator: std.mem.Allocator, display: *xlib.Display) void {
+    var current = bars;
+    while (current) |bar| {
+        const next = bar.next;
+        bar.destroy(allocator, display);
+        current = next;
+    }
+    bars = null;
+}
+
+pub fn window_to_bar(win: xlib.Window) ?*Bar {
+    var current = bars;
+    while (current) |bar| {
+        if (bar.window == win) {
+            return bar;
+        }
+        current = bar.next;
+    }
+    return null;
+}
diff --git a/src/bar/blocks/battery.rs b/src/bar/blocks/battery.rs
deleted file mode 100644
index b7c4357..0000000
--- a/src/bar/blocks/battery.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-use super::Block;
-use crate::errors::BlockError;
-use std::fs;
-use std::path::{Path, PathBuf};
-use std::time::Duration;
-
-pub struct Battery {
-    format_charging: String,
-    format_discharging: String,
-    format_full: String,
-    interval: Duration,
-    color: u32,
-    battery_path: String,
-}
-
-fn detect_battery_name() -> Option<String> {
-    let base = Path::new("/sys/class/power_supply");
-    let entries = fs::read_dir(base).ok()?;
-
-    for entry in entries.flatten() {
-        let path: PathBuf = entry.path();
-
-        let type_path = path.join("type");
-        let present_path = path.join("present");
-
-        let is_battery = fs::read_to_string(&type_path)
-            .map(|s| s.trim() == "Battery")
-            .unwrap_or(false);
-
-        let is_present = fs::read_to_string(&present_path)
-            .map(|s| s.trim() == "1")
-            .unwrap_or(true);
-
-        let is_device_scope = fs::read_to_string(path.join("scope"))
-            .map(|s| s.trim().eq_ignore_ascii_case("device"))
-            .unwrap_or(false);
-
-        if !is_battery || !is_present || is_device_scope {
-            continue;
-        }
-
-        if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
-            return Some(name.to_string());
-        }
-    }
-    None
-}
-
-impl Battery {
-    pub fn new(
-        format_charging: &str,
-        format_discharging: &str,
-        format_full: &str,
-        interval_secs: u64,
-        color: u32,
-        battery_name: Option<String>,
-    ) -> Self {
-        let name = battery_name
-            .or_else(detect_battery_name)
-            .unwrap_or_else(|| "BAT0".to_string());
-
-        Self {
-            format_charging: format_charging.to_string(),
-            format_discharging: format_discharging.to_string(),
-            format_full: format_full.to_string(),
-            interval: Duration::from_secs(interval_secs),
-            color,
-            battery_path: format!("/sys/class/power_supply/{}", name),
-        }
-    }
-
-    fn read_file(&self, filename: &str) -> Result<String, BlockError> {
-        let path = format!("{}/{}", self.battery_path, filename);
-        Ok(fs::read_to_string(path)?.trim().to_string())
-    }
-
-    fn get_capacity(&self) -> Result<u32, BlockError> {
-        Ok(self.read_file("capacity")?.parse()?)
-    }
-
-    fn get_status(&self) -> Result<String, BlockError> {
-        self.read_file("status")
-    }
-}
-
-impl Block for Battery {
-    fn content(&mut self) -> Result<String, BlockError> {
-        let capacity = self.get_capacity()?;
-        let status = self.get_status()?;
-
-        let format = match status.as_str() {
-            "Charging" => &self.format_charging,
-            "Full" => &self.format_full,
-            _ => &self.format_discharging,
-        };
-
-        Ok(format.replace("{}", &capacity.to_string()))
-    }
-
-    fn interval(&self) -> Duration {
-        self.interval
-    }
-
-    fn color(&self) -> u32 {
-        self.color
-    }
-}
diff --git a/src/bar/blocks/battery.zig b/src/bar/blocks/battery.zig
new file mode 100644
index 0000000..812b7d6
--- /dev/null
+++ b/src/bar/blocks/battery.zig
@@ -0,0 +1,87 @@
+const std = @import("std");
+const format_util = @import("format.zig");
+
+pub const Battery = struct {
+    format_charging: []const u8,
+    format_discharging: []const u8,
+    format_full: []const u8,
+    battery_name: []const u8,
+    interval_secs: u64,
+    color: c_ulong,
+
+    pub fn init(
+        format_charging: []const u8,
+        format_discharging: []const u8,
+        format_full: []const u8,
+        battery_name: []const u8,
+        interval_secs: u64,
+        color: c_ulong,
+    ) Battery {
+        return .{
+            .format_charging = format_charging,
+            .format_discharging = format_discharging,
+            .format_full = format_full,
+            .battery_name = if (battery_name.len > 0) battery_name else "BAT0",
+            .interval_secs = interval_secs,
+            .color = color,
+        };
+    }
+
+    pub fn content(self: *Battery, buffer: []u8) []const u8 {
+        var path_buf: [128]u8 = undefined;
+
+        const capacity = self.read_battery_file(&path_buf, "capacity") orelse return buffer[0..0];
+        const status = self.read_battery_status(&path_buf) orelse return buffer[0..0];
+
+        const format = switch (status) {
+            .charging => self.format_charging,
+            .discharging => self.format_discharging,
+            .full => self.format_full,
+        };
+
+        var cap_buf: [8]u8 = undefined;
+        const cap_str = std.fmt.bufPrint(&cap_buf, "{d}", .{capacity}) catch return buffer[0..0];
+
+        return format_util.substitute(format, cap_str, buffer);
+    }
+
+    const Status = enum { charging, discharging, full };
+
+    fn read_battery_status(self: *Battery, path_buf: *[128]u8) ?Status {
+        const path = std.fmt.bufPrint(path_buf, "/sys/class/power_supply/{s}/status", .{self.battery_name}) catch return null;
+
+        const file = std.fs.openFileAbsolute(path, .{}) catch return null;
+        defer file.close();
+
+        var buf: [32]u8 = undefined;
+        const len = file.read(&buf) catch return null;
+        const status_str = std.mem.trim(u8, buf[0..len], " \n\r\t");
+
+        if (std.mem.eql(u8, status_str, "Charging")) return .charging;
+        if (std.mem.eql(u8, status_str, "Discharging")) return .discharging;
+        if (std.mem.eql(u8, status_str, "Full")) return .full;
+        if (std.mem.eql(u8, status_str, "Not charging")) return .full;
+        return .discharging;
+    }
+
+    fn read_battery_file(self: *Battery, path_buf: *[128]u8, file_name: []const u8) ?u8 {
+        const path = std.fmt.bufPrint(path_buf, "/sys/class/power_supply/{s}/{s}", .{ self.battery_name, file_name }) catch return null;
+
+        const file = std.fs.openFileAbsolute(path, .{}) catch return null;
+        defer file.close();
+
+        var buf: [16]u8 = undefined;
+        const len = file.read(&buf) catch return null;
+        const value_str = std.mem.trim(u8, buf[0..len], " \n\r\t");
+
+        return std.fmt.parseInt(u8, value_str, 10) catch null;
+    }
+
+    pub fn interval(self: *Battery) u64 {
+        return self.interval_secs;
+    }
+
+    pub fn get_color(self: *Battery) c_ulong {
+        return self.color;
+    }
+};
diff --git a/src/bar/blocks/blocks.zig b/src/bar/blocks/blocks.zig
new file mode 100644
index 0000000..861bcd3
--- /dev/null
+++ b/src/bar/blocks/blocks.zig
@@ -0,0 +1,161 @@
+const std = @import("std");
+
+pub const Static = @import("static.zig").Static;
+pub const Date_Time = @import("datetime.zig").Date_Time;
+pub const Ram = @import("ram.zig").Ram;
+pub const Shell = @import("shell.zig").Shell;
+pub const Battery = @import("battery.zig").Battery;
+pub const Cpu_Temp = @import("cpu_temp.zig").Cpu_Temp;
+
+pub const Block_Type = enum {
+    static,
+    datetime,
+    ram,
+    shell,
+    battery,
+    cpu_temp,
+};
+
+pub const Block = struct {
+    data: Data,
+    last_update: i64,
+    cached_content: [256]u8,
+    cached_len: usize,
+    underline: bool,
+
+    pub const Data = union(Block_Type) {
+        static: Static,
+        datetime: Date_Time,
+        ram: Ram,
+        shell: Shell,
+        battery: Battery,
+        cpu_temp: Cpu_Temp,
+    };
+
+    pub fn init_static(text: []const u8, col: c_ulong, ul: bool) Block {
+        var block = Block{
+            .data = .{ .static = Static.init(text, col) },
+            .last_update = 0,
+            .cached_content = undefined,
+            .cached_len = 0,
+            .underline = ul,
+        };
+        @memcpy(block.cached_content[0..text.len], text);
+        block.cached_len = text.len;
+        return block;
+    }
+
+    pub fn init_datetime(format: []const u8, datetime_format: []const u8, interval_secs: u64, col: c_ulong, ul: bool) Block {
+        return .{
+            .data = .{ .datetime = Date_Time.init(format, datetime_format, interval_secs, col) },
+            .last_update = 0,
+            .cached_content = undefined,
+            .cached_len = 0,
+            .underline = ul,
+        };
+    }
+
+    pub fn init_ram(format: []const u8, interval_secs: u64, col: c_ulong, ul: bool) Block {
+        return .{
+            .data = .{ .ram = Ram.init(format, interval_secs, col) },
+            .last_update = 0,
+            .cached_content = undefined,
+            .cached_len = 0,
+            .underline = ul,
+        };
+    }
+
+    pub fn init_shell(format: []const u8, command: []const u8, interval_secs: u64, col: c_ulong, ul: bool) Block {
+        return .{
+            .data = .{ .shell = Shell.init(format, command, interval_secs, col) },
+            .last_update = 0,
+            .cached_content = undefined,
+            .cached_len = 0,
+            .underline = ul,
+        };
+    }
+
+    pub fn init_battery(
+        format_charging: []const u8,
+        format_discharging: []const u8,
+        format_full: []const u8,
+        battery_name: []const u8,
+        interval_secs: u64,
+        col: c_ulong,
+        ul: bool,
+    ) Block {
+        return .{
+            .data = .{ .battery = Battery.init(format_charging, format_discharging, format_full, battery_name, interval_secs, col) },
+            .last_update = 0,
+            .cached_content = undefined,
+            .cached_len = 0,
+            .underline = ul,
+        };
+    }
+
+    pub fn init_cpu_temp(
+        format: []const u8,
+        thermal_zone: []const u8,
+        interval_secs: u64,
+        col: c_ulong,
+        ul: bool,
+    ) Block {
+        return .{
+            .data = .{ .cpu_temp = Cpu_Temp.init(format, thermal_zone, interval_secs, col) },
+            .last_update = 0,
+            .cached_content = undefined,
+            .cached_len = 0,
+            .underline = ul,
+        };
+    }
+
+    pub fn update(self: *Block) bool {
+        const interval_secs = self.interval();
+        if (interval_secs == 0) return false;
+
+        const now = std.time.timestamp();
+        if (now - self.last_update < @as(i64, @intCast(interval_secs))) {
+            return false;
+        }
+
+        self.last_update = now;
+
+        const result = switch (self.data) {
+            .static => |*s| s.content(&self.cached_content),
+            .datetime => |*d| d.content(&self.cached_content),
+            .ram => |*r| r.content(&self.cached_content),
+            .shell => |*s| s.content(&self.cached_content),
+            .battery => |*b| b.content(&self.cached_content),
+            .cpu_temp => |*c| c.content(&self.cached_content),
+        };
+
+        self.cached_len = result.len;
+        return true;
+    }
+
+    pub fn interval(self: *Block) u64 {
+        return switch (self.data) {
+            .static => |*s| s.interval(),
+            .datetime => |*d| d.interval(),
+            .ram => |*r| r.interval(),
+            .shell => |*s| s.interval(),
+            .battery => |*b| b.interval(),
+            .cpu_temp => |*c| c.interval(),
+        };
+    }
+
+    pub fn color(self: *const Block) c_ulong {
+        return switch (self.data) {
+            .static => |s| s.color,
+            .datetime => |d| d.color,
+            .ram => |r| r.color,
+            .shell => |s| s.color,
+            .battery => |b| b.color,
+            .cpu_temp => |c| c.color,
+        };
+    }
+
+    pub fn get_content(self: *const Block) []const u8 {
+        return self.cached_content[0..self.cached_len];
+    }
+};
diff --git a/src/bar/blocks/cpu_temp.zig b/src/bar/blocks/cpu_temp.zig
new file mode 100644
index 0000000..539e49b
--- /dev/null
+++ b/src/bar/blocks/cpu_temp.zig
@@ -0,0 +1,113 @@
+const std = @import("std");
+const format_util = @import("format.zig");
+
+pub const Cpu_Temp = struct {
+    format: []const u8,
+    device: []const u8,
+    interval_secs: u64,
+    color: c_ulong,
+    cached_path: [128]u8,
+    cached_path_len: usize,
+
+    pub fn init(
+        format: []const u8,
+        device: []const u8,
+        interval_secs: u64,
+        color: c_ulong,
+    ) Cpu_Temp {
+        var self = Cpu_Temp{
+            .format = format,
+            .device = device,
+            .interval_secs = interval_secs,
+            .color = color,
+            .cached_path = undefined,
+            .cached_path_len = 0,
+        };
+        self.detect_path();
+        return self;
+    }
+
+    fn detect_path(self: *Cpu_Temp) void {
+        if (self.device.len > 0 and self.device[0] == '/') {
+            if (self.device.len <= self.cached_path.len) {
+                @memcpy(self.cached_path[0..self.device.len], self.device);
+                self.cached_path_len = self.device.len;
+            }
+            return;
+        }
+
+        if (self.device.len > 0) {
+            if (self.try_path("/sys/class/thermal/{s}/temp", self.device)) return;
+            if (self.try_path("/sys/class/hwmon/{s}/temp1_input", self.device)) return;
+        }
+
+        if (self.find_hwmon_cpu()) return;
+
+        if (self.try_path("/sys/class/thermal/{s}/temp", "thermal_zone0")) return;
+    }
+
+    fn try_path(self: *Cpu_Temp, comptime fmt: []const u8, device: []const u8) bool {
+        const path = std.fmt.bufPrint(&self.cached_path, fmt, .{device}) catch return false;
+        const file = std.fs.openFileAbsolute(path, .{}) catch return false;
+        file.close();
+        self.cached_path_len = path.len;
+        return true;
+    }
+
+    fn find_hwmon_cpu(self: *Cpu_Temp) bool {
+        var dir = std.fs.openDirAbsolute("/sys/class/hwmon", .{ .iterate = true }) catch return false;
+        defer dir.close();
+
+        var iter = dir.iterate();
+        while (iter.next() catch null) |entry| {
+            if (entry.kind != .directory and entry.kind != .sym_link) continue;
+
+            var name_path: [128]u8 = undefined;
+            const name_path_str = std.fmt.bufPrint(&name_path, "/sys/class/hwmon/{s}/name", .{entry.name}) catch continue;
+
+            const name_file = std.fs.openFileAbsolute(name_path_str, .{}) catch continue;
+            defer name_file.close();
+
+            var name_buf: [32]u8 = undefined;
+            const name_len = name_file.read(&name_buf) catch continue;
+            const name = std.mem.trim(u8, name_buf[0..name_len], " \n\r\t");
+
+            if (std.mem.eql(u8, name, "coretemp") or std.mem.eql(u8, name, "k10temp")) {
+                const temp_path = std.fmt.bufPrint(&self.cached_path, "/sys/class/hwmon/{s}/temp1_input", .{entry.name}) catch continue;
+                const temp_file = std.fs.openFileAbsolute(temp_path, .{}) catch continue;
+                temp_file.close();
+                self.cached_path_len = temp_path.len;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    pub fn content(self: *Cpu_Temp, buffer: []u8) []const u8 {
+        if (self.cached_path_len == 0) return buffer[0..0];
+
+        const path = self.cached_path[0..self.cached_path_len];
+        const file = std.fs.openFileAbsolute(path, .{}) catch return buffer[0..0];
+        defer file.close();
+
+        var temp_buf: [16]u8 = undefined;
+        const len = file.read(&temp_buf) catch return buffer[0..0];
+        const temp_str = std.mem.trim(u8, temp_buf[0..len], " \n\r\t");
+
+        const millidegrees = std.fmt.parseInt(i32, temp_str, 10) catch return buffer[0..0];
+        const degrees = @divTrunc(millidegrees, 1000);
+
+        var deg_buf: [8]u8 = undefined;
+        const deg_str = std.fmt.bufPrint(&deg_buf, "{d}", .{degrees}) catch return buffer[0..0];
+
+        return format_util.substitute(self.format, deg_str, buffer);
+    }
+
+    pub fn interval(self: *Cpu_Temp) u64 {
+        return self.interval_secs;
+    }
+
+    pub fn get_color(self: *Cpu_Temp) c_ulong {
+        return self.color;
+    }
+};
diff --git a/src/bar/blocks/datetime.rs b/src/bar/blocks/datetime.rs
deleted file mode 100644
index 9285176..0000000
--- a/src/bar/blocks/datetime.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use super::Block;
-use crate::errors::BlockError;
-use chrono::Local;
-use std::time::Duration;
-
-pub struct DateTime {
-    format_template: String,
-    time_format: String,
-    interval: Duration,
-    color: u32,
-}
-
-impl DateTime {
-    pub fn new(format_template: &str, time_format: &str, interval_secs: u64, color: u32) -> Self {
-        Self {
-            format_template: format_template.to_string(),
-            time_format: time_format.to_string(),
-            interval: Duration::from_secs(interval_secs),
-            color,
-        }
-    }
-}
-
-impl Block for DateTime {
-    fn content(&mut self) -> Result<String, BlockError> {
-        let now = Local::now();
-        let time_str = now.format(&self.time_format).to_string();
-        Ok(self.format_template.replace("{}", &time_str))
-    }
-
-    fn interval(&self) -> Duration {
-        self.interval
-    }
-
-    fn color(&self) -> u32 {
-        self.color
-    }
-}
diff --git a/src/bar/blocks/datetime.zig b/src/bar/blocks/datetime.zig
new file mode 100644
index 0000000..e176385
--- /dev/null
+++ b/src/bar/blocks/datetime.zig
@@ -0,0 +1,107 @@
+const std = @import("std");
+const format_util = @import("format.zig");
+const c = @cImport({
+    @cInclude("time.h");
+});
+
+pub const Date_Time = struct {
+    format: []const u8,
+    datetime_format: []const u8,
+    interval_secs: u64,
+    color: c_ulong,
+
+    pub fn init(format: []const u8, datetime_format: []const u8, interval_secs: u64, color: c_ulong) Date_Time {
+        return .{
+            .format = format,
+            .datetime_format = datetime_format,
+            .interval_secs = interval_secs,
+            .color = color,
+        };
+    }
+
+    pub fn content(self: *Date_Time, buffer: []u8) []const u8 {
+        var now: c.time_t = c.time(null);
+        const tm_ptr = c.localtime(&now);
+        if (tm_ptr == null) return buffer[0..0];
+        const tm = tm_ptr.*;
+
+        const hours: u32 = @intCast(tm.tm_hour);
+        const minutes: u32 = @intCast(tm.tm_min);
+        const seconds: u32 = @intCast(tm.tm_sec);
+        const day: u8 = @intCast(tm.tm_mday);
+        const month: u8 = @intCast(tm.tm_mon + 1);
+        const year: i32 = tm.tm_year + 1900;
+        const dow: i32 = tm.tm_wday;
+
+        var datetime_buf: [64]u8 = undefined;
+        var dt_len: usize = 0;
+
+        var fmt_idx: usize = 0;
+        while (fmt_idx < self.datetime_format.len and dt_len < datetime_buf.len - 10) {
+            if (self.datetime_format[fmt_idx] == '%' and fmt_idx + 1 < self.datetime_format.len) {
+                const next = self.datetime_format[fmt_idx + 1];
+                if (next == '-' and fmt_idx + 2 < self.datetime_format.len) {
+                    const spec = self.datetime_format[fmt_idx + 2];
+                    dt_len += format_spec(spec, false, hours, minutes, seconds, day, month, year, dow, datetime_buf[dt_len..]);
+                    fmt_idx += 3;
+                } else {
+                    dt_len += format_spec(next, true, hours, minutes, seconds, day, month, year, dow, datetime_buf[dt_len..]);
+                    fmt_idx += 2;
+                }
+            } else {
+                datetime_buf[dt_len] = self.datetime_format[fmt_idx];
+                dt_len += 1;
+                fmt_idx += 1;
+            }
+        }
+
+        return format_util.substitute(self.format, datetime_buf[0..dt_len], buffer);
+    }
+
+    fn format_spec(spec: u8, pad: bool, hours: u32, minutes: u32, seconds: u32, day: u8, month: u8, year: i32, dow: i32, buf: []u8) usize {
+        const day_names = [_][]const u8{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+        const month_names = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+        return switch (spec) {
+            'Y' => (std.fmt.bufPrint(buf, "{d}", .{year}) catch return 0).len,
+            'm' => if (pad) (std.fmt.bufPrint(buf, "{d:0>2}", .{month}) catch return 0).len else (std.fmt.bufPrint(buf, "{d}", .{month}) catch return 0).len,
+            'd' => if (pad) (std.fmt.bufPrint(buf, "{d:0>2}", .{day}) catch return 0).len else (std.fmt.bufPrint(buf, "{d}", .{day}) catch return 0).len,
+            'H' => if (pad) (std.fmt.bufPrint(buf, "{d:0>2}", .{hours}) catch return 0).len else (std.fmt.bufPrint(buf, "{d}", .{hours}) catch return 0).len,
+            'M' => if (pad) (std.fmt.bufPrint(buf, "{d:0>2}", .{minutes}) catch return 0).len else (std.fmt.bufPrint(buf, "{d}", .{minutes}) catch return 0).len,
+            'S' => if (pad) (std.fmt.bufPrint(buf, "{d:0>2}", .{seconds}) catch return 0).len else (std.fmt.bufPrint(buf, "{d}", .{seconds}) catch return 0).len,
+            'I' => blk: {
+                const h12 = if (hours == 0) 12 else if (hours > 12) hours - 12 else hours;
+                break :blk if (pad) (std.fmt.bufPrint(buf, "{d:0>2}", .{h12}) catch return 0).len else (std.fmt.bufPrint(buf, "{d}", .{h12}) catch return 0).len;
+            },
+            'p' => blk: {
+                const s: []const u8 = if (hours >= 12) "PM" else "AM";
+                @memcpy(buf[0..2], s);
+                break :blk 2;
+            },
+            'P' => blk: {
+                const s: []const u8 = if (hours >= 12) "pm" else "am";
+                @memcpy(buf[0..2], s);
+                break :blk 2;
+            },
+            'a' => blk: {
+                const name = day_names[@intCast(dow)];
+                @memcpy(buf[0..3], name);
+                break :blk 3;
+            },
+            'b' => blk: {
+                const name = month_names[month - 1];
+                @memcpy(buf[0..3], name);
+                break :blk 3;
+            },
+            else => 0,
+        };
+    }
+
+    pub fn interval(self: *Date_Time) u64 {
+        return self.interval_secs;
+    }
+
+    pub fn get_color(self: *Date_Time) c_ulong {
+        return self.color;
+    }
+};
diff --git a/src/bar/blocks/format.zig b/src/bar/blocks/format.zig
new file mode 100644
index 0000000..f3c90ef
--- /dev/null
+++ b/src/bar/blocks/format.zig
@@ -0,0 +1,52 @@
+pub fn substitute(format: []const u8, value: []const u8, buffer: []u8) []const u8 {
+    if (format.len == 0) {
+        const len = @min(value.len, buffer.len);
+        @memcpy(buffer[0..len], value[0..len]);
+        return buffer[0..len];
+    }
+
+    var out_idx: usize = 0;
+    var fmt_idx: usize = 0;
+    while (fmt_idx < format.len and out_idx < buffer.len -| value.len) {
+        if (fmt_idx + 1 < format.len and format[fmt_idx] == '{' and format[fmt_idx + 1] == '}') {
+            @memcpy(buffer[out_idx .. out_idx + value.len], value);
+            out_idx += value.len;
+            fmt_idx += 2;
+        } else {
+            buffer[out_idx] = format[fmt_idx];
+            out_idx += 1;
+            fmt_idx += 1;
+        }
+    }
+    return buffer[0..out_idx];
+}
+
+pub fn substitute_multi(format: []const u8, values: []const []const u8, buffer: []u8) []const u8 {
+    if (format.len == 0 and values.len > 0) {
+        const len = @min(values[0].len, buffer.len);
+        @memcpy(buffer[0..len], values[0][0..len]);
+        return buffer[0..len];
+    }
+
+    var out_idx: usize = 0;
+    var fmt_idx: usize = 0;
+    var val_idx: usize = 0;
+    while (fmt_idx < format.len and out_idx < buffer.len - 20) {
+        if (fmt_idx + 1 < format.len and format[fmt_idx] == '{' and format[fmt_idx + 1] == '}') {
+            if (val_idx < values.len) {
+                const value = values[val_idx];
+                if (out_idx + value.len <= buffer.len) {
+                    @memcpy(buffer[out_idx .. out_idx + value.len], value);
+                    out_idx += value.len;
+                }
+                val_idx += 1;
+            }
+            fmt_idx += 2;
+        } else {
+            buffer[out_idx] = format[fmt_idx];
+            out_idx += 1;
+            fmt_idx += 1;
+        }
+    }
+    return buffer[0..out_idx];
+}
diff --git a/src/bar/blocks/mod.rs b/src/bar/blocks/mod.rs
deleted file mode 100644
index 5c090f2..0000000
--- a/src/bar/blocks/mod.rs
+++ /dev/null
@@ -1,106 +0,0 @@
-use crate::errors::BlockError;
-use std::time::Duration;
-
-mod battery;
-mod datetime;
-mod ram;
-mod shell;
-
-use battery::Battery;
-use datetime::DateTime;
-use ram::Ram;
-use shell::ShellBlock;
-
-pub trait Block {
-    fn content(&mut self) -> Result<String, BlockError>;
-    fn interval(&self) -> Duration;
-    fn color(&self) -> u32;
-}
-
-#[derive(Debug, Clone)]
-pub struct BlockConfig {
-    pub format: String,
-    pub command: BlockCommand,
-    pub interval_secs: u64,
-    pub color: u32,
-    pub underline: bool,
-}
-
-#[derive(Debug, Clone)]
-pub enum BlockCommand {
-    Shell(String),
-    DateTime(String),
-    Battery {
-        format_charging: String,
-        format_discharging: String,
-        format_full: String,
-        battery_name: Option<String>,
-    },
-    Ram,
-    Static(String),
-}
-
-impl BlockConfig {
-    pub fn to_block(&self) -> Box<dyn Block> {
-        match &self.command {
-            BlockCommand::Shell(cmd) => Box::new(ShellBlock::new(
-                &self.format,
-                cmd,
-                self.interval_secs,
-                self.color,
-            )),
-            BlockCommand::DateTime(fmt) => Box::new(DateTime::new(
-                &self.format,
-                fmt,
-                self.interval_secs,
-                self.color,
-            )),
-            BlockCommand::Battery {
-                format_charging,
-                format_discharging,
-                format_full,
-                battery_name,
-            } => Box::new(Battery::new(
-                format_charging,
-                format_discharging,
-                format_full,
-                self.interval_secs,
-                self.color,
-                battery_name.clone(),
-            )),
-            BlockCommand::Ram => Box::new(Ram::new(&self.format, self.interval_secs, self.color)),
-            BlockCommand::Static(text) => Box::new(StaticBlock::new(
-                &format!("{}{}", self.format, text),
-                self.color,
-            )),
-        }
-    }
-}
-
-struct StaticBlock {
-    text: String,
-    color: u32,
-}
-
-impl StaticBlock {
-    fn new(text: &str, color: u32) -> Self {
-        Self {
-            text: text.to_string(),
-            color,
-        }
-    }
-}
-
-impl Block for StaticBlock {
-    fn content(&mut self) -> Result<String, BlockError> {
-        Ok(self.text.clone())
-    }
-
-    fn interval(&self) -> Duration {
-        Duration::from_secs(u64::MAX)
-    }
-
-    fn color(&self) -> u32 {
-        self.color
-    }
-}
diff --git a/src/bar/blocks/ram.rs b/src/bar/blocks/ram.rs
deleted file mode 100644
index c26571a..0000000
--- a/src/bar/blocks/ram.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use super::Block;
-use crate::errors::BlockError;
-use std::fs;
-use std::time::Duration;
-
-pub struct Ram {
-    format: String,
-    interval: Duration,
-    color: u32,
-}
-
-impl Ram {
-    pub fn new(format: &str, interval_secs: u64, color: u32) -> Self {
-        Self {
-            format: format.to_string(),
-            interval: Duration::from_secs(interval_secs),
-            color,
-        }
-    }
-
-    fn get_memory_info(&self) -> Result<(u64, u64, f32), BlockError> {
-        let meminfo = fs::read_to_string("/proc/meminfo")?;
-        let mut total: u64 = 0;
-        let mut available: u64 = 0;
-
-        for line in meminfo.lines() {
-            if line.starts_with("MemTotal:") {
-                total = line
-                    .split_whitespace()
-                    .nth(1)
-                    .and_then(|s| s.parse().ok())
-                    .unwrap_or(0)
-            } else if line.starts_with("MemAvailable:") {
-                available = line
-                    .split_whitespace()
-                    .nth(1)
-                    .and_then(|s| s.parse().ok())
-                    .unwrap_or(0)
-            }
-        }
-
-        let used = total.saturating_sub(available);
-        let percentage = if total > 0 {
-            (used as f32 / total as f32) * 100.0
-        } else {
-            0.0
-        };
-
-        Ok((used, total, percentage))
-    }
-}
-
-impl Block for Ram {
-    fn content(&mut self) -> Result<String, BlockError> {
-        let (used, total, percentage) = self.get_memory_info()?;
-
-        let used_gb = used as f32 / 1024.0 / 1024.0;
-        let total_gb = total as f32 / 1024.0 / 1024.0;
-
-        let result = self
-            .format
-            .replace("{used}", &format!("{:.1}", used_gb))
-            .replace("{total}", &format!("{:.1}", total_gb))
-            .replace("{percent}", &format!("{:.1}", percentage))
-            .replace("{}", &format!("{:.1}", used_gb));
-
-        Ok(result)
-    }
-
-    fn interval(&self) -> Duration {
-        self.interval
-    }
-
-    fn color(&self) -> u32 {
-        self.color
-    }
-}
diff --git a/src/bar/blocks/ram.zig b/src/bar/blocks/ram.zig
new file mode 100644
index 0000000..55ba924
--- /dev/null
+++ b/src/bar/blocks/ram.zig
@@ -0,0 +1,111 @@
+const std = @import("std");
+
+pub const Ram = struct {
+    format: []const u8,
+    interval_secs: u64,
+    color: c_ulong,
+
+    pub fn init(format: []const u8, interval_secs: u64, color: c_ulong) Ram {
+        return .{
+            .format = format,
+            .interval_secs = interval_secs,
+            .color = color,
+        };
+    }
+
+    pub fn content(self: *Ram, buffer: []u8) []const u8 {
+        const file = std.fs.openFileAbsolute("/proc/meminfo", .{}) catch return buffer[0..0];
+        defer file.close();
+
+        var read_buffer: [512]u8 = undefined;
+        const bytes_read = file.read(&read_buffer) catch return buffer[0..0];
+        const file_content = read_buffer[0..bytes_read];
+
+        var total: u64 = 0;
+        var available: u64 = 0;
+
+        var lines = std.mem.splitScalar(u8, file_content, '\n');
+        while (lines.next()) |line| {
+            if (std.mem.startsWith(u8, line, "MemTotal:")) {
+                total = parse_mem_value(line);
+            } else if (std.mem.startsWith(u8, line, "MemAvailable:")) {
+                available = parse_mem_value(line);
+            }
+        }
+
+        if (total == 0) return buffer[0..0];
+
+        const used = total - available;
+        const used_gb = @as(f32, @floatFromInt(used)) / 1024.0 / 1024.0;
+        const total_gb = @as(f32, @floatFromInt(total)) / 1024.0 / 1024.0;
+        const percent = (@as(f32, @floatFromInt(used)) / @as(f32, @floatFromInt(total))) * 100.0;
+
+        var val_bufs: [3][16]u8 = undefined;
+        const used_str = std.fmt.bufPrint(&val_bufs[0], "{d:.1}", .{used_gb}) catch return buffer[0..0];
+        const total_str = std.fmt.bufPrint(&val_bufs[1], "{d:.1}", .{total_gb}) catch return buffer[0..0];
+        const percent_str = std.fmt.bufPrint(&val_bufs[2], "{d:.1}", .{percent}) catch return buffer[0..0];
+
+        return substitute(self.format, buffer, .{
+            .used = used_str,
+            .total = total_str,
+            .percent = percent_str,
+        });
+    }
+
+    pub fn interval(self: *Ram) u64 {
+        return self.interval_secs;
+    }
+
+    pub fn get_color(self: *Ram) c_ulong {
+        return self.color;
+    }
+};
+
+fn parse_mem_value(line: []const u8) u64 {
+    var iter = std.mem.tokenizeAny(u8, line, ": \tkB");
+    _ = iter.next();
+    if (iter.next()) |value_str| {
+        return std.fmt.parseInt(u64, value_str, 10) catch 0;
+    }
+    return 0;
+}
+
+fn substitute(format: []const u8, buffer: []u8, values: struct {
+    used: []const u8,
+    total: []const u8,
+    percent: []const u8,
+}) []const u8 {
+    var pos: usize = 0;
+    var i: usize = 0;
+
+    while (i < format.len) {
+        if (format[i] == '{' and i + 1 < format.len) {
+            const rest = format[i..];
+            const repl = if (std.mem.startsWith(u8, rest, "{used}")) blk: {
+                i += 6;
+                break :blk values.used;
+            } else if (std.mem.startsWith(u8, rest, "{total}")) blk: {
+                i += 7;
+                break :blk values.total;
+            } else if (std.mem.startsWith(u8, rest, "{percent}")) blk: {
+                i += 9;
+                break :blk values.percent;
+            } else if (std.mem.startsWith(u8, rest, "{}")) blk: {
+                i += 2;
+                break :blk values.used;
+            } else null;
+
+            if (repl) |r| {
+                if (pos + r.len > buffer.len) break;
+                @memcpy(buffer[pos..][0..r.len], r);
+                pos += r.len;
+                continue;
+            }
+        }
+        if (pos >= buffer.len) break;
+        buffer[pos] = format[i];
+        pos += 1;
+        i += 1;
+    }
+    return buffer[0..pos];
+}
diff --git a/src/bar/blocks/shell.rs b/src/bar/blocks/shell.rs
deleted file mode 100644
index 81930e5..0000000
--- a/src/bar/blocks/shell.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-use super::Block;
-use crate::errors::BlockError;
-use std::process::Command;
-use std::time::{Duration, Instant};
-
-pub struct ShellBlock {
-    format: String,
-    command: String,
-    interval: Duration,
-    color: u32,
-    cached_output: Option<String>,
-    last_run: Option<Instant>,
-}
-
-impl ShellBlock {
-    pub fn new(format: &str, command: &str, interval_secs: u64, color: u32) -> Self {
-        Self {
-            format: format.to_string(),
-            command: command.to_string(),
-            interval: Duration::from_secs(interval_secs),
-            color,
-            cached_output: None,
-            last_run: None,
-        }
-    }
-
-    fn execute(&mut self) -> Result<String, BlockError> {
-        let output = Command::new("sh")
-            .arg("-c")
-            .arg(&self.command)
-            .output()
-            .map_err(|e| BlockError::CommandFailed(format!("Failed to execute command: {}", e)))?;
-
-        if !output.status.success() {
-            return Err(BlockError::CommandFailed(format!(
-                "Command exited with status: {}",
-                output.status
-            )));
-        }
-
-        let result = String::from_utf8_lossy(&output.stdout).trim().to_string();
-        let formatted = self.format.replace("{}", &result);
-
-        self.cached_output = Some(formatted.clone());
-        self.last_run = Some(Instant::now());
-
-        Ok(formatted)
-    }
-}
-
-impl Block for ShellBlock {
-    fn content(&mut self) -> Result<String, BlockError> {
-        let should_refresh = match self.last_run {
-            None => true,
-            Some(last) => last.elapsed() >= self.interval,
-        };
-
-        if should_refresh {
-            return self.execute();
-        }
-
-        self.cached_output
-            .clone()
-            .ok_or_else(|| BlockError::CommandFailed("No cached output".to_string()))
-    }
-
-    fn interval(&self) -> Duration {
-        self.interval
-    }
-
-    fn color(&self) -> u32 {
-        self.color
-    }
-}
diff --git a/src/bar/blocks/shell.zig b/src/bar/blocks/shell.zig
new file mode 100644
index 0000000..b884160
--- /dev/null
+++ b/src/bar/blocks/shell.zig
@@ -0,0 +1,45 @@
+const std = @import("std");
+const format_util = @import("format.zig");
+
+pub const Shell = struct {
+    format: []const u8,
+    command: []const u8,
+    interval_secs: u64,
+    color: c_ulong,
+
+    pub fn init(format: []const u8, command: []const u8, interval_secs: u64, col: c_ulong) Shell {
+        return .{
+            .format = format,
+            .command = command,
+            .interval_secs = interval_secs,
+            .color = col,
+        };
+    }
+
+    pub fn content(self: *Shell, buffer: []u8) []const u8 {
+        var cmd_output: [256]u8 = undefined;
+        const result = std.process.Child.run(.{
+            .allocator = std.heap.page_allocator,
+            .argv = &.{ "/bin/sh", "-c", self.command },
+        }) catch return buffer[0..0];
+        defer std.heap.page_allocator.free(result.stdout);
+        defer std.heap.page_allocator.free(result.stderr);
+
+        var cmd_len = @min(result.stdout.len, cmd_output.len);
+        @memcpy(cmd_output[0..cmd_len], result.stdout[0..cmd_len]);
+
+        while (cmd_len > 0 and (cmd_output[cmd_len - 1] == '\n' or cmd_output[cmd_len - 1] == '\r')) {
+            cmd_len -= 1;
+        }
+
+        return format_util.substitute(self.format, cmd_output[0..cmd_len], buffer);
+    }
+
+    pub fn interval(self: *Shell) u64 {
+        return self.interval_secs;
+    }
+
+    pub fn get_color(self: *Shell) c_ulong {
+        return self.color;
+    }
+};
diff --git a/src/bar/blocks/static.zig b/src/bar/blocks/static.zig
new file mode 100644
index 0000000..d243cb5
--- /dev/null
+++ b/src/bar/blocks/static.zig
@@ -0,0 +1,24 @@
+const std = @import("std");
+
+pub const Static = struct {
+    text: []const u8,
+    color: c_ulong,
+
+    pub fn init(text: []const u8, color: c_ulong) Static {
+        return .{ .text = text, .color = color };
+    }
+
+    pub fn content(self: *Static, buffer: []u8) []const u8 {
+        const len = @min(self.text.len, buffer.len);
+        @memcpy(buffer[0..len], self.text[0..len]);
+        return buffer[0..len];
+    }
+
+    pub fn interval(_: *Static) u64 {
+        return 0;
+    }
+
+    pub fn get_color(self: *Static) c_ulong {
+        return self.color;
+    }
+};
diff --git a/src/bar/font.rs b/src/bar/font.rs
deleted file mode 100644
index 9d45ae4..0000000
--- a/src/bar/font.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-use std::ffi::CString;
-use x11::xft::{XftColor, XftDraw, XftDrawStringUtf8, XftFont, XftFontOpenName};
-use x11::xlib::{Colormap, Display, Drawable, Visual};
-use x11::xrender::XRenderColor;
-
-use crate::errors::X11Error;
-
-pub struct Font {
-    xft_font: *mut XftFont,
-    display: *mut Display,
-}
-
-impl Font {
-    pub fn new(display: *mut Display, screen: i32, font_name: &str) -> Result<Self, X11Error> {
-        let font_name_cstr =
-            CString::new(font_name).map_err(|_| X11Error::FontLoadFailed(font_name.to_string()))?;
-
-        let xft_font = unsafe { XftFontOpenName(display, screen, font_name_cstr.as_ptr()) };
-
-        if xft_font.is_null() {
-            return Err(X11Error::FontLoadFailed(font_name.to_string()));
-        }
-
-        Ok(Font { xft_font, display })
-    }
-
-    pub fn height(&self) -> u16 {
-        unsafe {
-            let font = &*self.xft_font;
-            font.height as u16
-        }
-    }
-
-    pub fn ascent(&self) -> i16 {
-        unsafe {
-            let font = &*self.xft_font;
-            font.ascent as i16
-        }
-    }
-
-    pub fn text_width(&self, text: &str) -> u16 {
-        unsafe {
-            let mut extents = std::mem::zeroed();
-            x11::xft::XftTextExtentsUtf8(
-                self.display,
-                self.xft_font,
-                text.as_ptr(),
-                text.len() as i32,
-                &mut extents,
-            );
-            extents.width
-        }
-    }
-}
-
-impl Drop for Font {
-    fn drop(&mut self) {
-        unsafe {
-            if !self.xft_font.is_null() {
-                x11::xft::XftFontClose(self.display, self.xft_font);
-            }
-        }
-    }
-}
-
-pub struct FontDraw {
-    xft_draw: *mut XftDraw,
-}
-
-impl FontDraw {
-    pub fn new(
-        display: *mut Display,
-        drawable: Drawable,
-        visual: *mut Visual,
-        colormap: Colormap,
-    ) -> Result<Self, X11Error> {
-        let xft_draw = unsafe { x11::xft::XftDrawCreate(display, drawable, visual, colormap) };
-
-        if xft_draw.is_null() {
-            return Err(X11Error::DrawCreateFailed);
-        }
-
-        Ok(FontDraw { xft_draw })
-    }
-
-    pub fn draw_text(&self, font: &Font, color: u32, x: i16, y: i16, text: &str) {
-        let red = ((color >> 16) & 0xFF) as u16;
-        let green = ((color >> 8) & 0xFF) as u16;
-        let blue = (color & 0xFF) as u16;
-
-        let render_color = XRenderColor {
-            red: red << 8 | red,
-            green: green << 8 | green,
-            blue: blue << 8 | blue,
-            alpha: 0xFFFF,
-        };
-
-        let mut xft_color: XftColor = unsafe { std::mem::zeroed() };
-
-        unsafe {
-            x11::xft::XftColorAllocValue(
-                x11::xft::XftDrawDisplay(self.xft_draw),
-                x11::xft::XftDrawVisual(self.xft_draw),
-                x11::xft::XftDrawColormap(self.xft_draw),
-                &render_color,
-                &mut xft_color,
-            );
-
-            XftDrawStringUtf8(
-                self.xft_draw,
-                &xft_color,
-                font.xft_font,
-                x as i32,
-                y as i32,
-                text.as_ptr(),
-                text.len() as i32,
-            );
-
-            x11::xft::XftColorFree(
-                x11::xft::XftDrawDisplay(self.xft_draw),
-                x11::xft::XftDrawVisual(self.xft_draw),
-                x11::xft::XftDrawColormap(self.xft_draw),
-                &mut xft_color,
-            );
-        }
-    }
-
-    pub fn flush(&self) {
-        unsafe {
-            let display = x11::xft::XftDrawDisplay(self.xft_draw);
-            x11::xlib::XFlush(display);
-        }
-    }
-
-    pub fn sync(&self) {
-        unsafe {
-            let display = x11::xft::XftDrawDisplay(self.xft_draw);
-            x11::xlib::XSync(display, 1);
-        }
-    }
-}
-
-impl Drop for FontDraw {
-    fn drop(&mut self) {
-        unsafe {
-            if !self.xft_draw.is_null() {
-                x11::xft::XftDrawDestroy(self.xft_draw);
-            }
-        }
-    }
-}
-
-pub struct DrawingSurface {
-    font_draw: FontDraw,
-    pixmap: x11::xlib::Pixmap,
-    display: *mut Display,
-}
-
-impl DrawingSurface {
-    pub fn new(
-        display: *mut Display,
-        window: x11::xlib::Drawable,
-        width: u32,
-        height: u32,
-        visual: *mut Visual,
-        colormap: Colormap,
-    ) -> Result<Self, crate::errors::X11Error> {
-        let depth = unsafe { x11::xlib::XDefaultDepth(display, 0) };
-        let pixmap = unsafe {
-            x11::xlib::XCreatePixmap(display, window, width, height, depth as u32)
-        };
-
-        let font_draw = FontDraw::new(display, pixmap, visual, colormap)?;
-
-        Ok(Self {
-            font_draw,
-            pixmap,
-            display,
-        })
-    }
-
-    pub fn pixmap(&self) -> x11::xlib::Pixmap {
-        self.pixmap
-    }
-
-    pub fn font_draw(&self) -> &FontDraw {
-        &self.font_draw
-    }
-}
-
-impl Drop for DrawingSurface {
-    fn drop(&mut self) {
-        unsafe {
-            x11::xft::XftDrawDestroy(self.font_draw.xft_draw);
-            self.font_draw.xft_draw = std::ptr::null_mut();
-            x11::xlib::XFreePixmap(self.display, self.pixmap);
-        }
-    }
-}
diff --git a/src/bar/mod.rs b/src/bar/mod.rs
deleted file mode 100644
index a60db74..0000000
--- a/src/bar/mod.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-mod bar;
-mod blocks;
-pub mod font;
-
-pub use bar::Bar;
-pub use blocks::{BlockCommand, BlockConfig};
-
-// Bar position (for future use)
-#[derive(Debug, Clone, Copy)]
-pub enum BarPosition {
-    Top,
-    Bottom,
-}
diff --git a/src/bin/main.rs b/src/bin/main.rs
deleted file mode 100644
index 42ef91d..0000000
--- a/src/bin/main.rs
+++ /dev/null
@@ -1,192 +0,0 @@
-use oxwm::errors::ConfigError;
-use oxwm::errors::MainError;
-use std::path::Path;
-use std::path::PathBuf;
-
-const CONFIG_FILE: &str = "config.lua";
-const TEMPLATE: &str = include_str!("../../templates/config.lua");
-
-enum Args {
-    Exit,
-    Arguments(Vec<String>),
-    Error(MainError),
-}
-
-fn main() -> Result<(), MainError> {
-    let arguments = match process_args() {
-        Args::Exit => return Ok(()),
-        Args::Arguments(v) => v,
-        Args::Error(e) => return Err(e),
-    };
-
-    let (config, config_warning) = load_config(arguments.get(2))?;
-
-    let mut window_manager = match oxwm::window_manager::WindowManager::new(config) {
-        Ok(wm) => wm,
-        Err(e) => return Err(MainError::CouldNotStartWm(e)),
-    };
-
-    if let Some(warning) = config_warning {
-        window_manager.show_startup_config_error(warning);
-    }
-
-    if let Err(e) = window_manager.run() {
-        return Err(MainError::WmError(e));
-    }
-
-    Ok(())
-}
-
-fn load_config(
-    config_path: Option<&String>,
-) -> Result<(oxwm::Config, Option<ConfigError>), MainError> {
-    let path = match config_path {
-        None => {
-            let config_dir = get_config_path()?;
-            let config_path = config_dir.join(CONFIG_FILE);
-            check_convert(&config_path)?;
-            config_path
-        }
-        Some(p) => PathBuf::from(p),
-    };
-
-    let config_string = match std::fs::read_to_string(&path) {
-        Ok(c) => c,
-        Err(e) => return Err(MainError::FailedReadConfig(e)),
-    };
-
-    let config_directory = path.parent();
-
-    let (mut config, config_warning) =
-        match oxwm::config::parse_lua_config(&config_string, config_directory) {
-            Ok(config) => (config, None),
-            Err(warning) => {
-                let config = match oxwm::config::parse_lua_config(TEMPLATE, None) {
-                    Ok(c) => c,
-                    Err(e) => return Err(MainError::FailedReadConfigTemplate(e)),
-                };
-                (config, Some(warning))
-            }
-        };
-    config.path = Some(path);
-    Ok((config, config_warning))
-}
-
-fn init_config() -> Result<(), MainError> {
-    let config_directory = get_config_path()?;
-    if let Err(e) = std::fs::create_dir_all(&config_directory) {
-        return Err(MainError::CouldNotCreateConfigDir(e));
-    }
-
-    let config_template = TEMPLATE;
-    let config_path = config_directory.join(CONFIG_FILE);
-    if let Err(e) = std::fs::write(&config_path, config_template) {
-        return Err(MainError::CouldNotWriteConfig(e));
-    }
-
-    println!("✓ Config created at {:?}", config_path);
-    println!("  Edit the file and reload with Mod+Shift+R");
-    println!("  No compilation needed - changes take effect immediately!");
-
-    Ok(())
-}
-
-fn get_config_path() -> Result<PathBuf, MainError> {
-    match dirs::config_dir() {
-        Some(p) => Ok(p.join("oxwm")),
-        None => Err(MainError::NoConfigDir),
-    }
-}
-
-fn print_help() {
-    println!("OXWM - A dynamic window manager written in Rust\n");
-    println!("USAGE:");
-    println!("    oxwm [OPTIONS]\n");
-    println!("OPTIONS:");
-    println!("    --init              Create default config in ~/.config/oxwm/config.lua");
-    println!("    --config <PATH>     Use custom config file");
-    println!("    --version           Print version information");
-    println!("    --help              Print this help message\n");
-    println!("CONFIG:");
-    println!("    Location: ~/.config/oxwm/config.lua");
-    println!("    Edit the config file and use Mod+Shift+R to reload");
-    println!("    No compilation needed - instant hot-reload!");
-    println!("    LSP support included with oxwm.lua type definitions\n");
-    println!("FIRST RUN:");
-    println!("    Run 'oxwm --init' to create a config file");
-    println!("    Or just start oxwm and it will create one automatically\n");
-}
-
-fn process_args() -> Args {
-    let mut args = std::env::args();
-    let name = match args.next() {
-        Some(n) => n,
-        None => return Args::Error(MainError::NoProgramName),
-    };
-    let switch = args.next();
-    let path = args.next();
-
-    let switch = match switch {
-        Some(s) => s,
-        None => return Args::Arguments(vec![name]),
-    };
-
-    match switch.as_str() {
-        "--version" => {
-            println!("{name} {}", env!("CARGO_PKG_VERSION"));
-            Args::Exit
-        }
-        "--help" => {
-            print_help();
-            Args::Exit
-        }
-        "--init" => match init_config() {
-            Ok(_) => Args::Exit,
-            Err(e) => Args::Error(e),
-        },
-        "--config" => match check_custom_config(path) {
-            Ok(p) => Args::Arguments(vec![name, switch, p]),
-            Err(e) => Args::Error(e),
-        },
-        _ => Args::Error(MainError::InvalidArguments),
-    }
-}
-
-fn check_custom_config(path: Option<String>) -> Result<String, MainError> {
-    let path = match path {
-        Some(p) => p,
-        None => {
-            return Err(MainError::NoConfigPath);
-        }
-    };
-
-    match std::fs::exists(&path) {
-        Ok(b) => match b {
-            true => Ok(path),
-            false => Err(MainError::BadConfigPath),
-        },
-        Err(e) => Err(MainError::FailedCheckExist(e)),
-    }
-}
-
-fn check_convert(path: &Path) -> Result<(), MainError> {
-    let config_directory = get_config_path()?;
-
-    if !path.exists() {
-        let ron_path = config_directory.join("config.ron");
-        let had_ron_config = ron_path.exists();
-
-        println!("No config found at {:?}", config_directory);
-        println!("Creating default Lua config...");
-        init_config()?;
-
-        if had_ron_config {
-            println!("\n NOTICE: OXWM has migrated to Lua configuration.");
-            println!("   Your old config.ron has been preserved, but is no longer used.");
-            println!("   Your settings have been reset to defaults.");
-            println!("   Please manually port your configuration to the new Lua format.");
-            println!("   See the new config.lua template for examples.\n");
-        }
-    }
-    Ok(())
-}
diff --git a/src/client.zig b/src/client.zig
new file mode 100644
index 0000000..c92c51d
--- /dev/null
+++ b/src/client.zig
@@ -0,0 +1,259 @@
+const std = @import("std");
+const xlib = @import("x11/xlib.zig");
+const Monitor = @import("monitor.zig").Monitor;
+
+pub const Client = struct {
+    name: [256]u8 = std.mem.zeroes([256]u8),
+    min_aspect: f32 = 0,
+    max_aspect: f32 = 0,
+    x: i32 = 0,
+    y: i32 = 0,
+    width: i32 = 0,
+    height: i32 = 0,
+    old_x: i32 = 0,
+    old_y: i32 = 0,
+    old_width: i32 = 0,
+    old_height: i32 = 0,
+    base_width: i32 = 0,
+    base_height: i32 = 0,
+    increment_width: i32 = 0,
+    increment_height: i32 = 0,
+    max_width: i32 = 0,
+    max_height: i32 = 0,
+    min_width: i32 = 0,
+    min_height: i32 = 0,
+    hints_valid: bool = false,
+    border_width: i32 = 0,
+    old_border_width: i32 = 0,
+    tags: u32 = 0,
+    is_fixed: bool = false,
+    is_floating: bool = false,
+    is_urgent: bool = false,
+    never_focus: bool = false,
+    old_state: bool = false,
+    is_fullscreen: bool = false,
+    next: ?*Client = null,
+    stack_next: ?*Client = null,
+    monitor: ?*Monitor = null,
+    window: xlib.Window = 0,
+};
+
+var allocator: std.mem.Allocator = undefined;
+
+pub fn init(alloc: std.mem.Allocator) void {
+    allocator = alloc;
+}
+
+pub fn create(window: xlib.Window) ?*Client {
+    const client = allocator.create(Client) catch return null;
+    client.* = Client{ .window = window };
+    return client;
+}
+
+pub fn destroy(client: *Client) void {
+    allocator.destroy(client);
+}
+
+pub fn attach(client: *Client) void {
+    if (client.monitor) |monitor| {
+        client.next = monitor.clients;
+        monitor.clients = client;
+    }
+}
+
+pub fn detach(client: *Client) void {
+    if (client.monitor) |monitor| {
+        var current_ptr: *?*Client = &monitor.clients;
+        while (current_ptr.*) |current| {
+            if (current == client) {
+                current_ptr.* = client.next;
+                return;
+            }
+            current_ptr = &current.next;
+        }
+    }
+}
+
+pub fn attach_stack(client: *Client) void {
+    if (client.monitor) |monitor| {
+        client.stack_next = monitor.stack;
+        monitor.stack = client;
+    }
+}
+
+pub fn detach_stack(client: *Client) void {
+    if (client.monitor) |monitor| {
+        var current_ptr: *?*Client = &monitor.stack;
+        while (current_ptr.*) |current| {
+            if (current == client) {
+                current_ptr.* = client.stack_next;
+                return;
+            }
+            current_ptr = &current.stack_next;
+        }
+    }
+}
+
+pub fn window_to_client(window: xlib.Window) ?*Client {
+    const monitor_mod = @import("monitor.zig");
+    var current_monitor = monitor_mod.monitors;
+    while (current_monitor) |monitor| {
+        var current_client = monitor.clients;
+        while (current_client) |client| {
+            if (client.window == window) {
+                return client;
+            }
+            current_client = client.next;
+        }
+        current_monitor = monitor.next;
+    }
+    return null;
+}
+
+pub fn next_tiled(client: ?*Client) ?*Client {
+    var current = client;
+    while (current) |iter| {
+        if (!iter.is_floating and is_visible(iter)) {
+            return iter;
+        }
+        current = iter.next;
+    }
+    return null;
+}
+
+pub fn is_visible(client: *Client) bool {
+    if (client.monitor) |monitor| {
+        return (client.tags & monitor.tagset[monitor.sel_tags]) != 0;
+    }
+    return false;
+}
+
+pub fn is_visible_on_tag(client: *Client, tags: u32) bool {
+    return (client.tags & tags) != 0;
+}
+
+pub fn next_tagged(client: *Client) ?*Client {
+    const monitor = client.monitor orelse return null;
+    var walked = monitor.clients;
+    while (walked) |iter| {
+        if (!iter.is_floating and is_visible_on_tag(iter, client.tags)) {
+            return iter;
+        }
+        walked = iter.next;
+    }
+    return null;
+}
+
+pub fn attach_aside(client: *Client) void {
+    const at = next_tagged(client);
+    if (at == null) {
+        attach(client);
+        return;
+    }
+    client.next = at.?.next;
+    at.?.next = client;
+}
+
+pub fn count_tiled(monitor: *Monitor) u32 {
+    var count: u32 = 0;
+    var current = next_tiled(monitor.clients);
+    while (current) |client| {
+        count += 1;
+        current = next_tiled(client.next);
+    }
+    return count;
+}
+
+pub fn tiled_window_at(exclude: *Client, monitor: *Monitor, point_x: i32, point_y: i32) ?*Client {
+    const tags = monitor.tagset[monitor.sel_tags];
+    var current = monitor.clients;
+
+    while (current) |client| {
+        if (client != exclude and !client.is_floating and (client.tags & tags) != 0) {
+            const client_x = client.x;
+            const client_y = client.y;
+            const client_w = client.width + client.border_width * 2;
+            const client_h = client.height + client.border_width * 2;
+
+            if (point_x >= client_x and point_x < client_x + client_w and
+                point_y >= client_y and point_y < client_y + client_h)
+            {
+                return client;
+            }
+        }
+        current = client.next;
+    }
+    return null;
+}
+
+pub fn insert_before(client: *Client, target: *Client) void {
+    const monitor = target.monitor orelse return;
+    if (client.monitor != monitor) return;
+
+    detach(client);
+
+    if (monitor.clients == target) {
+        client.next = target;
+        monitor.clients = client;
+        return;
+    }
+
+    var current = monitor.clients;
+    while (current) |iter| {
+        if (iter.next == target) {
+            client.next = target;
+            iter.next = client;
+            return;
+        }
+        current = iter.next;
+    }
+}
+
+pub fn swap_clients(client_a: *Client, client_b: *Client) void {
+    const monitor = client_a.monitor orelse return;
+    if (client_b.monitor != monitor) return;
+
+    var prev_a: ?*Client = null;
+    var prev_b: ?*Client = null;
+    var iter = monitor.clients;
+
+    while (iter) |client| {
+        if (client.next == client_a) prev_a = client;
+        if (client.next == client_b) prev_b = client;
+        iter = client.next;
+    }
+
+    const next_a = client_a.next;
+    const next_b = client_b.next;
+
+    if (next_a == client_b) {
+        client_a.next = next_b;
+        client_b.next = client_a;
+        if (prev_a) |prev| {
+            prev.next = client_b;
+        } else {
+            monitor.clients = client_b;
+        }
+    } else if (next_b == client_a) {
+        client_b.next = next_a;
+        client_a.next = client_b;
+        if (prev_b) |prev| {
+            prev.next = client_a;
+        } else {
+            monitor.clients = client_a;
+        }
+    } else {
+        client_a.next = next_b;
+        client_b.next = next_a;
+        if (prev_a) |prev| {
+            prev.next = client_b;
+        } else {
+            monitor.clients = client_b;
+        }
+        if (prev_b) |prev| {
+            prev.next = client_a;
+        } else {
+            monitor.clients = client_a;
+        }
+    }
+}
diff --git a/src/client/mod.rs b/src/client/mod.rs
deleted file mode 100644
index 38c0645..0000000
--- a/src/client/mod.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-use x11rb::protocol::xproto::Window;
-
-pub type TagMask = u32;
-
-#[derive(Debug, Clone)]
-pub struct Client {
-    pub name: String,
-    pub min_aspect: f32,
-    pub max_aspect: f32,
-    pub x_position: i16,
-    pub y_position: i16,
-    pub width: u16,
-    pub height: u16,
-    pub old_x_position: i16,
-    pub old_y_position: i16,
-    pub old_width: u16,
-    pub old_height: u16,
-    pub base_width: i32,
-    pub base_height: i32,
-    pub increment_width: i32,
-    pub increment_height: i32,
-    pub max_width: i32,
-    pub max_height: i32,
-    pub min_width: i32,
-    pub min_height: i32,
-    pub hints_valid: bool,
-    pub border_width: u16,
-    pub old_border_width: u16,
-    pub tags: TagMask,
-    pub is_fixed: bool,
-    pub is_floating: bool,
-    pub is_urgent: bool,
-    pub never_focus: bool,
-    pub old_state: bool,
-    pub is_fullscreen: bool,
-    pub next: Option<Window>,
-    pub stack_next: Option<Window>,
-    pub monitor_index: usize,
-    pub window: Window,
-}
-
-impl Client {
-    pub fn new(window: Window, monitor_index: usize, tags: TagMask) -> Self {
-        Self {
-            name: String::new(),
-            min_aspect: 0.0,
-            max_aspect: 0.0,
-            x_position: 0,
-            y_position: 0,
-            width: 0,
-            height: 0,
-            old_x_position: 0,
-            old_y_position: 0,
-            old_width: 0,
-            old_height: 0,
-            base_width: 0,
-            base_height: 0,
-            increment_width: 0,
-            increment_height: 0,
-            max_width: 0,
-            max_height: 0,
-            min_width: 0,
-            min_height: 0,
-            hints_valid: false,
-            border_width: 0,
-            old_border_width: 0,
-            tags,
-            is_fixed: false,
-            is_floating: false,
-            is_urgent: false,
-            never_focus: false,
-            old_state: false,
-            is_fullscreen: false,
-            next: None,
-            stack_next: None,
-            monitor_index,
-            window,
-        }
-    }
-
-    pub fn width_with_border(&self) -> u16 {
-        self.width.saturating_add(2 * self.border_width)
-    }
-
-    pub fn height_with_border(&self) -> u16 {
-        self.height.saturating_add(2 * self.border_width)
-    }
-}
diff --git a/src/config/config.zig b/src/config/config.zig
new file mode 100644
index 0000000..a8986b8
--- /dev/null
+++ b/src/config/config.zig
@@ -0,0 +1,185 @@
+const std = @import("std");
+
+pub const Action = enum {
+    spawn_terminal,
+    spawn,
+    kill_client,
+    quit,
+    reload_config,
+    restart,
+    focus_next,
+    focus_prev,
+    move_next,
+    move_prev,
+    resize_master,
+    inc_master,
+    dec_master,
+    toggle_floating,
+    toggle_fullscreen,
+    toggle_gaps,
+    cycle_layout,
+    set_layout,
+    set_layout_tiling,
+    set_layout_floating,
+    view_tag,
+    view_next_tag,
+    view_prev_tag,
+    view_next_nonempty_tag,
+    view_prev_nonempty_tag,
+    move_to_tag,
+    toggle_view_tag,
+    toggle_tag,
+    focus_monitor,
+    send_to_monitor,
+    scroll_left,
+    scroll_right,
+};
+
+pub const Keybind = struct {
+    mod_mask: u32,
+    keysym: u64,
+    action: Action,
+    int_arg: i32 = 0,
+    str_arg: ?[]const u8 = null,
+};
+
+pub const Rule = struct {
+    class: ?[]const u8,
+    instance: ?[]const u8,
+    title: ?[]const u8,
+    tags: u32,
+    is_floating: bool,
+    monitor: i32,
+};
+
+pub const Block_Type = enum {
+    static,
+    datetime,
+    ram,
+    shell,
+    battery,
+    cpu_temp,
+};
+
+pub const Click_Target = enum {
+    client_win,
+    root_win,
+    tag_bar,
+};
+
+pub const Mouse_Action = enum {
+    move_mouse,
+    resize_mouse,
+    toggle_floating,
+};
+
+pub const Mouse_Button = struct {
+    click: Click_Target,
+    mod_mask: u32,
+    button: u32,
+    action: Mouse_Action,
+};
+
+pub const Block = struct {
+    block_type: Block_Type,
+    format: []const u8,
+    command: ?[]const u8 = null,
+    interval: u32,
+    color: u32,
+    underline: bool = true,
+    datetime_format: ?[]const u8 = null,
+    format_charging: ?[]const u8 = null,
+    format_discharging: ?[]const u8 = null,
+    format_full: ?[]const u8 = null,
+    battery_name: ?[]const u8 = null,
+    thermal_zone: ?[]const u8 = null,
+};
+
+pub const Color_Scheme = struct {
+    fg: u32 = 0xbbbbbb,
+    bg: u32 = 0x1a1b26,
+    border: u32 = 0x444444,
+};
+
+pub const Config = struct {
+    allocator: std.mem.Allocator,
+
+    terminal: []const u8 = "st",
+    font: []const u8 = "monospace:size=10",
+    tags: [9][]const u8 = .{ "1", "2", "3", "4", "5", "6", "7", "8", "9" },
+
+    border_width: i32 = 2,
+    border_focused: u32 = 0x6dade3,
+    border_unfocused: u32 = 0x444444,
+
+    gaps_enabled: bool = true,
+    smartgaps_enabled: bool = false,
+    gap_inner_h: i32 = 5,
+    gap_inner_v: i32 = 5,
+    gap_outer_h: i32 = 5,
+    gap_outer_v: i32 = 5,
+
+    modkey: u32 = (1 << 6),
+    auto_tile: bool = false,
+    tag_back_and_forth: bool = false,
+    hide_vacant_tags: bool = false,
+
+    layout_tile_symbol: []const u8 = "[]=",
+    layout_monocle_symbol: []const u8 = "[M]",
+    layout_floating_symbol: []const u8 = "><>",
+
+    scheme_normal: Color_Scheme = .{ .fg = 0xbbbbbb, .bg = 0x1a1b26, .border = 0x444444 },
+    scheme_selected: Color_Scheme = .{ .fg = 0x0db9d7, .bg = 0x1a1b26, .border = 0xad8ee6 },
+    scheme_occupied: Color_Scheme = .{ .fg = 0x0db9d7, .bg = 0x1a1b26, .border = 0x0db9d7 },
+    scheme_urgent: Color_Scheme = .{ .fg = 0xf7768e, .bg = 0x1a1b26, .border = 0xf7768e },
+
+    keybinds: std.ArrayListUnmanaged(Keybind) = .{},
+    rules: std.ArrayListUnmanaged(Rule) = .{},
+    blocks: std.ArrayListUnmanaged(Block) = .{},
+    buttons: std.ArrayListUnmanaged(Mouse_Button) = .{},
+    autostart: std.ArrayListUnmanaged([]const u8) = .{},
+
+    pub fn init(allocator: std.mem.Allocator) Config {
+        return Config{
+            .allocator = allocator,
+        };
+    }
+
+    pub fn deinit(self: *Config) void {
+        self.keybinds.deinit(self.allocator);
+        self.rules.deinit(self.allocator);
+        self.blocks.deinit(self.allocator);
+        self.buttons.deinit(self.allocator);
+        self.autostart.deinit(self.allocator);
+    }
+
+    pub fn add_keybind(self: *Config, keybind: Keybind) !void {
+        try self.keybinds.append(self.allocator, keybind);
+    }
+
+    pub fn add_rule(self: *Config, rule: Rule) !void {
+        try self.rules.append(self.allocator, rule);
+    }
+
+    pub fn add_block(self: *Config, block: Block) !void {
+        try self.blocks.append(self.allocator, block);
+    }
+
+    pub fn add_button(self: *Config, button: Mouse_Button) !void {
+        try self.buttons.append(self.allocator, button);
+    }
+
+    pub fn add_autostart(self: *Config, cmd: []const u8) !void {
+        try self.autostart.append(self.allocator, cmd);
+    }
+};
+
+pub var global_config: ?*Config = null;
+
+pub fn get_config() ?*Config {
+    return global_config;
+}
+
+pub fn set_config(cfg: *Config) void {
+    global_config = cfg;
+}
diff --git a/src/config/lua.rs b/src/config/lua.rs
deleted file mode 100644
index 69d4286..0000000
--- a/src/config/lua.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use crate::errors::ConfigError;
-use mlua::Lua;
-
-use super::lua_api;
-
-pub fn parse_lua_config(
-    input: &str,
-    config_dir: Option<&std::path::Path>,
-) -> Result<crate::Config, ConfigError> {
-    let lua = Lua::new();
-
-    if let Some(dir) = config_dir
-        && let Some(dir_str) = dir.to_str()
-    {
-        let setup_code = format!("package.path = '{}/?.lua;' .. package.path", dir_str);
-        lua.load(&setup_code)
-            .exec()
-            .map_err(|e| ConfigError::LuaError(format!("Failed to set package.path: {}", e)))?;
-    }
-
-    let builder = lua_api::register_api(&lua)?;
-
-    lua.load(input)
-        .exec()
-        .map_err(|e| ConfigError::LuaError(format!("{}", e)))?;
-
-    let builder_data = builder.borrow().clone();
-
-    Ok(crate::Config {
-        border_width: builder_data.border_width,
-        border_focused: builder_data.border_focused,
-        border_unfocused: builder_data.border_unfocused,
-        font: builder_data.font,
-        gaps_enabled: builder_data.gaps_enabled,
-        smartgaps_enabled: builder_data.smartgaps_enabled,
-        gap_inner_horizontal: builder_data.gap_inner_horizontal,
-        gap_inner_vertical: builder_data.gap_inner_vertical,
-        gap_outer_horizontal: builder_data.gap_outer_horizontal,
-        gap_outer_vertical: builder_data.gap_outer_vertical,
-        terminal: builder_data.terminal,
-        modkey: builder_data.modkey,
-        tags: builder_data.tags,
-        layout_symbols: builder_data.layout_symbols,
-        keybindings: builder_data.keybindings,
-        tag_back_and_forth: builder_data.tag_back_and_forth,
-        window_rules: builder_data.window_rules,
-        status_blocks: builder_data.status_blocks,
-        scheme_normal: builder_data.scheme_normal,
-        scheme_occupied: builder_data.scheme_occupied,
-        scheme_selected: builder_data.scheme_selected,
-        scheme_urgent: builder_data.scheme_urgent,
-        autostart: builder_data.autostart,
-        auto_tile: builder_data.auto_tile,
-        hide_vacant_tags: builder_data.hide_vacant_tags,
-        path: None,
-    })
-}
diff --git a/src/config/lua.zig b/src/config/lua.zig
new file mode 100644
index 0000000..96473f5
--- /dev/null
+++ b/src/config/lua.zig
@@ -0,0 +1,1172 @@
+const std = @import("std");
+const config_mod = @import("config.zig");
+const Config = config_mod.Config;
+const Keybind = config_mod.Keybind;
+const Action = config_mod.Action;
+const Rule = config_mod.Rule;
+const Block = config_mod.Block;
+const Block_Type = config_mod.Block_Type;
+const Mouse_Button = config_mod.Mouse_Button;
+const Click_Target = config_mod.Click_Target;
+const Mouse_Action = config_mod.Mouse_Action;
+const Color_Scheme = config_mod.Color_Scheme;
+
+const c = @cImport({
+    @cInclude("lua.h");
+    @cInclude("lauxlib.h");
+    @cInclude("lualib.h");
+});
+
+var L: ?*c.lua_State = null;
+var config: ?*Config = null;
+
+pub fn init(cfg: *Config) bool {
+    config = cfg;
+    L = c.luaL_newstate();
+    if (L == null) return false;
+    c.luaL_openlibs(L);
+    register_api();
+    return true;
+}
+
+pub fn deinit() void {
+    if (L) |state| {
+        c.lua_close(state);
+    }
+    L = null;
+    config = null;
+}
+
+pub fn load_file(path: []const u8) bool {
+    const state = L orelse return false;
+    var path_buf: [512]u8 = undefined;
+    if (path.len >= path_buf.len) return false;
+    @memcpy(path_buf[0..path.len], path);
+    path_buf[path.len] = 0;
+
+    if (c.luaL_loadfilex(state, &path_buf, null) != 0) {
+        const err = c.lua_tolstring(state, -1, null);
+        if (err != null) {
+            std.debug.print("lua load error: {s}\n", .{std.mem.span(err)});
+        }
+        c.lua_settop(state, -2);
+        return false;
+    }
+
+    if (c.lua_pcallk(state, 0, 0, 0, 0, null) != 0) {
+        const err = c.lua_tolstring(state, -1, null);
+        if (err != null) {
+            std.debug.print("lua runtime error: {s}\n", .{std.mem.span(err)});
+        }
+        c.lua_settop(state, -2);
+        return false;
+    }
+
+    return true;
+}
+
+pub fn load_config() bool {
+    const home = std.posix.getenv("HOME") orelse return false;
+    var path_buf: [512]u8 = undefined;
+    const path = std.fmt.bufPrint(&path_buf, "{s}/.config/oxwm/config.lua", .{home}) catch return false;
+    return load_file(path);
+}
+
+fn register_api() void {
+    const state = L orelse return;
+
+    c.lua_createtable(state, 0, 16);
+
+    register_spawn_functions(state);
+    register_key_module(state);
+    register_gaps_module(state);
+    register_border_module(state);
+    register_client_module(state);
+    register_layout_module(state);
+    register_tag_module(state);
+    register_monitor_module(state);
+    register_rule_module(state);
+    register_bar_module(state);
+    register_misc_functions(state);
+
+    c.lua_setglobal(state, "oxwm");
+}
+
+fn register_spawn_functions(state: *c.lua_State) void {
+    c.lua_pushcfunction(state, lua_spawn);
+    c.lua_setfield(state, -2, "spawn");
+
+    c.lua_pushcfunction(state, lua_spawn_terminal);
+    c.lua_setfield(state, -2, "spawn_terminal");
+}
+
+fn register_key_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 2);
+
+    c.lua_pushcfunction(state, lua_key_bind);
+    c.lua_setfield(state, -2, "bind");
+
+    c.lua_pushcfunction(state, lua_key_chord);
+    c.lua_setfield(state, -2, "chord");
+
+    c.lua_setfield(state, -2, "key");
+}
+
+fn register_gaps_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 6);
+
+    c.lua_pushcfunction(state, lua_gaps_set_enabled);
+    c.lua_setfield(state, -2, "set_enabled");
+
+    c.lua_pushcfunction(state, lua_gaps_enable);
+    c.lua_setfield(state, -2, "enable");
+
+    c.lua_pushcfunction(state, lua_gaps_disable);
+    c.lua_setfield(state, -2, "disable");
+
+    c.lua_pushcfunction(state, lua_gaps_set_inner);
+    c.lua_setfield(state, -2, "set_inner");
+
+    c.lua_pushcfunction(state, lua_gaps_set_outer);
+    c.lua_setfield(state, -2, "set_outer");
+
+    c.lua_pushcfunction(state, lua_gaps_set_smart);
+    c.lua_setfield(state, -2, "set_smart");
+
+    c.lua_setfield(state, -2, "gaps");
+}
+
+fn register_border_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 3);
+
+    c.lua_pushcfunction(state, lua_border_set_width);
+    c.lua_setfield(state, -2, "set_width");
+
+    c.lua_pushcfunction(state, lua_border_set_focused_color);
+    c.lua_setfield(state, -2, "set_focused_color");
+
+    c.lua_pushcfunction(state, lua_border_set_unfocused_color);
+    c.lua_setfield(state, -2, "set_unfocused_color");
+
+    c.lua_setfield(state, -2, "border");
+}
+
+fn register_client_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 5);
+
+    c.lua_pushcfunction(state, lua_client_kill);
+    c.lua_setfield(state, -2, "kill");
+
+    c.lua_pushcfunction(state, lua_client_toggle_fullscreen);
+    c.lua_setfield(state, -2, "toggle_fullscreen");
+
+    c.lua_pushcfunction(state, lua_client_toggle_floating);
+    c.lua_setfield(state, -2, "toggle_floating");
+
+    c.lua_pushcfunction(state, lua_client_focus_stack);
+    c.lua_setfield(state, -2, "focus_stack");
+
+    c.lua_pushcfunction(state, lua_client_move_stack);
+    c.lua_setfield(state, -2, "move_stack");
+
+    c.lua_setfield(state, -2, "client");
+}
+
+fn register_layout_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 4);
+
+    c.lua_pushcfunction(state, lua_layout_cycle);
+    c.lua_setfield(state, -2, "cycle");
+
+    c.lua_pushcfunction(state, lua_layout_set);
+    c.lua_setfield(state, -2, "set");
+
+    c.lua_pushcfunction(state, lua_layout_scroll_left);
+    c.lua_setfield(state, -2, "scroll_left");
+
+    c.lua_pushcfunction(state, lua_layout_scroll_right);
+    c.lua_setfield(state, -2, "scroll_right");
+
+    c.lua_setfield(state, -2, "layout");
+}
+
+fn register_tag_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 10);
+
+    c.lua_pushcfunction(state, lua_tag_view);
+    c.lua_setfield(state, -2, "view");
+
+    c.lua_pushcfunction(state, lua_tag_view_next);
+    c.lua_setfield(state, -2, "view_next");
+
+    c.lua_pushcfunction(state, lua_tag_view_previous);
+    c.lua_setfield(state, -2, "view_previous");
+
+    c.lua_pushcfunction(state, lua_tag_view_next_nonempty);
+    c.lua_setfield(state, -2, "view_next_nonempty");
+
+    c.lua_pushcfunction(state, lua_tag_view_previous_nonempty);
+    c.lua_setfield(state, -2, "view_previous_nonempty");
+
+    c.lua_pushcfunction(state, lua_tag_toggleview);
+    c.lua_setfield(state, -2, "toggleview");
+
+    c.lua_pushcfunction(state, lua_tag_move_to);
+    c.lua_setfield(state, -2, "move_to");
+
+    c.lua_pushcfunction(state, lua_tag_toggletag);
+    c.lua_setfield(state, -2, "toggletag");
+
+    c.lua_pushcfunction(state, lua_tag_set_back_and_forth);
+    c.lua_setfield(state, -2, "set_back_and_forth");
+
+    c.lua_setfield(state, -2, "tag");
+}
+
+fn register_monitor_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 2);
+
+    c.lua_pushcfunction(state, lua_monitor_focus);
+    c.lua_setfield(state, -2, "focus");
+
+    c.lua_pushcfunction(state, lua_monitor_tag);
+    c.lua_setfield(state, -2, "tag");
+
+    c.lua_setfield(state, -2, "monitor");
+}
+
+fn register_rule_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 1);
+
+    c.lua_pushcfunction(state, lua_rule_add);
+    c.lua_setfield(state, -2, "add");
+
+    c.lua_setfield(state, -2, "rule");
+}
+
+fn register_bar_module(state: *c.lua_State) void {
+    c.lua_createtable(state, 0, 10);
+
+    c.lua_pushcfunction(state, lua_bar_set_font);
+    c.lua_setfield(state, -2, "set_font");
+
+    c.lua_pushcfunction(state, lua_bar_set_blocks);
+    c.lua_setfield(state, -2, "set_blocks");
+
+    c.lua_pushcfunction(state, lua_bar_set_scheme_normal);
+    c.lua_setfield(state, -2, "set_scheme_normal");
+
+    c.lua_pushcfunction(state, lua_bar_set_scheme_selected);
+    c.lua_setfield(state, -2, "set_scheme_selected");
+
+    c.lua_pushcfunction(state, lua_bar_set_scheme_occupied);
+    c.lua_setfield(state, -2, "set_scheme_occupied");
+
+    c.lua_pushcfunction(state, lua_bar_set_scheme_urgent);
+    c.lua_setfield(state, -2, "set_scheme_urgent");
+
+    c.lua_pushcfunction(state, lua_bar_set_hide_vacant_tags);
+    c.lua_setfield(state, -2, "set_hide_vacant_tags");
+
+    c.lua_createtable(state, 0, 6);
+
+    c.lua_pushcfunction(state, lua_bar_block_ram);
+    c.lua_setfield(state, -2, "ram");
+
+    c.lua_pushcfunction(state, lua_bar_block_datetime);
+    c.lua_setfield(state, -2, "datetime");
+
+    c.lua_pushcfunction(state, lua_bar_block_shell);
+    c.lua_setfield(state, -2, "shell");
+
+    c.lua_pushcfunction(state, lua_bar_block_static);
+    c.lua_setfield(state, -2, "static");
+
+    c.lua_pushcfunction(state, lua_bar_block_battery);
+    c.lua_setfield(state, -2, "battery");
+
+    c.lua_setfield(state, -2, "block");
+
+    c.lua_setfield(state, -2, "bar");
+}
+
+fn register_misc_functions(state: *c.lua_State) void {
+    c.lua_pushcfunction(state, lua_set_terminal);
+    c.lua_setfield(state, -2, "set_terminal");
+
+    c.lua_pushcfunction(state, lua_set_modkey);
+    c.lua_setfield(state, -2, "set_modkey");
+
+    c.lua_pushcfunction(state, lua_set_tags);
+    c.lua_setfield(state, -2, "set_tags");
+
+    c.lua_pushcfunction(state, lua_autostart);
+    c.lua_setfield(state, -2, "autostart");
+
+    c.lua_pushcfunction(state, lua_auto_tile);
+    c.lua_setfield(state, -2, "auto_tile");
+
+    c.lua_pushcfunction(state, lua_quit);
+    c.lua_setfield(state, -2, "quit");
+
+    c.lua_pushcfunction(state, lua_restart);
+    c.lua_setfield(state, -2, "restart");
+
+    c.lua_pushcfunction(state, lua_toggle_gaps);
+    c.lua_setfield(state, -2, "toggle_gaps");
+
+    c.lua_pushcfunction(state, lua_set_master_factor);
+    c.lua_setfield(state, -2, "set_master_factor");
+
+    c.lua_pushcfunction(state, lua_inc_num_master);
+    c.lua_setfield(state, -2, "inc_num_master");
+}
+
+fn create_action_table(state: *c.lua_State, action_name: [*:0]const u8) void {
+    c.lua_createtable(state, 0, 2);
+    _ = c.lua_pushstring(state, action_name);
+    c.lua_setfield(state, -2, "__action");
+}
+
+fn create_action_table_with_int(state: *c.lua_State, action_name: [*:0]const u8, arg: i32) void {
+    c.lua_createtable(state, 0, 2);
+    _ = c.lua_pushstring(state, action_name);
+    c.lua_setfield(state, -2, "__action");
+    c.lua_pushinteger(state, arg);
+    c.lua_setfield(state, -2, "__arg");
+}
+
+fn create_action_table_with_string(state: *c.lua_State, action_name: [*:0]const u8) void {
+    c.lua_createtable(state, 0, 2);
+    _ = c.lua_pushstring(state, action_name);
+    c.lua_setfield(state, -2, "__action");
+    c.lua_pushvalue(state, 1);
+    c.lua_setfield(state, -2, "__arg");
+}
+
+fn lua_spawn(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table_with_string(s, "Spawn");
+    return 1;
+}
+
+fn lua_spawn_terminal(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "SpawnTerminal");
+    return 1;
+}
+
+fn lua_key_bind(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const cfg = config orelse return 0;
+
+    const mod_mask = parse_modifiers(s, 1);
+    const key_str = get_string_arg(s, 2) orelse return 0;
+    const keysym = key_name_to_keysym(key_str) orelse return 0;
+
+    if (c.lua_type(s, 3) != c.LUA_TTABLE) return 0;
+
+    _ = c.lua_getfield(s, 3, "__action");
+    const action_str = get_lua_string(s, -1) orelse {
+        c.lua_settop(s, -2);
+        return 0;
+    };
+    c.lua_settop(s, -2);
+
+    const action = parse_action(action_str) orelse return 0;
+
+    var int_arg: i32 = 0;
+    var str_arg: ?[]const u8 = null;
+
+    _ = c.lua_getfield(s, 3, "__arg");
+    if (c.lua_type(s, -1) == c.LUA_TNUMBER) {
+        int_arg = @intCast(c.lua_tointegerx(s, -1, null));
+    } else if (c.lua_type(s, -1) == c.LUA_TSTRING) {
+        str_arg = get_lua_string(s, -1);
+    }
+    c.lua_settop(s, -2);
+
+    cfg.add_keybind(.{
+        .mod_mask = mod_mask,
+        .keysym = keysym,
+        .action = action,
+        .int_arg = int_arg,
+        .str_arg = str_arg,
+    }) catch return 0;
+
+    return 0;
+}
+
+fn lua_key_chord(state: ?*c.lua_State) callconv(.c) c_int {
+    _ = state;
+    return 0;
+}
+
+fn lua_gaps_set_enabled(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.gaps_enabled = c.lua_toboolean(s, 1) != 0;
+    return 0;
+}
+
+fn lua_gaps_enable(state: ?*c.lua_State) callconv(.c) c_int {
+    _ = state;
+    const cfg = config orelse return 0;
+    cfg.gaps_enabled = true;
+    return 0;
+}
+
+fn lua_gaps_disable(state: ?*c.lua_State) callconv(.c) c_int {
+    _ = state;
+    const cfg = config orelse return 0;
+    cfg.gaps_enabled = false;
+    return 0;
+}
+
+fn lua_gaps_set_inner(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.gap_inner_h = @intCast(c.lua_tointegerx(s, 1, null));
+    cfg.gap_inner_v = @intCast(c.lua_tointegerx(s, 2, null));
+    return 0;
+}
+
+fn lua_gaps_set_outer(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.gap_outer_h = @intCast(c.lua_tointegerx(s, 1, null));
+    cfg.gap_outer_v = @intCast(c.lua_tointegerx(s, 2, null));
+    return 0;
+}
+
+fn lua_gaps_set_smart(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.smartgaps_enabled = c.lua_toboolean(s, 1) != 0;
+    return 0;
+}
+
+fn lua_border_set_width(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.border_width = @intCast(c.lua_tointegerx(s, 1, null));
+    return 0;
+}
+
+fn lua_border_set_focused_color(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.border_focused = parse_color(s, 1);
+    return 0;
+}
+
+fn lua_border_set_unfocused_color(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.border_unfocused = parse_color(s, 1);
+    return 0;
+}
+
+fn lua_client_kill(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "KillClient");
+    return 1;
+}
+
+fn lua_client_toggle_fullscreen(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ToggleFullScreen");
+    return 1;
+}
+
+fn lua_client_toggle_floating(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ToggleFloating");
+    return 1;
+}
+
+fn lua_client_focus_stack(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const dir: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "FocusStack", dir);
+    return 1;
+}
+
+fn lua_client_move_stack(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const dir: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "MoveStack", dir);
+    return 1;
+}
+
+fn lua_layout_cycle(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "CycleLayout");
+    return 1;
+}
+
+fn lua_layout_set(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table_with_string(s, "ChangeLayout");
+    return 1;
+}
+
+fn lua_layout_scroll_left(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ScrollLeft");
+    return 1;
+}
+
+fn lua_layout_scroll_right(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ScrollRight");
+    return 1;
+}
+
+fn lua_tag_view(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "ViewTag", idx - 1);
+    return 1;
+}
+
+fn lua_tag_view_next(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ViewNextTag");
+    return 1;
+}
+
+fn lua_tag_view_previous(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ViewPreviousTag");
+    return 1;
+}
+
+fn lua_tag_view_next_nonempty(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ViewNextNonEmptyTag");
+    return 1;
+}
+
+fn lua_tag_view_previous_nonempty(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ViewPreviousNonEmptyTag");
+    return 1;
+}
+
+fn lua_tag_toggleview(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "ToggleView", idx - 1);
+    return 1;
+}
+
+fn lua_tag_move_to(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "MoveToTag", idx - 1);
+    return 1;
+}
+
+fn lua_tag_toggletag(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "ToggleTag", idx - 1);
+    return 1;
+}
+
+fn lua_tag_set_back_and_forth(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.tag_back_and_forth = c.lua_toboolean(s, 1) != 0;
+    return 0;
+}
+
+fn lua_monitor_focus(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const dir: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "FocusMonitor", dir);
+    return 1;
+}
+
+fn lua_monitor_tag(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const dir: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "TagMonitor", dir);
+    return 1;
+}
+
+fn lua_rule_add(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+
+    if (c.lua_type(s, 1) != c.LUA_TTABLE) return 0;
+
+    var rule = Rule{
+        .class = null,
+        .instance = null,
+        .title = null,
+        .tags = 0,
+        .is_floating = false,
+        .monitor = -1,
+    };
+
+    _ = c.lua_getfield(s, 1, "class");
+    if (c.lua_type(s, -1) == c.LUA_TSTRING) {
+        rule.class = get_lua_string(s, -1);
+    }
+    c.lua_settop(s, -2);
+
+    _ = c.lua_getfield(s, 1, "instance");
+    if (c.lua_type(s, -1) == c.LUA_TSTRING) {
+        rule.instance = get_lua_string(s, -1);
+    }
+    c.lua_settop(s, -2);
+
+    _ = c.lua_getfield(s, 1, "title");
+    if (c.lua_type(s, -1) == c.LUA_TSTRING) {
+        rule.title = get_lua_string(s, -1);
+    }
+    c.lua_settop(s, -2);
+
+    _ = c.lua_getfield(s, 1, "tag");
+    if (c.lua_type(s, -1) == c.LUA_TNUMBER) {
+        const tag_idx: i32 = @intCast(c.lua_tointegerx(s, -1, null));
+        if (tag_idx > 0) {
+            rule.tags = @as(u32, 1) << @intCast(tag_idx - 1);
+        }
+    }
+    c.lua_settop(s, -2);
+
+    _ = c.lua_getfield(s, 1, "floating");
+    if (c.lua_type(s, -1) == c.LUA_TBOOLEAN) {
+        rule.is_floating = c.lua_toboolean(s, -1) != 0;
+    }
+    c.lua_settop(s, -2);
+
+    _ = c.lua_getfield(s, 1, "monitor");
+    if (c.lua_type(s, -1) == c.LUA_TNUMBER) {
+        rule.monitor = @intCast(c.lua_tointegerx(s, -1, null));
+    }
+    c.lua_settop(s, -2);
+
+    cfg.add_rule(rule) catch return 0;
+    return 0;
+}
+
+fn lua_bar_set_font(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    if (get_string_arg(s, 1)) |font| {
+        cfg.font = font;
+    }
+    return 0;
+}
+
+fn lua_bar_set_blocks(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+
+    if (c.lua_type(s, 1) != c.LUA_TTABLE) return 0;
+
+    const len = c.lua_rawlen(s, 1);
+    var i: usize = 1;
+    while (i <= len) : (i += 1) {
+        _ = c.lua_rawgeti(s, 1, @intCast(i));
+
+        if (c.lua_type(s, -1) != c.LUA_TTABLE) {
+            c.lua_settop(s, -2);
+            continue;
+        }
+
+        if (parse_block_config(s, -1)) |block| {
+            cfg.add_block(block) catch {};
+        }
+
+        c.lua_settop(s, -2);
+    }
+
+    return 0;
+}
+
+fn parse_block_config(state: *c.lua_State, idx: c_int) ?Block {
+    _ = c.lua_getfield(state, idx, "__block_type");
+    const block_type_str = get_lua_string(state, -1) orelse {
+        c.lua_settop(state, -2);
+        return null;
+    };
+    c.lua_settop(state, -2);
+
+    _ = c.lua_getfield(state, idx, "format");
+    const format = get_lua_string(state, -1) orelse "";
+    c.lua_settop(state, -2);
+
+    _ = c.lua_getfield(state, idx, "interval");
+    const interval: u32 = @intCast(c.lua_tointegerx(state, -1, null));
+    c.lua_settop(state, -2);
+
+    _ = c.lua_getfield(state, idx, "color");
+    const color = parse_color(state, -1);
+    c.lua_settop(state, -2);
+
+    _ = c.lua_getfield(state, idx, "underline");
+    const underline = c.lua_toboolean(state, -1) != 0;
+    c.lua_settop(state, -2);
+
+    var block = Block{
+        .block_type = .static,
+        .format = format,
+        .interval = interval,
+        .color = color,
+        .underline = underline,
+    };
+
+    if (std.mem.eql(u8, block_type_str, "Ram")) {
+        block.block_type = .ram;
+    } else if (std.mem.eql(u8, block_type_str, "DateTime")) {
+        block.block_type = .datetime;
+        _ = c.lua_getfield(state, idx, "__arg");
+        block.datetime_format = get_lua_string(state, -1);
+        c.lua_settop(state, -2);
+    } else if (std.mem.eql(u8, block_type_str, "Shell")) {
+        block.block_type = .shell;
+        _ = c.lua_getfield(state, idx, "__arg");
+        block.command = get_lua_string(state, -1);
+        c.lua_settop(state, -2);
+    } else if (std.mem.eql(u8, block_type_str, "Static")) {
+        block.block_type = .static;
+        _ = c.lua_getfield(state, idx, "__arg");
+        if (get_lua_string(state, -1)) |text| {
+            block.format = text;
+        }
+        c.lua_settop(state, -2);
+    } else if (std.mem.eql(u8, block_type_str, "Battery")) {
+        block.block_type = .battery;
+        _ = c.lua_getfield(state, idx, "__arg");
+        if (c.lua_type(state, -1) == c.LUA_TTABLE) {
+            _ = c.lua_getfield(state, -1, "charging");
+            block.format_charging = get_lua_string(state, -1);
+            c.lua_settop(state, -2);
+
+            _ = c.lua_getfield(state, -1, "discharging");
+            block.format_discharging = get_lua_string(state, -1);
+            c.lua_settop(state, -2);
+
+            _ = c.lua_getfield(state, -1, "full");
+            block.format_full = get_lua_string(state, -1);
+            c.lua_settop(state, -2);
+
+            _ = c.lua_getfield(state, -1, "battery_name");
+            block.battery_name = get_lua_string(state, -1);
+            c.lua_settop(state, -2);
+        }
+        c.lua_settop(state, -2);
+    } else {
+        return null;
+    }
+
+    return block;
+}
+
+fn lua_bar_set_scheme_normal(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.scheme_normal = parse_scheme(s);
+    return 0;
+}
+
+fn lua_bar_set_scheme_selected(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.scheme_selected = parse_scheme(s);
+    return 0;
+}
+
+fn lua_bar_set_scheme_occupied(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.scheme_occupied = parse_scheme(s);
+    return 0;
+}
+
+fn lua_bar_set_scheme_urgent(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.scheme_urgent = parse_scheme(s);
+    return 0;
+}
+
+fn lua_bar_set_hide_vacant_tags(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.hide_vacant_tags = c.lua_toboolean(s, 1) != 0;
+    return 0;
+}
+
+fn lua_bar_block_ram(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_block_table(s, "Ram", null);
+    return 1;
+}
+
+fn lua_bar_block_datetime(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    _ = c.lua_getfield(s, 1, "date_format");
+    const date_format = get_lua_string(s, -1);
+    c.lua_settop(s, -2);
+    create_block_table(s, "DateTime", date_format);
+    return 1;
+}
+
+fn lua_bar_block_shell(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    _ = c.lua_getfield(s, 1, "command");
+    const command = get_lua_string(s, -1);
+    c.lua_settop(s, -2);
+    create_block_table(s, "Shell", command);
+    return 1;
+}
+
+fn lua_bar_block_static(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    _ = c.lua_getfield(s, 1, "text");
+    const text = get_lua_string(s, -1);
+    c.lua_settop(s, -2);
+    create_block_table(s, "Static", text);
+    return 1;
+}
+
+fn lua_bar_block_battery(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+
+    c.lua_createtable(s, 0, 6);
+
+    _ = c.lua_pushstring(s, "Battery");
+    c.lua_setfield(s, -2, "__block_type");
+
+    _ = c.lua_getfield(s, 1, "format");
+    c.lua_setfield(s, -2, "format");
+
+    _ = c.lua_getfield(s, 1, "interval");
+    c.lua_setfield(s, -2, "interval");
+
+    _ = c.lua_getfield(s, 1, "color");
+    c.lua_setfield(s, -2, "color");
+
+    _ = c.lua_getfield(s, 1, "underline");
+    c.lua_setfield(s, -2, "underline");
+
+    c.lua_createtable(s, 0, 4);
+    _ = c.lua_getfield(s, 1, "charging");
+    c.lua_setfield(s, -2, "charging");
+    _ = c.lua_getfield(s, 1, "discharging");
+    c.lua_setfield(s, -2, "discharging");
+    _ = c.lua_getfield(s, 1, "full");
+    c.lua_setfield(s, -2, "full");
+    _ = c.lua_getfield(s, 1, "battery_name");
+    c.lua_setfield(s, -2, "battery_name");
+    c.lua_setfield(s, -2, "__arg");
+
+    return 1;
+}
+
+fn create_block_table(state: *c.lua_State, block_type: [*:0]const u8, arg: ?[]const u8) void {
+    c.lua_createtable(state, 0, 6);
+
+    _ = c.lua_pushstring(state, block_type);
+    c.lua_setfield(state, -2, "__block_type");
+
+    _ = c.lua_getfield(state, 1, "format");
+    c.lua_setfield(state, -2, "format");
+
+    _ = c.lua_getfield(state, 1, "interval");
+    c.lua_setfield(state, -2, "interval");
+
+    _ = c.lua_getfield(state, 1, "color");
+    c.lua_setfield(state, -2, "color");
+
+    _ = c.lua_getfield(state, 1, "underline");
+    c.lua_setfield(state, -2, "underline");
+
+    if (arg) |a| {
+        var buf: [256]u8 = undefined;
+        if (a.len < buf.len) {
+            @memcpy(buf[0..a.len], a);
+            buf[a.len] = 0;
+            _ = c.lua_pushstring(state, &buf);
+            c.lua_setfield(state, -2, "__arg");
+        }
+    }
+}
+
+fn lua_set_terminal(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    if (get_string_arg(s, 1)) |term| {
+        cfg.terminal = term;
+    }
+    return 0;
+}
+
+fn lua_set_modkey(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    if (get_string_arg(s, 1)) |modkey_str| {
+        cfg.modkey = parse_single_modifier(modkey_str);
+    }
+    return 0;
+}
+
+fn lua_set_tags(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+
+    if (c.lua_type(s, 1) != c.LUA_TTABLE) return 0;
+
+    const len = c.lua_rawlen(s, 1);
+    var i: usize = 0;
+    while (i < len and i < 9) : (i += 1) {
+        _ = c.lua_rawgeti(s, 1, @intCast(i + 1));
+        if (get_lua_string(s, -1)) |tag_str| {
+            cfg.tags[i] = tag_str;
+        }
+        c.lua_settop(s, -2);
+    }
+
+    return 0;
+}
+
+fn lua_autostart(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    if (get_string_arg(s, 1)) |cmd| {
+        cfg.add_autostart(cmd) catch return 0;
+    }
+    return 0;
+}
+
+fn lua_auto_tile(state: ?*c.lua_State) callconv(.c) c_int {
+    const cfg = config orelse return 0;
+    const s = state orelse return 0;
+    cfg.auto_tile = c.lua_toboolean(s, 1) != 0;
+    return 0;
+}
+
+fn lua_quit(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "Quit");
+    return 1;
+}
+
+fn lua_restart(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "Restart");
+    return 1;
+}
+
+fn lua_toggle_gaps(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    create_action_table(s, "ToggleGaps");
+    return 1;
+}
+
+fn lua_set_master_factor(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const delta: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    create_action_table_with_int(s, "ResizeMaster", delta);
+    return 1;
+}
+
+fn lua_inc_num_master(state: ?*c.lua_State) callconv(.c) c_int {
+    const s = state orelse return 0;
+    const delta: i32 = @intCast(c.lua_tointegerx(s, 1, null));
+    if (delta > 0) {
+        create_action_table(s, "IncMaster");
+    } else {
+        create_action_table(s, "DecMaster");
+    }
+    return 1;
+}
+
+fn get_string_arg(state: *c.lua_State, idx: c_int) ?[]const u8 {
+    if (c.lua_type(state, idx) != c.LUA_TSTRING) return null;
+    return get_lua_string(state, idx);
+}
+
+fn get_lua_string(state: *c.lua_State, idx: c_int) ?[]const u8 {
+    const cstr = c.lua_tolstring(state, idx, null);
+    if (cstr == null) return null;
+    return std.mem.span(cstr);
+}
+
+fn parse_color(state: *c.lua_State, idx: c_int) u32 {
+    const lua_type = c.lua_type(state, idx);
+    if (lua_type == c.LUA_TNUMBER) {
+        return @intCast(c.lua_tointegerx(state, idx, null));
+    }
+    if (lua_type == c.LUA_TSTRING) {
+        const str = get_lua_string(state, idx) orelse return 0;
+        if (str.len > 0 and str[0] == '#') {
+            return std.fmt.parseInt(u32, str[1..], 16) catch return 0;
+        }
+        if (str.len > 2 and str[0] == '0' and str[1] == 'x') {
+            return std.fmt.parseInt(u32, str[2..], 16) catch return 0;
+        }
+        return std.fmt.parseInt(u32, str, 16) catch return 0;
+    }
+    return 0;
+}
+
+fn parse_scheme(state: *c.lua_State) Color_Scheme {
+    return Color_Scheme{
+        .fg = parse_color(state, 1),
+        .bg = parse_color(state, 2),
+        .border = parse_color(state, 3),
+    };
+}
+
+fn parse_modifiers(state: *c.lua_State, idx: c_int) u32 {
+    var mod_mask: u32 = 0;
+
+    if (c.lua_type(state, idx) != c.LUA_TTABLE) return mod_mask;
+
+    const len = c.lua_rawlen(state, idx);
+    var i: usize = 1;
+    while (i <= len) : (i += 1) {
+        _ = c.lua_rawgeti(state, idx, @intCast(i));
+        if (get_lua_string(state, -1)) |mod_str| {
+            mod_mask |= parse_single_modifier(mod_str);
+        }
+        c.lua_settop(state, -2);
+    }
+
+    return mod_mask;
+}
+
+fn parse_single_modifier(name: []const u8) u32 {
+    if (std.mem.eql(u8, name, "Mod4") or std.mem.eql(u8, name, "mod4") or std.mem.eql(u8, name, "super")) {
+        return (1 << 6);
+    } else if (std.mem.eql(u8, name, "Mod1") or std.mem.eql(u8, name, "mod1") or std.mem.eql(u8, name, "alt")) {
+        return (1 << 3);
+    } else if (std.mem.eql(u8, name, "Shift") or std.mem.eql(u8, name, "shift")) {
+        return (1 << 0);
+    } else if (std.mem.eql(u8, name, "Control") or std.mem.eql(u8, name, "control") or std.mem.eql(u8, name, "ctrl")) {
+        return (1 << 2);
+    }
+    return 0;
+}
+
+fn parse_action(name: []const u8) ?Action {
+    const action_map = .{
+        .{ "Spawn", Action.spawn },
+        .{ "SpawnTerminal", Action.spawn_terminal },
+        .{ "KillClient", Action.kill_client },
+        .{ "Quit", Action.quit },
+        .{ "Restart", Action.restart },
+        .{ "FocusStack", Action.focus_next },
+        .{ "MoveStack", Action.move_next },
+        .{ "ResizeMaster", Action.resize_master },
+        .{ "IncMaster", Action.inc_master },
+        .{ "DecMaster", Action.dec_master },
+        .{ "ToggleFloating", Action.toggle_floating },
+        .{ "ToggleFullScreen", Action.toggle_fullscreen },
+        .{ "ToggleGaps", Action.toggle_gaps },
+        .{ "CycleLayout", Action.cycle_layout },
+        .{ "ChangeLayout", Action.set_layout },
+        .{ "ViewTag", Action.view_tag },
+        .{ "ViewNextTag", Action.view_next_tag },
+        .{ "ViewPreviousTag", Action.view_prev_tag },
+        .{ "ViewNextNonEmptyTag", Action.view_next_nonempty_tag },
+        .{ "ViewPreviousNonEmptyTag", Action.view_prev_nonempty_tag },
+        .{ "MoveToTag", Action.move_to_tag },
+        .{ "ToggleView", Action.toggle_view_tag },
+        .{ "ToggleTag", Action.toggle_tag },
+        .{ "FocusMonitor", Action.focus_monitor },
+        .{ "TagMonitor", Action.send_to_monitor },
+        .{ "ScrollLeft", Action.scroll_left },
+        .{ "ScrollRight", Action.scroll_right },
+    };
+
+    inline for (action_map) |entry| {
+        if (std.mem.eql(u8, name, entry[0])) {
+            return entry[1];
+        }
+    }
+    return null;
+}
+
+fn key_name_to_keysym(name: []const u8) ?u64 {
+    const key_map = .{
+        .{ "Return", 0xff0d },
+        .{ "Enter", 0xff0d },
+        .{ "Tab", 0xff09 },
+        .{ "Escape", 0xff1b },
+        .{ "BackSpace", 0xff08 },
+        .{ "Delete", 0xffff },
+        .{ "space", 0x0020 },
+        .{ "Space", 0x0020 },
+        .{ "comma", 0x002c },
+        .{ "Comma", 0x002c },
+        .{ "period", 0x002e },
+        .{ "Period", 0x002e },
+        .{ "slash", 0x002f },
+        .{ "Slash", 0x002f },
+        .{ "minus", 0x002d },
+        .{ "Minus", 0x002d },
+        .{ "equal", 0x003d },
+        .{ "Equal", 0x003d },
+        .{ "bracketleft", 0x005b },
+        .{ "bracketright", 0x005d },
+        .{ "backslash", 0x005c },
+        .{ "semicolon", 0x003b },
+        .{ "apostrophe", 0x0027 },
+        .{ "grave", 0x0060 },
+        .{ "Left", 0xff51 },
+        .{ "Up", 0xff52 },
+        .{ "Right", 0xff53 },
+        .{ "Down", 0xff54 },
+        .{ "F1", 0xffbe },
+        .{ "F2", 0xffbf },
+        .{ "F3", 0xffc0 },
+        .{ "F4", 0xffc1 },
+        .{ "F5", 0xffc2 },
+        .{ "F6", 0xffc3 },
+        .{ "F7", 0xffc4 },
+        .{ "F8", 0xffc5 },
+        .{ "F9", 0xffc6 },
+        .{ "F10", 0xffc7 },
+        .{ "F11", 0xffc8 },
+        .{ "F12", 0xffc9 },
+        .{ "Print", 0xff61 },
+        .{ "XF86AudioRaiseVolume", 0x1008ff13 },
+        .{ "XF86AudioLowerVolume", 0x1008ff11 },
+        .{ "XF86AudioMute", 0x1008ff12 },
+        .{ "XF86AudioPlay", 0x1008ff14 },
+        .{ "XF86AudioPause", 0x1008ff31 },
+        .{ "XF86AudioNext", 0x1008ff17 },
+        .{ "XF86AudioPrev", 0x1008ff16 },
+        .{ "XF86MonBrightnessUp", 0x1008ff02 },
+        .{ "XF86MonBrightnessDown", 0x1008ff03 },
+    };
+
+    inline for (key_map) |entry| {
+        if (std.mem.eql(u8, name, entry[0])) {
+            return entry[1];
+        }
+    }
+
+    if (name.len == 1) {
+        const char = name[0];
+        if (char >= 'a' and char <= 'z') {
+            return char;
+        }
+        if (char >= 'A' and char <= 'Z') {
+            return char + 32;
+        }
+        if (char >= '0' and char <= '9') {
+            return char;
+        }
+    }
+
+    return null;
+}
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
deleted file mode 100644
index 9685277..0000000
--- a/src/config/lua_api.rs
+++ /dev/null
@@ -1,1033 +0,0 @@
-use mlua::{Lua, Table, Value};
-use std::cell::RefCell;
-use std::rc::Rc;
-
-use crate::ColorScheme;
-use crate::bar::BlockConfig;
-use crate::errors::ConfigError;
-use crate::keyboard::handlers::{Arg, KeyAction, KeyBinding, KeyPress};
-use crate::keyboard::keysyms::{self, Keysym};
-use x11rb::protocol::xproto::KeyButMask;
-
-#[derive(Clone)]
-pub struct ConfigBuilder {
-    pub border_width: u32,
-    pub border_focused: u32,
-    pub border_unfocused: u32,
-    pub font: String,
-    pub gaps_enabled: bool,
-    pub smartgaps_enabled: bool,
-    pub gap_inner_horizontal: u32,
-    pub gap_inner_vertical: u32,
-    pub gap_outer_horizontal: u32,
-    pub gap_outer_vertical: u32,
-    pub terminal: String,
-    pub modkey: KeyButMask,
-    pub tags: Vec<String>,
-    pub layout_symbols: Vec<crate::LayoutSymbolOverride>,
-    pub keybindings: Vec<KeyBinding>,
-    pub tag_back_and_forth: bool,
-    pub window_rules: Vec<crate::WindowRule>,
-    pub status_blocks: Vec<BlockConfig>,
-    pub scheme_normal: ColorScheme,
-    pub scheme_occupied: ColorScheme,
-    pub scheme_selected: ColorScheme,
-    pub scheme_urgent: ColorScheme,
-    pub autostart: Vec<String>,
-    pub auto_tile: bool,
-    pub hide_vacant_tags: bool,
-}
-
-impl Default for ConfigBuilder {
-    fn default() -> Self {
-        Self {
-            border_width: 2,
-            border_focused: 0x6dade3,
-            border_unfocused: 0xbbbbbb,
-            font: "monospace:style=Bold:size=10".to_string(),
-            gaps_enabled: true,
-            smartgaps_enabled: true,
-            gap_inner_horizontal: 5,
-            gap_inner_vertical: 5,
-            gap_outer_horizontal: 5,
-            gap_outer_vertical: 5,
-            terminal: "st".to_string(),
-            modkey: KeyButMask::MOD4,
-            tags: vec!["1".into(), "2".into(), "3".into()],
-            layout_symbols: Vec::new(),
-            keybindings: Vec::new(),
-            tag_back_and_forth: false,
-            window_rules: Vec::new(),
-            status_blocks: Vec::new(),
-            scheme_normal: ColorScheme {
-                foreground: 0xffffff,
-                background: 0x000000,
-                underline: 0x444444,
-            },
-            scheme_occupied: ColorScheme {
-                foreground: 0xffffff,
-                background: 0x000000,
-                underline: 0x444444,
-            },
-            scheme_selected: ColorScheme {
-                foreground: 0xffffff,
-                background: 0x000000,
-                underline: 0x444444,
-            },
-            scheme_urgent: ColorScheme {
-                foreground: 0xff5555,
-                background: 0x000000,
-                underline: 0xff5555,
-            },
-            autostart: Vec::new(),
-            auto_tile: false,
-            hide_vacant_tags: false,
-        }
-    }
-}
-
-type SharedBuilder = Rc<RefCell<ConfigBuilder>>;
-
-pub fn register_api(lua: &Lua) -> Result<SharedBuilder, ConfigError> {
-    let builder = Rc::new(RefCell::new(ConfigBuilder::default()));
-
-    let oxwm_table = lua.create_table()?;
-
-    register_spawn(lua, &oxwm_table, builder.clone())?;
-    register_key_module(lua, &oxwm_table, builder.clone())?;
-    register_gaps_module(lua, &oxwm_table, builder.clone())?;
-    register_border_module(lua, &oxwm_table, builder.clone())?;
-    register_client_module(lua, &oxwm_table)?;
-    register_layout_module(lua, &oxwm_table)?;
-    register_tag_module(lua, &oxwm_table, builder.clone())?;
-    register_monitor_module(lua, &oxwm_table)?;
-    register_rule_module(lua, &oxwm_table, builder.clone())?;
-    register_bar_module(lua, &oxwm_table, builder.clone())?;
-    register_misc(lua, &oxwm_table, builder.clone())?;
-
-    lua.globals().set("oxwm", oxwm_table)?;
-
-    Ok(builder)
-}
-
-fn register_spawn(lua: &Lua, parent: &Table, _builder: SharedBuilder) -> Result<(), ConfigError> {
-    let spawn = lua.create_function(|lua, cmd: Value| create_action_table(lua, "Spawn", cmd))?;
-    let spawn_terminal =
-        lua.create_function(|lua, ()| create_action_table(lua, "SpawnTerminal", Value::Nil))?;
-    parent.set("spawn", spawn)?;
-    parent.set("spawn_terminal", spawn_terminal)?;
-    Ok(())
-}
-
-fn register_key_module(
-    lua: &Lua,
-    parent: &Table,
-    builder: SharedBuilder,
-) -> Result<(), ConfigError> {
-    let key_table = lua.create_table()?;
-
-    let builder_clone = builder.clone();
-    let bind = lua.create_function(move |lua, (mods, key, action): (Value, String, Value)| {
-        let modifiers = parse_modifiers_value(lua, mods)?;
-        let keysym = parse_keysym(&key)?;
-        let (key_action, arg) = parse_action_value(lua, action)?;
-
-        let binding = KeyBinding::single_key(modifiers, keysym, key_action, arg);
-        builder_clone.borrow_mut().keybindings.push(binding);
-
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let chord = lua.create_function(move |lua, (keys, action): (Table, Value)| {
-        let mut key_presses = Vec::new();
-
-        for i in 1..=keys.len()? {
-            let key_spec: Table = keys.get(i)?;
-            let mods: Value = key_spec.get(1)?;
-            let key: String = key_spec.get(2)?;
-
-            let modifiers = parse_modifiers_value(lua, mods)?;
-            let keysym = parse_keysym(&key)?;
-
-            key_presses.push(KeyPress { modifiers, keysym });
-        }
-
-        let (key_action, arg) = parse_action_value(lua, action)?;
-        let binding = KeyBinding::new(key_presses, key_action, arg);
-        builder_clone.borrow_mut().keybindings.push(binding);
-
-        Ok(())
-    })?;
-
-    key_table.set("bind", bind)?;
-    key_table.set("chord", chord)?;
-    parent.set("key", key_table)?;
-    Ok(())
-}
-
-fn register_gaps_module(
-    lua: &Lua,
-    parent: &Table,
-    builder: SharedBuilder,
-) -> Result<(), ConfigError> {
-    let gaps_table = lua.create_table()?;
-
-    let builder_clone = builder.clone();
-    let set_enabled = lua.create_function(move |_, enabled: bool| {
-        builder_clone.borrow_mut().gaps_enabled = enabled;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let enable = lua.create_function(move |_, ()| {
-        builder_clone.borrow_mut().gaps_enabled = true;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let disable = lua.create_function(move |_, ()| {
-        builder_clone.borrow_mut().gaps_enabled = false;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_inner = lua.create_function(move |_, (h, v): (u32, u32)| {
-        let mut b = builder_clone.borrow_mut();
-        b.gap_inner_horizontal = h;
-        b.gap_inner_vertical = v;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_outer = lua.create_function(move |_, (h, v): (u32, u32)| {
-        let mut b = builder_clone.borrow_mut();
-        b.gap_outer_horizontal = h;
-        b.gap_outer_vertical = v;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_smart = lua.create_function(move |_, enabled: bool| {
-        builder_clone.borrow_mut().smartgaps_enabled = enabled;
-        Ok(())
-    })?;
-
-    gaps_table.set("set_enabled", set_enabled)?;
-    gaps_table.set("enable", enable)?;
-    gaps_table.set("disable", disable)?;
-    gaps_table.set("set_inner", set_inner)?;
-    gaps_table.set("set_outer", set_outer)?;
-    gaps_table.set("set_smart", set_smart)?;
-    parent.set("gaps", gaps_table)?;
-    Ok(())
-}
-
-fn register_border_module(
-    lua: &Lua,
-    parent: &Table,
-    builder: SharedBuilder,
-) -> Result<(), ConfigError> {
-    let border_table = lua.create_table()?;
-
-    let builder_clone = builder.clone();
-    let set_width = lua.create_function(move |_, width: u32| {
-        builder_clone.borrow_mut().border_width = width;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_focused_color = lua.create_function(move |_, color: Value| {
-        let color_u32 = parse_color_value(color)?;
-        builder_clone.borrow_mut().border_focused = color_u32;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_unfocused_color = lua.create_function(move |_, color: Value| {
-        let color_u32 = parse_color_value(color)?;
-        builder_clone.borrow_mut().border_unfocused = color_u32;
-        Ok(())
-    })?;
-
-    border_table.set("set_width", set_width)?;
-    border_table.set("set_focused_color", set_focused_color)?;
-    border_table.set("set_unfocused_color", set_unfocused_color)?;
-    parent.set("border", border_table)?;
-    Ok(())
-}
-
-fn register_client_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
-    let client_table = lua.create_table()?;
-
-    let kill = lua.create_function(|lua, ()| create_action_table(lua, "KillClient", Value::Nil))?;
-
-    let toggle_fullscreen =
-        lua.create_function(|lua, ()| create_action_table(lua, "ToggleFullScreen", Value::Nil))?;
-
-    let toggle_floating =
-        lua.create_function(|lua, ()| create_action_table(lua, "ToggleFloating", Value::Nil))?;
-
-    let focus_stack = lua.create_function(|lua, dir: i32| {
-        create_action_table(lua, "FocusStack", Value::Integer(dir as i64))
-    })?;
-
-    let move_stack = lua.create_function(|lua, dir: i32| {
-        create_action_table(lua, "MoveStack", Value::Integer(dir as i64))
-    })?;
-
-    client_table.set("kill", kill)?;
-    client_table.set("toggle_fullscreen", toggle_fullscreen)?;
-    client_table.set("toggle_floating", toggle_floating)?;
-    client_table.set("focus_stack", focus_stack)?;
-    client_table.set("move_stack", move_stack)?;
-
-    parent.set("client", client_table)?;
-    Ok(())
-}
-
-fn register_layout_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
-    let layout_table = lua.create_table()?;
-
-    let cycle =
-        lua.create_function(|lua, ()| create_action_table(lua, "CycleLayout", Value::Nil))?;
-
-    let set = lua.create_function(|lua, name: String| {
-        create_action_table(
-            lua,
-            "ChangeLayout",
-            Value::String(lua.create_string(&name)?),
-        )
-    })?;
-
-    let scroll_left =
-        lua.create_function(|lua, ()| create_action_table(lua, "ScrollLeft", Value::Nil))?;
-
-    let scroll_right =
-        lua.create_function(|lua, ()| create_action_table(lua, "ScrollRight", Value::Nil))?;
-
-    layout_table.set("cycle", cycle)?;
-    layout_table.set("set", set)?;
-    layout_table.set("scroll_left", scroll_left)?;
-    layout_table.set("scroll_right", scroll_right)?;
-    parent.set("layout", layout_table)?;
-    Ok(())
-}
-
-fn register_tag_module(
-    lua: &Lua,
-    parent: &Table,
-    builder: SharedBuilder,
-) -> Result<(), ConfigError> {
-    let builder_clone = builder.clone();
-    let tag_table = lua.create_table()?;
-
-    let view = lua.create_function(|lua, idx: i32| {
-        create_action_table(lua, "ViewTag", Value::Integer(idx as i64))
-    })?;
-
-    let view_next =
-        lua.create_function(|lua, ()| create_action_table(lua, "ViewNextTag", Value::Nil))?;
-
-    let view_previous =
-        lua.create_function(|lua, ()| create_action_table(lua, "ViewPreviousTag", Value::Nil))?;
-
-    let view_next_nonempty =
-        lua.create_function(|lua, ()| create_action_table(lua, "ViewNextNonEmptyTag", Value::Nil))?;
-
-    let view_previous_nonempty = lua.create_function(|lua, ()| {
-        create_action_table(lua, "ViewPreviousNonEmptyTag", Value::Nil)
-    })?;
-
-    let toggleview = lua.create_function(|lua, idx: i32| {
-        create_action_table(lua, "ToggleView", Value::Integer(idx as i64))
-    })?;
-
-    let move_to = lua.create_function(|lua, idx: i32| {
-        create_action_table(lua, "MoveToTag", Value::Integer(idx as i64))
-    })?;
-
-    let toggletag = lua.create_function(|lua, idx: i32| {
-        create_action_table(lua, "ToggleTag", Value::Integer(idx as i64))
-    })?;
-
-    let set_back_and_forth = lua.create_function(move |_, enabled: bool| {
-        builder_clone.borrow_mut().tag_back_and_forth = enabled;
-        Ok(())
-    })?;
-
-    tag_table.set("view", view)?;
-    tag_table.set("view_next", view_next)?;
-    tag_table.set("view_previous", view_previous)?;
-    tag_table.set("view_next_nonempty", view_next_nonempty)?;
-    tag_table.set("view_previous_nonempty", view_previous_nonempty)?;
-    tag_table.set("toggleview", toggleview)?;
-    tag_table.set("move_to", move_to)?;
-    tag_table.set("toggletag", toggletag)?;
-    tag_table.set("set_back_and_forth", set_back_and_forth)?;
-    parent.set("tag", tag_table)?;
-    Ok(())
-}
-
-fn register_monitor_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
-    let monitor_table = lua.create_table()?;
-
-    let focus = lua.create_function(|lua, direction: i64| {
-        create_action_table(lua, "FocusMonitor", Value::Integer(direction))
-    })?;
-
-    let tag = lua.create_function(|lua, direction: i64| {
-        create_action_table(lua, "TagMonitor", Value::Integer(direction))
-    })?;
-
-    monitor_table.set("focus", focus)?;
-    monitor_table.set("tag", tag)?;
-    parent.set("monitor", monitor_table)?;
-    Ok(())
-}
-
-fn register_rule_module(
-    lua: &Lua,
-    parent: &Table,
-    builder: SharedBuilder,
-) -> Result<(), ConfigError> {
-    let rule_table = lua.create_table()?;
-
-    let builder_clone = builder.clone();
-    let add = lua.create_function(move |_, config: Table| {
-        let class: Option<String> = config.get("class").ok();
-        let instance: Option<String> = config.get("instance").ok();
-        let title: Option<String> = config.get("title").ok();
-        let is_floating: Option<bool> = config.get("floating").ok();
-        let monitor: Option<usize> = config.get("monitor").ok();
-        let focus: Option<bool> = config.get("focus").ok();
-
-        let tags: Option<u32> = if let Ok(tag_index) = config.get::<i32>("tag") {
-            if tag_index > 0 {
-                Some(1 << (tag_index - 1))
-            } else {
-                None
-            }
-        } else {
-            None
-        };
-
-        let rule = crate::WindowRule {
-            class,
-            instance,
-            title,
-            tags,
-            focus,
-            is_floating,
-            monitor,
-        };
-
-        builder_clone.borrow_mut().window_rules.push(rule);
-        Ok(())
-    })?;
-
-    rule_table.set("add", add)?;
-    parent.set("rule", rule_table)?;
-    Ok(())
-}
-
-fn register_bar_module(
-    lua: &Lua,
-    parent: &Table,
-    builder: SharedBuilder,
-) -> Result<(), ConfigError> {
-    let bar_table = lua.create_table()?;
-
-    let builder_clone = builder.clone();
-    let set_font = lua.create_function(move |_, font: String| {
-        builder_clone.borrow_mut().font = font;
-        Ok(())
-    })?;
-
-    let block_table = lua.create_table()?;
-
-    let ram =
-        lua.create_function(|lua, config: Table| create_block_config(lua, config, "Ram", None))?;
-
-    let datetime = lua.create_function(|lua, config: Table| {
-        let date_format: String = config.get("date_format").map_err(|_| {
-            mlua::Error::RuntimeError(
-                "oxwm.bar.block.datetime: 'date_format' field is required (e.g., '%H:%M')".into(),
-            )
-        })?;
-        create_block_config(
-            lua,
-            config,
-            "DateTime",
-            Some(Value::String(lua.create_string(&date_format)?)),
-        )
-    })?;
-
-    let shell = lua.create_function(|lua, config: Table| {
-        let command: String = config.get("command").map_err(|_| {
-            mlua::Error::RuntimeError("oxwm.bar.block.shell: 'command' field is required".into())
-        })?;
-        create_block_config(
-            lua,
-            config,
-            "Shell",
-            Some(Value::String(lua.create_string(&command)?)),
-        )
-    })?;
-
-    let static_block = lua.create_function(|lua, config: Table| {
-        let text: String = config.get("text").map_err(|_| {
-            mlua::Error::RuntimeError("oxwm.bar.block.static: 'text' field is required".into())
-        })?;
-        create_block_config(
-            lua,
-            config,
-            "Static",
-            Some(Value::String(lua.create_string(&text)?)),
-        )
-    })?;
-
-    let battery = lua.create_function(|lua, config: Table| {
-        let charging: String = config.get("charging").map_err(|_| {
-            mlua::Error::RuntimeError("oxwm.bar.block.battery: 'charging' field is required".into())
-        })?;
-        let discharging: String = config.get("discharging").map_err(|_| {
-            mlua::Error::RuntimeError(
-                "oxwm.bar.block.battery: 'discharging' field is required".into(),
-            )
-        })?;
-        let full: String = config.get("full").map_err(|_| {
-            mlua::Error::RuntimeError("oxwm.bar.block.battery: 'full' field is required".into())
-        })?;
-        let battery_name: Option<String> = config.get("battery_name").unwrap_or(None);
-
-        let formats_table = lua.create_table()?;
-        formats_table.set("charging", charging)?;
-        formats_table.set("discharging", discharging)?;
-        formats_table.set("full", full)?;
-        formats_table.set("battery_name", battery_name)?;
-
-        create_block_config(lua, config, "Battery", Some(Value::Table(formats_table)))
-    })?;
-
-    block_table.set("ram", ram)?;
-    block_table.set("datetime", datetime)?;
-    block_table.set("shell", shell)?;
-    block_table.set("static", static_block)?;
-    block_table.set("battery", battery)?;
-
-    // Deprecated add_block() function for backwards compatibility
-    // This allows old configs to still work, but users should migrate to set_blocks()
-    let builder_clone = builder.clone();
-    let add_block = lua.create_function(move |_, (format, block_type, arg, interval, color, underline): (String, String, Value, u64, Value, Option<bool>)| -> mlua::Result<()> {
-        eprintln!("WARNING: oxwm.bar.add_block() is deprecated. Please migrate to oxwm.bar.set_blocks() with block constructors.");
-        eprintln!("See the migration guide for details.");
-
-        let cmd = match block_type.as_str() {
-            "DateTime" => {
-                let fmt = if let Value::String(s) = arg {
-                    s.to_str()?.to_string()
-                } else {
-                    return Err(mlua::Error::RuntimeError("DateTime block requires format string as third argument".into()));
-                };
-                crate::bar::BlockCommand::DateTime(fmt)
-            }
-            "Shell" => {
-                let cmd_str = if let Value::String(s) = arg {
-                    s.to_str()?.to_string()
-                } else {
-                    return Err(mlua::Error::RuntimeError("Shell block requires command string as third argument".into()));
-                };
-                crate::bar::BlockCommand::Shell(cmd_str)
-            }
-            "Ram" => crate::bar::BlockCommand::Ram,
-            "Static" => {
-                let text = if let Value::String(s) = arg {
-                    s.to_str()?.to_string()
-                } else {
-                    String::new()
-                };
-                crate::bar::BlockCommand::Static(text)
-            }
-            "Battery" => {
-                return Err(mlua::Error::RuntimeError(
-                    "Battery block is not supported with add_block(). Please use oxwm.bar.set_blocks() with oxwm.bar.block.battery()".into()
-                ));
-            }
-            _ => return Err(mlua::Error::RuntimeError(format!("Unknown block type '{}'", block_type))),
-        };
-
-        let color_u32 = parse_color_value(color)?;
-
-        let block = crate::bar::BlockConfig {
-            format,
-            command: cmd,
-            interval_secs: interval,
-            color: color_u32,
-            underline: underline.unwrap_or(false),
-        };
-
-        builder_clone.borrow_mut().status_blocks.push(block);
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_blocks = lua.create_function(move |_, blocks: Table| {
-        use crate::bar::BlockCommand;
-
-        let mut block_configs = Vec::new();
-
-        for i in 1..=blocks.len()? {
-            let block_table: Table = blocks.get(i)?;
-            let block_type: String = block_table.get("__block_type")?;
-            let format: String = block_table.get("format").unwrap_or_default();
-            let interval: u64 = block_table.get("interval")?;
-            let color_val: Value = block_table.get("color")?;
-            let underline: bool = block_table.get("underline").unwrap_or(false);
-            let arg: Option<Value> = block_table.get("__arg").ok();
-
-            let cmd = match block_type.as_str() {
-                "DateTime" => {
-                    let fmt = arg
-                        .and_then(|v| {
-                            if let Value::String(s) = v {
-                                s.to_str().ok().map(|s| s.to_string())
-                            } else {
-                                None
-                            }
-                        })
-                        .ok_or_else(|| {
-                            mlua::Error::RuntimeError("DateTime block missing format".into())
-                        })?;
-                    BlockCommand::DateTime(fmt)
-                }
-                "Shell" => {
-                    let cmd_str = arg
-                        .and_then(|v| {
-                            if let Value::String(s) = v {
-                                s.to_str().ok().map(|s| s.to_string())
-                            } else {
-                                None
-                            }
-                        })
-                        .ok_or_else(|| {
-                            mlua::Error::RuntimeError("Shell block missing command".into())
-                        })?;
-                    BlockCommand::Shell(cmd_str)
-                }
-                "Ram" => BlockCommand::Ram,
-                "Static" => {
-                    let text = arg
-                        .and_then(|v| {
-                            if let Value::String(s) = v {
-                                s.to_str().ok().map(|s| s.to_string())
-                            } else {
-                                None
-                            }
-                        })
-                        .unwrap_or_default();
-                    BlockCommand::Static(text)
-                }
-                "Battery" => {
-                    let formats = arg
-                        .and_then(|v| {
-                            if let Value::Table(t) = v {
-                                Some(t)
-                            } else {
-                                None
-                            }
-                        })
-                        .ok_or_else(|| {
-                            mlua::Error::RuntimeError("Battery block missing formats".into())
-                        })?;
-
-                    let charging: String = formats.get("charging")?;
-                    let discharging: String = formats.get("discharging")?;
-                    let full: String = formats.get("full")?;
-                    let battery_name: Option<String> = formats.get("battery_name").unwrap_or(None);
-
-                    BlockCommand::Battery {
-                        format_charging: charging,
-                        format_discharging: discharging,
-                        format_full: full,
-                        battery_name,
-                    }
-                }
-                _ => {
-                    return Err(mlua::Error::RuntimeError(format!(
-                        "Unknown block type '{}'",
-                        block_type
-                    )));
-                }
-            };
-
-            let color_u32 = parse_color_value(color_val)?;
-
-            let block = crate::bar::BlockConfig {
-                format,
-                command: cmd,
-                interval_secs: interval,
-                color: color_u32,
-                underline,
-            };
-
-            block_configs.push(block);
-        }
-
-        builder_clone.borrow_mut().status_blocks = block_configs;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_scheme_normal =
-        lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
-            let foreground = parse_color_value(fg)?;
-            let background = parse_color_value(bg)?;
-            let underline = parse_color_value(ul)?;
-
-            builder_clone.borrow_mut().scheme_normal = ColorScheme {
-                foreground,
-                background,
-                underline,
-            };
-            Ok(())
-        })?;
-
-    let builder_clone = builder.clone();
-    let set_scheme_occupied =
-        lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
-            let foreground = parse_color_value(fg)?;
-            let background = parse_color_value(bg)?;
-            let underline = parse_color_value(ul)?;
-
-            builder_clone.borrow_mut().scheme_occupied = ColorScheme {
-                foreground,
-                background,
-                underline,
-            };
-            Ok(())
-        })?;
-
-    let builder_clone = builder.clone();
-    let set_scheme_selected =
-        lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
-            let foreground = parse_color_value(fg)?;
-            let background = parse_color_value(bg)?;
-            let underline = parse_color_value(ul)?;
-
-            builder_clone.borrow_mut().scheme_selected = ColorScheme {
-                foreground,
-                background,
-                underline,
-            };
-            Ok(())
-        })?;
-
-    let builder_clone = builder.clone();
-    let set_scheme_urgent =
-        lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
-            let foreground = parse_color_value(fg)?;
-            let background = parse_color_value(bg)?;
-            let underline = parse_color_value(ul)?;
-
-            builder_clone.borrow_mut().scheme_urgent = ColorScheme {
-                foreground,
-                background,
-                underline,
-            };
-            Ok(())
-        })?;
-
-    let builder_clone = builder.clone();
-    let set_hide_vacant_tags = lua.create_function(move |_, hide: bool| {
-        builder_clone.borrow_mut().hide_vacant_tags = hide;
-        Ok(())
-    })?;
-
-    bar_table.set("set_font", set_font)?;
-    bar_table.set("block", block_table)?;
-    bar_table.set("add_block", add_block)?; // Deprecated, for backwards compatibility
-    bar_table.set("set_blocks", set_blocks)?;
-    bar_table.set("set_scheme_normal", set_scheme_normal)?;
-    bar_table.set("set_scheme_occupied", set_scheme_occupied)?;
-    bar_table.set("set_scheme_selected", set_scheme_selected)?;
-    bar_table.set("set_scheme_urgent", set_scheme_urgent)?;
-    bar_table.set("set_hide_vacant_tags", set_hide_vacant_tags)?;
-    parent.set("bar", bar_table)?;
-    Ok(())
-}
-
-fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<(), ConfigError> {
-    let builder_clone = builder.clone();
-    let set_terminal = lua.create_function(move |_, term: String| {
-        builder_clone.borrow_mut().terminal = term;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_modkey = lua.create_function(move |_, modkey_str: String| {
-        let modkey = parse_modkey_string(&modkey_str)
-            .map_err(|e| mlua::Error::RuntimeError(format!("{}", e)))?;
-        builder_clone.borrow_mut().modkey = modkey;
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_tags = lua.create_function(move |_, tags: Vec<String>| {
-        builder_clone.borrow_mut().tags = tags;
-        Ok(())
-    })?;
-
-    let quit = lua.create_function(|lua, ()| create_action_table(lua, "Quit", Value::Nil))?;
-
-    let restart = lua.create_function(|lua, ()| create_action_table(lua, "Restart", Value::Nil))?;
-
-    let toggle_gaps =
-        lua.create_function(|lua, ()| create_action_table(lua, "ToggleGaps", Value::Nil))?;
-
-    let set_master_factor = lua.create_function(|lua, delta: i32| {
-        create_action_table(lua, "SetMasterFactor", Value::Integer(delta as i64))
-    })?;
-
-    let inc_num_master = lua.create_function(|lua, delta: i32| {
-        create_action_table(lua, "IncNumMaster", Value::Integer(delta as i64))
-    })?;
-
-    let show_keybinds =
-        lua.create_function(|lua, ()| create_action_table(lua, "ShowKeybindOverlay", Value::Nil))?;
-
-    let focus_monitor = lua.create_function(|lua, idx: i32| {
-        create_action_table(lua, "FocusMonitor", Value::Integer(idx as i64))
-    })?;
-
-    let builder_clone = builder.clone();
-    let set_layout_symbol = lua.create_function(move |_, (name, symbol): (String, String)| {
-        builder_clone
-            .borrow_mut()
-            .layout_symbols
-            .push(crate::LayoutSymbolOverride { name, symbol });
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let autostart = lua.create_function(move |_, cmd: String| {
-        builder_clone.borrow_mut().autostart.push(cmd);
-        Ok(())
-    })?;
-
-    let builder_clone = builder.clone();
-    let auto_tile = lua.create_function(move |_, enabled: bool| {
-        builder_clone.borrow_mut().auto_tile = enabled;
-        Ok(())
-    })?;
-
-    parent.set("set_terminal", set_terminal)?;
-    parent.set("set_modkey", set_modkey)?;
-    parent.set("set_tags", set_tags)?;
-    parent.set("set_layout_symbol", set_layout_symbol)?;
-    parent.set("autostart", autostart)?;
-    parent.set("quit", quit)?;
-    parent.set("restart", restart)?;
-    parent.set("toggle_gaps", toggle_gaps)?;
-    parent.set("set_master_factor", set_master_factor)?;
-    parent.set("inc_num_master", inc_num_master)?;
-    parent.set("show_keybinds", show_keybinds)?;
-    parent.set("focus_monitor", focus_monitor)?;
-    parent.set("auto_tile", auto_tile)?;
-    Ok(())
-}
-
-fn parse_modifiers_value(_lua: &Lua, value: Value) -> mlua::Result<Vec<KeyButMask>> {
-    match value {
-        Value::Table(t) => {
-            let mut mods = Vec::new();
-            for i in 1..=t.len()? {
-                let mod_str: String = t.get(i)?;
-                let mask = parse_modkey_string(&mod_str)
-                    .map_err(|e| mlua::Error::RuntimeError(format!("oxwm.key.bind: invalid modifier - {}", e)))?;
-                mods.push(mask);
-            }
-            Ok(mods)
-        }
-        Value::String(s) => {
-            let s_str = s.to_str()?;
-            let mask = parse_modkey_string(&s_str)
-                .map_err(|e| mlua::Error::RuntimeError(format!("oxwm.key.bind: invalid modifier - {}", e)))?;
-            Ok(vec![mask])
-        }
-        _ => Err(mlua::Error::RuntimeError(
-            "oxwm.key.bind: first argument must be a table of modifiers like {\"Mod4\"} or {\"Mod4\", \"Shift\"}".into(),
-        )),
-    }
-}
-
-fn parse_modkey_string(s: &str) -> Result<KeyButMask, ConfigError> {
-    match s {
-        "Mod1" => Ok(KeyButMask::MOD1),
-        "Mod2" => Ok(KeyButMask::MOD2),
-        "Mod3" => Ok(KeyButMask::MOD3),
-        "Mod4" => Ok(KeyButMask::MOD4),
-        "Mod5" => Ok(KeyButMask::MOD5),
-        "Shift" => Ok(KeyButMask::SHIFT),
-        "Control" => Ok(KeyButMask::CONTROL),
-        _ => Err(ConfigError::InvalidModkey(format!(
-            "'{}' is not a valid modifier. Use one of: Mod1, Mod4, Shift, Control",
-            s
-        ))),
-    }
-}
-
-fn parse_keysym(key: &str) -> mlua::Result<Keysym> {
-    keysyms::keysym_from_str(key)
-        .ok_or_else(|| mlua::Error::RuntimeError(format!("unknown key '{}'. valid keys include: Return, Space, A-Z, 0-9, F1-F12, Left, Right, Up, Down, etc. check oxwm.lua type definitions for the complete list", key)))
-}
-
-fn parse_action_value(_lua: &Lua, value: Value) -> mlua::Result<(KeyAction, Arg)> {
-    match value {
-        Value::Function(_) => {
-            Err(mlua::Error::RuntimeError(
-                "action must be a function call, not a function reference. did you forget ()? example: oxwm.spawn('st') not oxwm.spawn".into()
-            ))
-        }
-        Value::Table(t) => {
-            if let Ok(action_name) = t.get::<String>("__action") {
-                let action = string_to_action(&action_name)?;
-                let arg = if let Ok(arg_val) = t.get::<Value>("__arg") {
-                    value_to_arg(arg_val)?
-                } else {
-                    Arg::None
-                };
-                return Ok((action, arg));
-            }
-
-            Err(mlua::Error::RuntimeError(
-                "action must be a table returned by oxwm functions like oxwm.spawn(), oxwm.client.kill(), oxwm.quit(), etc.".into(),
-            ))
-        }
-        _ => Err(mlua::Error::RuntimeError(
-            "action must be a table returned by oxwm functions like oxwm.spawn(), oxwm.client.kill(), oxwm.quit(), etc.".into(),
-        )),
-    }
-}
-
-fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
-    match s {
-        "Spawn" => Ok(KeyAction::Spawn),
-        "SpawnTerminal" => Ok(KeyAction::SpawnTerminal),
-        "KillClient" => Ok(KeyAction::KillClient),
-        "FocusStack" => Ok(KeyAction::FocusStack),
-        "MoveStack" => Ok(KeyAction::MoveStack),
-        "Quit" => Ok(KeyAction::Quit),
-        "Restart" => Ok(KeyAction::Restart),
-        "ViewTag" => Ok(KeyAction::ViewTag),
-        "ViewNextTag" => Ok(KeyAction::ViewNextTag),
-        "ViewPreviousTag" => Ok(KeyAction::ViewPreviousTag),
-        "ViewNextNonEmptyTag" => Ok(KeyAction::ViewNextNonEmptyTag),
-        "ViewPreviousNonEmptyTag" => Ok(KeyAction::ViewPreviousNonEmptyTag),
-        "ToggleView" => Ok(KeyAction::ToggleView),
-        "MoveToTag" => Ok(KeyAction::MoveToTag),
-        "ToggleTag" => Ok(KeyAction::ToggleTag),
-        "ToggleGaps" => Ok(KeyAction::ToggleGaps),
-        "SetMasterFactor" => Ok(KeyAction::SetMasterFactor),
-        "IncNumMaster" => Ok(KeyAction::IncNumMaster),
-        "ToggleFullScreen" => Ok(KeyAction::ToggleFullScreen),
-        "ToggleFloating" => Ok(KeyAction::ToggleFloating),
-        "ChangeLayout" => Ok(KeyAction::ChangeLayout),
-        "CycleLayout" => Ok(KeyAction::CycleLayout),
-        "FocusMonitor" => Ok(KeyAction::FocusMonitor),
-        "TagMonitor" => Ok(KeyAction::TagMonitor),
-        "ShowKeybindOverlay" => Ok(KeyAction::ShowKeybindOverlay),
-        "ScrollLeft" => Ok(KeyAction::ScrollLeft),
-        "ScrollRight" => Ok(KeyAction::ScrollRight),
-        _ => Err(mlua::Error::RuntimeError(format!(
-            "unknown action '{}'. this is an internal error, please report it",
-            s
-        ))),
-    }
-}
-
-fn value_to_arg(value: Value) -> mlua::Result<Arg> {
-    match value {
-        Value::Nil => Ok(Arg::None),
-        Value::String(s) => Ok(Arg::Str(s.to_str()?.to_string())),
-        Value::Integer(i) => Ok(Arg::Int(i as i32)),
-        Value::Number(n) => Ok(Arg::Int(n as i32)),
-        Value::Table(t) => {
-            let mut arr = Vec::new();
-            for i in 1..=t.len()? {
-                let item: String = t.get(i)?;
-                arr.push(item);
-            }
-            Ok(Arg::Array(arr))
-        }
-        _ => Ok(Arg::None),
-    }
-}
-
-fn create_action_table(lua: &Lua, action_name: &str, arg: Value) -> mlua::Result<Table> {
-    let table = lua.create_table()?;
-    table.set("__action", action_name)?;
-    table.set("__arg", arg)?;
-    Ok(table)
-}
-
-fn parse_color_value(value: Value) -> mlua::Result<u32> {
-    match value {
-        Value::Integer(i) => Ok(i as u32),
-        Value::Number(n) => Ok(n as u32),
-        Value::String(s) => {
-            let s = s.to_str()?;
-            if let Some(hex) = s.strip_prefix('#') {
-                u32::from_str_radix(hex, 16).map_err(|e| {
-                    mlua::Error::RuntimeError(format!(
-                        "invalid hex color '{}': {}. use format like #ff0000 or 0xff0000",
-                        s, e
-                    ))
-                })
-            } else if let Some(hex) = s.strip_prefix("0x") {
-                u32::from_str_radix(hex, 16).map_err(|e| {
-                    mlua::Error::RuntimeError(format!(
-                        "invalid hex color '{}': {}. use format like 0xff0000 or #ff0000",
-                        s, e
-                    ))
-                })
-            } else {
-                s.parse::<u32>().map_err(|e| {
-                    mlua::Error::RuntimeError(format!(
-                        "invalid color '{}': {}. use hex format like 0xff0000 or #ff0000",
-                        s, e
-                    ))
-                })
-            }
-        }
-        _ => Err(mlua::Error::RuntimeError(
-            "color must be a number (0xff0000) or string ('#ff0000' or '0xff0000')".into(),
-        )),
-    }
-}
-
-fn create_block_config(
-    lua: &Lua,
-    config: Table,
-    block_type: &str,
-    arg: Option<Value>,
-) -> mlua::Result<Table> {
-    let table = lua.create_table()?;
-    table.set("__block_type", block_type)?;
-
-    let format: String = config.get("format").unwrap_or_default();
-    let interval: u64 = config.get("interval")?;
-    let color: Value = config.get("color")?;
-    let underline: bool = config.get("underline").unwrap_or(false);
-
-    table.set("format", format)?;
-    table.set("interval", interval)?;
-    table.set("color", color)?;
-    table.set("underline", underline)?;
-
-    if let Some(arg_val) = arg {
-        table.set("__arg", arg_val)?;
-    }
-
-    Ok(table)
-}
diff --git a/src/config/mod.rs b/src/config/mod.rs
deleted file mode 100644
index 4628e1a..0000000
--- a/src/config/mod.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-mod lua;
-mod lua_api;
-
-pub use lua::parse_lua_config;
diff --git a/src/errors.rs b/src/errors.rs
deleted file mode 100644
index fbba5db..0000000
--- a/src/errors.rs
+++ /dev/null
@@ -1,221 +0,0 @@
-use std::io;
-
-#[derive(Debug)]
-pub enum WmError {
-    X11(X11Error),
-    Io(io::Error),
-    Config(ConfigError),
-    Block(BlockError),
-    Autostart(String, io::Error),
-}
-
-#[derive(Debug)]
-pub enum X11Error {
-    ConnectError(x11rb::errors::ConnectError),
-    ConnectionError(x11rb::errors::ConnectionError),
-    ReplyError(x11rb::errors::ReplyError),
-    ReplyOrIdError(x11rb::errors::ReplyOrIdError),
-    DisplayOpenFailed,
-    FontLoadFailed(String),
-    DrawCreateFailed,
-}
-
-#[derive(Debug)]
-pub enum ConfigError {
-    LuaError(String),
-    InvalidModkey(String),
-    UnknownKey(String),
-    UnknownAction(String),
-    UnknownBlockCommand(String),
-    MissingCommandArg { command: String, field: String },
-    ValidationError(String),
-    NoConfigPathSet,
-    NoConfigAtPath,
-    CouldNotReadConfig(std::io::Error),
-}
-
-#[derive(Debug)]
-pub enum BlockError {
-    Io(io::Error),
-    ParseInt(std::num::ParseIntError),
-    MissingFile(String),
-    InvalidData(String),
-    CommandFailed(String),
-}
-
-pub enum MainError {
-    CouldNotCreateConfigDir(std::io::Error),
-    CouldNotWriteConfig(std::io::Error),
-    FailedCheckExist(std::io::Error),
-    FailedReadConfig(std::io::Error),
-    FailedReadConfigTemplate(ConfigError),
-    CouldNotStartWm(WmError),
-    WmError(WmError),
-    BadConfigPath,
-    NoConfigPath,
-    InvalidArguments,
-    NoProgramName,
-    NoConfigDir,
-}
-
-impl std::fmt::Display for WmError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::X11(error) => write!(f, "{}", error),
-            Self::Io(error) => write!(f, "{}", error),
-            Self::Config(error) => write!(f, "{}", error),
-            Self::Block(error) => write!(f, "{}", error),
-            Self::Autostart(command, error) => write!(
-                f,
-                "Failed to spawn autostart command '{}': {}",
-                command, error
-            ),
-        }
-    }
-}
-
-impl std::fmt::Display for X11Error {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::ConnectError(err) => write!(f, "{}", err),
-            Self::ConnectionError(err) => write!(f, "{}", err),
-            Self::ReplyError(err) => write!(f, "{}", err),
-            Self::ReplyOrIdError(err) => write!(f, "{}", err),
-            Self::DisplayOpenFailed => write!(f, "failed to open X11 display"),
-            Self::FontLoadFailed(font_name) => write!(f, "failed to load Xft font: {}", font_name),
-            Self::DrawCreateFailed => write!(f, "failed to create XftDraw"),
-        }
-    }
-}
-
-impl std::fmt::Display for ConfigError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::LuaError(msg) => write!(f, "{}", msg),
-            Self::InvalidModkey(msg) => write!(f, "{}", msg),
-            Self::UnknownKey(msg) => write!(f, "{}", msg),
-            Self::UnknownAction(msg) => write!(f, "{}", msg),
-            Self::UnknownBlockCommand(msg) => write!(f, "{}", msg),
-            Self::MissingCommandArg { command, field } => {
-                write!(f, "{} command requires {}", command, field)
-            }
-            Self::ValidationError(msg) => write!(f, "{}", msg),
-            Self::NoConfigPathSet => write!(
-                f,
-                "Could not find config file. Config path should've been set while loading"
-            ),
-            Self::NoConfigAtPath => write!(f, "Could not find config file, has it been moved?"),
-            Self::CouldNotReadConfig(e) => write!(f, "Could not read config: {e}"),
-        }
-    }
-}
-
-impl std::fmt::Display for BlockError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Self::Io(err) => write!(f, "Block I/O error: {}", err),
-            Self::ParseInt(err) => write!(f, "Block parse error: {}", err),
-            Self::MissingFile(path) => write!(f, "Block missing file: {}", path),
-            Self::InvalidData(msg) => write!(f, "Block invalid data: {}", msg),
-            Self::CommandFailed(msg) => write!(f, "Block command failed: {}", msg),
-        }
-    }
-}
-
-impl<T: Into<X11Error>> From<T> for WmError {
-    fn from(value: T) -> Self {
-        Self::X11(value.into())
-    }
-}
-
-impl From<io::Error> for WmError {
-    fn from(value: io::Error) -> Self {
-        Self::Io(value)
-    }
-}
-
-impl From<ConfigError> for WmError {
-    fn from(value: ConfigError) -> Self {
-        Self::Config(value)
-    }
-}
-
-impl From<BlockError> for WmError {
-    fn from(value: BlockError) -> Self {
-        Self::Block(value)
-    }
-}
-
-impl From<io::Error> for BlockError {
-    fn from(value: io::Error) -> Self {
-        BlockError::Io(value)
-    }
-}
-
-impl From<std::num::ParseIntError> for BlockError {
-    fn from(value: std::num::ParseIntError) -> Self {
-        BlockError::ParseInt(value)
-    }
-}
-
-impl From<x11rb::errors::ConnectError> for X11Error {
-    fn from(value: x11rb::errors::ConnectError) -> Self {
-        X11Error::ConnectError(value)
-    }
-}
-
-impl From<x11rb::errors::ConnectionError> for X11Error {
-    fn from(value: x11rb::errors::ConnectionError) -> Self {
-        X11Error::ConnectionError(value)
-    }
-}
-
-impl From<x11rb::errors::ReplyError> for X11Error {
-    fn from(value: x11rb::errors::ReplyError) -> Self {
-        X11Error::ReplyError(value)
-    }
-}
-
-impl From<x11rb::errors::ReplyOrIdError> for X11Error {
-    fn from(value: x11rb::errors::ReplyOrIdError) -> Self {
-        X11Error::ReplyOrIdError(value)
-    }
-}
-
-impl From<mlua::Error> for ConfigError {
-    fn from(err: mlua::Error) -> Self {
-        ConfigError::LuaError(err.to_string())
-    }
-}
-
-pub trait LuaResultExt<T> {
-    fn lua_context(self, context: &str) -> Result<T, ConfigError>;
-}
-
-impl<T> LuaResultExt<T> for Result<T, mlua::Error> {
-    fn lua_context(self, context: &str) -> Result<T, ConfigError> {
-        self.map_err(|e| ConfigError::LuaError(format!("{}: {}", context, e)))
-    }
-}
-
-impl std::fmt::Debug for MainError {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        use MainError::*;
-
-        match self {
-            CouldNotCreateConfigDir(e)
-            | CouldNotWriteConfig(e)
-            | FailedCheckExist(e)
-            | FailedReadConfig(e) => {
-                write!(f, "{e}")
-            }
-            FailedReadConfigTemplate(e) => write!(f, "{e}"),
-            CouldNotStartWm(e) | WmError(e) => write!(f, "{e}"),
-            BadConfigPath => write!(f, "Given config path does not exist"),
-            NoConfigPath => write!(f, "The --config switch requires a path value"),
-            InvalidArguments => write!(f, "The arguments given are invalid try --help"),
-            NoProgramName => write!(f, "Could not get the program name from the environment"),
-            NoConfigDir => write!(f, "Could not get the config dir"),
-        }
-    }
-}
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
deleted file mode 100644
index 8c8e88e..0000000
--- a/src/keyboard/handlers.rs
+++ /dev/null
@@ -1,370 +0,0 @@
-use std::io::Result;
-
-use serde::Deserialize;
-use x11rb::connection::Connection;
-use x11rb::protocol::xproto::*;
-
-use crate::errors::X11Error;
-use crate::keyboard::keysyms::{self, Keysym, format_keysym};
-
-/// When adding a new action, update:
-/// 1. Add variant here
-/// 2. lua_api.rs: string_to_action()
-/// 3. lua_api.rs: register_*_module()
-/// 4. window_manager.rs: handle_key_action()
-/// 5. (optionally) overlay/keybind.rs: action_description()
-/// 6. templates/oxwm.lua
-#[derive(Debug, Copy, Clone, Deserialize, PartialEq)]
-pub enum KeyAction {
-    Spawn,
-    SpawnTerminal,
-    KillClient,
-    FocusStack,
-    MoveStack,
-    Quit,
-    Restart,
-    ViewTag,
-    ViewNextTag,
-    ViewPreviousTag,
-    ViewNextNonEmptyTag,
-    ViewPreviousNonEmptyTag,
-    ToggleView,
-    MoveToTag,
-    ToggleTag,
-    ToggleGaps,
-    ToggleFullScreen,
-    ToggleFloating,
-    ChangeLayout,
-    CycleLayout,
-    FocusMonitor,
-    TagMonitor,
-    ShowKeybindOverlay,
-    SetMasterFactor,
-    IncNumMaster,
-    ScrollLeft,
-    ScrollRight,
-    None,
-}
-
-#[derive(Debug, Clone)]
-pub enum Arg {
-    None,
-    Int(i32),
-    Str(String),
-    Array(Vec<String>),
-}
-
-impl Arg {
-    pub const fn none() -> Self {
-        Arg::None
-    }
-}
-
-#[derive(Clone)]
-pub struct KeyPress {
-    pub(crate) modifiers: Vec<KeyButMask>,
-    pub(crate) keysym: Keysym,
-}
-
-impl std::fmt::Debug for KeyPress {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("KeyPress")
-            .field("modifiers", &self.modifiers)
-            .field("keysym", &format_keysym(self.keysym))
-            .finish()
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct KeyBinding {
-    pub(crate) keys: Vec<KeyPress>,
-    pub(crate) func: KeyAction,
-    pub(crate) arg: Arg,
-}
-
-impl KeyBinding {
-    pub fn new(keys: Vec<KeyPress>, func: KeyAction, arg: Arg) -> Self {
-        Self { keys, func, arg }
-    }
-
-    pub fn single_key(
-        modifiers: Vec<KeyButMask>,
-        keysym: Keysym,
-        func: KeyAction,
-        arg: Arg,
-    ) -> Self {
-        Self {
-            keys: vec![KeyPress { modifiers, keysym }],
-            func,
-            arg,
-        }
-    }
-}
-
-pub type Key = KeyBinding;
-
-#[derive(Debug, Clone)]
-pub enum KeychordState {
-    Idle,
-    InProgress {
-        candidates: Vec<usize>,
-        keys_pressed: usize,
-    },
-}
-
-pub enum KeychordResult {
-    Completed(KeyAction, Arg),
-    InProgress(Vec<usize>),
-    None,
-    Cancelled,
-}
-
-pub fn modifiers_to_mask(modifiers: &[KeyButMask]) -> u16 {
-    modifiers
-        .iter()
-        .fold(0u16, |acc, &modifier| acc | u16::from(modifier))
-}
-
-pub struct KeyboardMapping {
-    pub syms: Vec<Keysym>,
-    pub keysyms_per_keycode: u8,
-    pub min_keycode: Keycode,
-}
-
-impl KeyboardMapping {
-    pub fn keycode_to_keysym(&self, keycode: Keycode) -> Keysym {
-        if keycode < self.min_keycode {
-            return 0;
-        }
-        let index = (keycode - self.min_keycode) as usize * self.keysyms_per_keycode as usize;
-        self.syms.get(index).copied().unwrap_or(0)
-    }
-
-    pub fn find_keycode(
-        &self,
-        keysym: Keysym,
-        min_keycode: Keycode,
-        max_keycode: Keycode,
-    ) -> Option<Keycode> {
-        for keycode in min_keycode..=max_keycode {
-            let index = (keycode - self.min_keycode) as usize * self.keysyms_per_keycode as usize;
-            if let Some(&sym) = self.syms.get(index)
-                && sym == keysym
-            {
-                return Some(keycode);
-            }
-        }
-        None
-    }
-}
-
-pub fn get_keyboard_mapping(
-    connection: &impl Connection,
-) -> std::result::Result<KeyboardMapping, X11Error> {
-    let setup = connection.setup();
-    let min_keycode = setup.min_keycode;
-    let max_keycode = setup.max_keycode;
-
-    let mapping = connection
-        .get_keyboard_mapping(min_keycode, max_keycode - min_keycode + 1)?
-        .reply()?;
-
-    Ok(KeyboardMapping {
-        syms: mapping.keysyms,
-        keysyms_per_keycode: mapping.keysyms_per_keycode,
-        min_keycode,
-    })
-}
-
-pub fn grab_keys(
-    connection: &impl Connection,
-    root: Window,
-    keybindings: &[KeyBinding],
-    current_key: usize,
-) -> std::result::Result<KeyboardMapping, X11Error> {
-    let setup = connection.setup();
-    let min_keycode = setup.min_keycode;
-    let max_keycode = setup.max_keycode;
-
-    let mapping = get_keyboard_mapping(connection)?;
-
-    connection.ungrab_key(x11rb::protocol::xproto::Grab::ANY, root, ModMask::ANY)?;
-
-    let modifiers = [
-        0u16,
-        u16::from(ModMask::LOCK),
-        u16::from(ModMask::M2),
-        u16::from(ModMask::LOCK | ModMask::M2),
-    ];
-
-    for keycode in min_keycode..=max_keycode {
-        for keybinding in keybindings {
-            if current_key >= keybinding.keys.len() {
-                continue;
-            }
-
-            let key = &keybinding.keys[current_key];
-            if key.keysym == mapping.keycode_to_keysym(keycode) {
-                let modifier_mask = modifiers_to_mask(&key.modifiers);
-                for &ignore_mask in &modifiers {
-                    connection.grab_key(
-                        true,
-                        root,
-                        (modifier_mask | ignore_mask).into(),
-                        keycode,
-                        GrabMode::ASYNC,
-                        GrabMode::ASYNC,
-                    )?;
-                }
-            }
-        }
-    }
-
-    if current_key > 0
-        && let Some(escape_keycode) =
-            mapping.find_keycode(keysyms::XK_ESCAPE, min_keycode, max_keycode)
-    {
-        connection.grab_key(
-            true,
-            root,
-            ModMask::ANY,
-            escape_keycode,
-            GrabMode::ASYNC,
-            GrabMode::ASYNC,
-        )?;
-    }
-
-    connection.flush()?;
-    Ok(mapping)
-}
-
-pub fn handle_key_press(
-    event: KeyPressEvent,
-    keybindings: &[KeyBinding],
-    keychord_state: &KeychordState,
-    mapping: &KeyboardMapping,
-) -> KeychordResult {
-    let keysym = mapping.keycode_to_keysym(event.detail);
-
-    if keysym == keysyms::XK_ESCAPE {
-        return match keychord_state {
-            KeychordState::InProgress { .. } => KeychordResult::Cancelled,
-            KeychordState::Idle => KeychordResult::None,
-        };
-    }
-
-    match keychord_state {
-        KeychordState::Idle => handle_first_key(event, keysym, keybindings),
-        KeychordState::InProgress {
-            candidates,
-            keys_pressed,
-        } => handle_next_key(event, keysym, keybindings, candidates, *keys_pressed),
-    }
-}
-
-fn handle_first_key(
-    event: KeyPressEvent,
-    event_keysym: Keysym,
-    keybindings: &[KeyBinding],
-) -> KeychordResult {
-    let mut candidates = Vec::new();
-
-    let clean_state = event.state & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2));
-
-    for (keybinding_index, keybinding) in keybindings.iter().enumerate() {
-        if keybinding.keys.is_empty() {
-            continue;
-        }
-
-        let first_key = &keybinding.keys[0];
-        let modifier_mask = modifiers_to_mask(&first_key.modifiers);
-
-        if event_keysym == first_key.keysym && clean_state == modifier_mask.into() {
-            if keybinding.keys.len() == 1 {
-                return KeychordResult::Completed(keybinding.func, keybinding.arg.clone());
-            } else {
-                candidates.push(keybinding_index);
-            }
-        }
-    }
-
-    if candidates.is_empty() {
-        KeychordResult::None
-    } else {
-        KeychordResult::InProgress(candidates)
-    }
-}
-
-fn handle_next_key(
-    event: KeyPressEvent,
-    event_keysym: Keysym,
-    keybindings: &[KeyBinding],
-    candidates: &[usize],
-    keys_pressed: usize,
-) -> KeychordResult {
-    let mut new_candidates = Vec::new();
-
-    let clean_state = event.state & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2));
-
-    for &candidate_index in candidates {
-        let keybinding = &keybindings[candidate_index];
-
-        if keys_pressed >= keybinding.keys.len() {
-            continue;
-        }
-
-        let next_key = &keybinding.keys[keys_pressed];
-        let required_mask = modifiers_to_mask(&next_key.modifiers);
-
-        let modifiers_match = if next_key.modifiers.is_empty() {
-            true
-        } else {
-            (clean_state & required_mask) == required_mask.into()
-        };
-
-        if event_keysym == next_key.keysym && modifiers_match {
-            if keys_pressed + 1 == keybinding.keys.len() {
-                return KeychordResult::Completed(keybinding.func, keybinding.arg.clone());
-            } else {
-                new_candidates.push(candidate_index);
-            }
-        }
-    }
-
-    if new_candidates.is_empty() {
-        KeychordResult::Cancelled
-    } else {
-        KeychordResult::InProgress(new_candidates)
-    }
-}
-
-pub fn handle_spawn_action(action: KeyAction, arg: &Arg, selected_monitor: usize) -> Result<()> {
-    if let KeyAction::Spawn = action {
-        match arg {
-            Arg::Str(command) => {
-                crate::signal::spawn_detached(command);
-            }
-            Arg::Array(command) => {
-                let Some((cmd, args)) = command.split_first() else {
-                    return Ok(());
-                };
-
-                let mut args_vec: Vec<String> = args.to_vec();
-
-                let is_dmenu = cmd.contains("dmenu");
-                let has_monitor_flag = args.iter().any(|arg| arg == "-m");
-
-                if is_dmenu && !has_monitor_flag {
-                    args_vec.insert(0, selected_monitor.to_string());
-                    args_vec.insert(0, "-m".to_string());
-                }
-
-                let args_str: Vec<&str> = args_vec.iter().map(|s| s.as_str()).collect();
-                crate::signal::spawn_detached_with_args(cmd, &args_str);
-            }
-            _ => {}
-        }
-    }
-
-    Ok(())
-}
diff --git a/src/keyboard/keysyms.rs b/src/keyboard/keysyms.rs
deleted file mode 100644
index d4c1837..0000000
--- a/src/keyboard/keysyms.rs
+++ /dev/null
@@ -1,422 +0,0 @@
-#![allow(dead_code)]
-
-pub type Keysym = u32;
-pub const XK_ESCAPE: Keysym = 0xff1b;
-pub const XK_RETURN: Keysym = 0xff0d;
-pub const XK_SPACE: Keysym = 0x0020;
-pub const XK_TAB: Keysym = 0xff09;
-pub const XK_BACKSPACE: Keysym = 0xff08;
-pub const XK_DELETE: Keysym = 0xffff;
-pub const XK_F1: Keysym = 0xffbe;
-pub const XK_F2: Keysym = 0xffbf;
-pub const XK_F3: Keysym = 0xffc0;
-pub const XK_F4: Keysym = 0xffc1;
-pub const XK_F5: Keysym = 0xffc2;
-pub const XK_F6: Keysym = 0xffc3;
-pub const XK_F7: Keysym = 0xffc4;
-pub const XK_F8: Keysym = 0xffc5;
-pub const XK_F9: Keysym = 0xffc6;
-pub const XK_F10: Keysym = 0xffc7;
-pub const XK_F11: Keysym = 0xffc8;
-pub const XK_F12: Keysym = 0xffc9;
-pub const XK_A: Keysym = 0x0061;
-pub const XK_B: Keysym = 0x0062;
-pub const XK_C: Keysym = 0x0063;
-pub const XK_D: Keysym = 0x0064;
-pub const XK_E: Keysym = 0x0065;
-pub const XK_F: Keysym = 0x0066;
-pub const XK_G: Keysym = 0x0067;
-pub const XK_H: Keysym = 0x0068;
-pub const XK_I: Keysym = 0x0069;
-pub const XK_J: Keysym = 0x006a;
-pub const XK_K: Keysym = 0x006b;
-pub const XK_L: Keysym = 0x006c;
-pub const XK_M: Keysym = 0x006d;
-pub const XK_N: Keysym = 0x006e;
-pub const XK_O: Keysym = 0x006f;
-pub const XK_P: Keysym = 0x0070;
-pub const XK_Q: Keysym = 0x0071;
-pub const XK_R: Keysym = 0x0072;
-pub const XK_S: Keysym = 0x0073;
-pub const XK_T: Keysym = 0x0074;
-pub const XK_U: Keysym = 0x0075;
-pub const XK_V: Keysym = 0x0076;
-pub const XK_W: Keysym = 0x0077;
-pub const XK_X: Keysym = 0x0078;
-pub const XK_Y: Keysym = 0x0079;
-pub const XK_Z: Keysym = 0x007a;
-pub const XK_0: Keysym = 0x0030;
-pub const XK_1: Keysym = 0x0031;
-pub const XK_2: Keysym = 0x0032;
-pub const XK_3: Keysym = 0x0033;
-pub const XK_4: Keysym = 0x0034;
-pub const XK_5: Keysym = 0x0035;
-pub const XK_6: Keysym = 0x0036;
-pub const XK_7: Keysym = 0x0037;
-pub const XK_8: Keysym = 0x0038;
-pub const XK_9: Keysym = 0x0039;
-pub const XK_LEFT: Keysym = 0xff51;
-pub const XK_UP: Keysym = 0xff52;
-pub const XK_RIGHT: Keysym = 0xff53;
-pub const XK_DOWN: Keysym = 0xff54;
-pub const XK_HOME: Keysym = 0xff50;
-pub const XK_END: Keysym = 0xff57;
-pub const XK_PAGE_UP: Keysym = 0xff55;
-pub const XK_PAGE_DOWN: Keysym = 0xff56;
-pub const XK_INSERT: Keysym = 0xff63;
-pub const XK_MINUS: Keysym = 0x002d;
-pub const XK_EQUAL: Keysym = 0x003d;
-pub const XK_LEFT_BRACKET: Keysym = 0x005b;
-pub const XK_RIGHT_BRACKET: Keysym = 0x005d;
-pub const XK_SEMICOLON: Keysym = 0x003b;
-pub const XK_QUESTION: Keysym = 0x003f;
-pub const XK_APOSTROPHE: Keysym = 0x0027;
-pub const XK_GRAVE: Keysym = 0x0060;
-pub const XK_BACKSLASH: Keysym = 0x005c;
-pub const XK_COMMA: Keysym = 0x002c;
-pub const XK_PERIOD: Keysym = 0x002e;
-pub const XK_SLASH: Keysym = 0x002f;
-pub const XK_PRINT: Keysym = 0xff61;
-pub const XK_AMPERSAND: Keysym = 0x26;
-pub const XK_AGRAVE: Keysym = 0xe0;
-pub const XK_CCEDILLA: Keysym = 0xe7;
-pub const XK_EGRAVE: Keysym = 0xe8;
-pub const XK_EACUTE: Keysym = 0xe9;
-pub const XK_PARENLEFT: Keysym = 0x28;
-pub const XK_PARENRIGHT: Keysym = 0x29;
-pub const XK_QUOTEDBL: Keysym = 0x22;
-pub const XK_UNDERSCORE: Keysym = 0x5f;
-pub const XK_HYPHEN: Keysym = 0xad;
-pub const XF86_AUDIO_MEDIA: Keysym = 0x1008ff32;
-pub const XF86_AUDIO_NEXT: Keysym = 0x1008ff17;
-pub const XF86_AUDIO_PAUSE: Keysym = 0x1008ff31;
-pub const XF86_AUDIO_PLAY: Keysym = 0x1008ff14;
-pub const XF86_AUDIO_PREV: Keysym = 0x1008ff16;
-pub const XF86_AUDIO_RAISE_VOLUME: Keysym = 0x1008ff13;
-pub const XF86_AUDIO_LOWER_VOLUME: Keysym = 0x1008ff11;
-pub const XF86_AUDIO_MUTE: Keysym = 0x1008ff12;
-pub const XF86_AUDIO_MIC_MUTE: Keysym = 0x1008ffb2;
-pub const XF86_AUDIO_RECORD: Keysym = 0x1008ff1c;
-pub const XF86_AUDIO_REWIND: Keysym = 0x1008ff3e;
-pub const XF86_AUDIO_STOP: Keysym = 0x1008ff15;
-pub const XF86_BACK: Keysym = 0x1008ff26;
-pub const XF86_CALCULATOR: Keysym = 0x1008ff1d;
-pub const XF86_CLOSE: Keysym = 0x1008ff56;
-pub const XF86_COPY: Keysym = 0x1008ff57;
-pub const XF86_CUT: Keysym = 0x1008ff58;
-pub const XF86_DOS: Keysym = 0x1008ff5a;
-pub const XF86_DISPLAY: Keysym = 0x1008ff59;
-pub const XF86_DOCUMENTS: Keysym = 0x1008ff5b;
-pub const XF86_EJECT: Keysym = 0x1008ff2c;
-pub const XF86_EXPLORER: Keysym = 0x1008ff5d;
-pub const XF86_FAVORITES: Keysym = 0x1008ff30;
-pub const XF86_FINANCE: Keysym = 0x1008ff3c;
-pub const XF86_FORWARD: Keysym = 0x1008ff27;
-pub const XF86_GAME: Keysym = 0x1008ff5e;
-pub const XF86_GO: Keysym = 0x1008ff5f;
-pub const XF86_HOME_PAGE: Keysym = 0x1008ff18;
-pub const XF86_MAIL: Keysym = 0x1008ff19;
-pub const XF86_MAIL_FORWARD: Keysym = 0x1008ff90;
-pub const XF86_MENU_KB: Keysym = 0x1008ff65;
-pub const XF86_MESSENGER: Keysym = 0x1008ff8e;
-pub const XF86_MON_BRIGHTNESS_UP: Keysym = 0x1008ff02;
-pub const XF86_MON_BRIGHTNESS_DOWN: Keysym = 0x1008ff03;
-pub const XF86_MY_COMPUTER: Keysym = 0x1008ff33;
-pub const XF86_NEW: Keysym = 0x1008ff68;
-pub const XF86_NEXT_VMODE: Keysym = 0x1008fe22;
-pub const XF86_PREV_VMODE: Keysym = 0x1008fe23;
-pub const XF86_OPEN: Keysym = 0x1008ff6b;
-pub const XF86_PASTE: Keysym = 0x1008ff6d;
-pub const XF86_PHONE: Keysym = 0x1008ff6e;
-pub const XF86_POWER_OFF: Keysym = 0x1008ff2a;
-pub const XF86_RELOAD: Keysym = 0x1008ff73;
-pub const XF86_REPLY: Keysym = 0x1008ff72;
-pub const XF86_ROTATE_WINDOWS: Keysym = 0x1008ff74;
-pub const XF86_SAVE: Keysym = 0x1008ff77;
-pub const XF86_SCREEN_SAVER: Keysym = 0x1008ff2d;
-pub const XF86_SCROLL_DOWN: Keysym = 0x1008ff79;
-pub const XF86_SCROLL_UP: Keysym = 0x1008ff78;
-pub const XF86_SEARCH: Keysym = 0x1008ff1b;
-pub const XF86_SEND: Keysym = 0x1008ff7b;
-pub const XF86_SHOP: Keysym = 0x1008ff36;
-pub const XF86_SLEEP: Keysym = 0x1008ff2f;
-pub const XF86_TASK_PANE: Keysym = 0x1008ff7f;
-pub const XF86_TOOLS: Keysym = 0x1008ff81;
-pub const XF86_WWW: Keysym = 0x1008ff2e;
-pub const XF86_WAKE_UP: Keysym = 0x1008ff2b;
-pub const XF86_WEBCAM: Keysym = 0x1008ff8f;
-pub const XF86_XFER: Keysym = 0x1008ff8a;
-
-pub fn keysym_from_str(s: &str) -> Option<Keysym> {
-    match s {
-        "Return" => Some(XK_RETURN),
-        "Escape" => Some(XK_ESCAPE),
-        "Space" => Some(XK_SPACE),
-        "Tab" => Some(XK_TAB),
-        "Backspace" => Some(XK_BACKSPACE),
-        "Delete" => Some(XK_DELETE),
-        "F1" => Some(XK_F1),
-        "F2" => Some(XK_F2),
-        "F3" => Some(XK_F3),
-        "F4" => Some(XK_F4),
-        "F5" => Some(XK_F5),
-        "F6" => Some(XK_F6),
-        "F7" => Some(XK_F7),
-        "F8" => Some(XK_F8),
-        "F9" => Some(XK_F9),
-        "F10" => Some(XK_F10),
-        "F11" => Some(XK_F11),
-        "F12" => Some(XK_F12),
-        "A" => Some(XK_A),
-        "B" => Some(XK_B),
-        "C" => Some(XK_C),
-        "D" => Some(XK_D),
-        "E" => Some(XK_E),
-        "F" => Some(XK_F),
-        "G" => Some(XK_G),
-        "H" => Some(XK_H),
-        "I" => Some(XK_I),
-        "J" => Some(XK_J),
-        "K" => Some(XK_K),
-        "L" => Some(XK_L),
-        "M" => Some(XK_M),
-        "N" => Some(XK_N),
-        "O" => Some(XK_O),
-        "P" => Some(XK_P),
-        "Q" => Some(XK_Q),
-        "R" => Some(XK_R),
-        "S" => Some(XK_S),
-        "T" => Some(XK_T),
-        "U" => Some(XK_U),
-        "V" => Some(XK_V),
-        "W" => Some(XK_W),
-        "X" => Some(XK_X),
-        "Y" => Some(XK_Y),
-        "Z" => Some(XK_Z),
-        "0" => Some(XK_0),
-        "1" => Some(XK_1),
-        "2" => Some(XK_2),
-        "3" => Some(XK_3),
-        "4" => Some(XK_4),
-        "5" => Some(XK_5),
-        "6" => Some(XK_6),
-        "7" => Some(XK_7),
-        "8" => Some(XK_8),
-        "9" => Some(XK_9),
-        "Left" => Some(XK_LEFT),
-        "Right" => Some(XK_RIGHT),
-        "Up" => Some(XK_UP),
-        "Down" => Some(XK_DOWN),
-        "Home" => Some(XK_HOME),
-        "End" => Some(XK_END),
-        "PageUp" => Some(XK_PAGE_UP),
-        "PageDown" => Some(XK_PAGE_DOWN),
-        "Insert" => Some(XK_INSERT),
-        "Minus" => Some(XK_MINUS),
-        "Equal" => Some(XK_EQUAL),
-        "BracketLeft" => Some(XK_LEFT_BRACKET),
-        "BracketRight" => Some(XK_RIGHT_BRACKET),
-        "Semicolon" => Some(XK_SEMICOLON),
-        "Question" => Some(XK_QUESTION),
-        "Apostrophe" => Some(XK_APOSTROPHE),
-        "Grave" => Some(XK_GRAVE),
-        "Backslash" => Some(XK_BACKSLASH),
-        "Comma" => Some(XK_COMMA),
-        "Period" => Some(XK_PERIOD),
-        "Slash" => Some(XK_SLASH),
-        "Print" => Some(XK_PRINT),
-        "Ampersand" => Some(XK_AMPERSAND),
-        "Agrave" => Some(XK_AGRAVE),
-        "Ccedilla" => Some(XK_CCEDILLA),
-        "Egrave" => Some(XK_EGRAVE),
-        "Eacute" => Some(XK_EACUTE),
-        "ParenLeft" => Some(XK_PARENLEFT),
-        "ParenRight" => Some(XK_PARENRIGHT),
-        "QuoteDouble" => Some(XK_QUOTEDBL),
-        "Underscore" => Some(XK_UNDERSCORE),
-        "Hyphen" => Some(XK_HYPHEN),
-        "AudioMedia" | "XF86AudioMedia" => Some(XF86_AUDIO_MEDIA),
-        "XF86AudioNext" => Some(XF86_AUDIO_NEXT),
-        "XF86AudioPause" => Some(XF86_AUDIO_PAUSE),
-        "XF86AudioPlay" => Some(XF86_AUDIO_PLAY),
-        "XF86AudioPrev" => Some(XF86_AUDIO_PREV),
-        "AudioRaiseVolume" | "XF86AudioRaiseVolume" => Some(XF86_AUDIO_RAISE_VOLUME),
-        "AudioLowerVolume" | "XF86AudioLowerVolume" => Some(XF86_AUDIO_LOWER_VOLUME),
-        "AudioMute" | "XF86AudioMute" => Some(XF86_AUDIO_MUTE),
-        "XF86AudioMicMute" => Some(XF86_AUDIO_MIC_MUTE),
-        "XF86AudioRecord" => Some(XF86_AUDIO_RECORD),
-        "XF86AudioRewind" => Some(XF86_AUDIO_REWIND),
-        "XF86AudioStop" => Some(XF86_AUDIO_STOP),
-        "XF86Back" => Some(XF86_BACK),
-        "XF86Calculator" => Some(XF86_CALCULATOR),
-        "XF86Close" => Some(XF86_CLOSE),
-        "XF86Copy" => Some(XF86_COPY),
-        "XF86Cut" => Some(XF86_CUT),
-        "XF86DOS" => Some(XF86_DOS),
-        "XF86Display" => Some(XF86_DISPLAY),
-        "XF86Documents" => Some(XF86_DOCUMENTS),
-        "XF86Eject" => Some(XF86_EJECT),
-        "XF86Explorer" => Some(XF86_EXPLORER),
-        "XF86Favorites" => Some(XF86_FAVORITES),
-        "XF86Finance" => Some(XF86_FINANCE),
-        "XF86Forward" => Some(XF86_FORWARD),
-        "XF86Game" => Some(XF86_GAME),
-        "XF86Go" => Some(XF86_GO),
-        "XF86HomePage" => Some(XF86_HOME_PAGE),
-        "XF86Mail" => Some(XF86_MAIL),
-        "XF86MailForward" => Some(XF86_MAIL_FORWARD),
-        "XF86MenuKB" => Some(XF86_MENU_KB),
-        "XF86Messenger" => Some(XF86_MESSENGER),
-        "MonBrightnessUp" | "XF86MonBrightnessUp" => Some(XF86_MON_BRIGHTNESS_UP),
-        "MonBrightnessDown" | "XF86MonBrightnessDown" => Some(XF86_MON_BRIGHTNESS_DOWN),
-        "XF86MyComputer" => Some(XF86_MY_COMPUTER),
-        "XF86New" => Some(XF86_NEW),
-        "XF86Next_VMode" => Some(XF86_NEXT_VMODE),
-        "XF86Prev_VMode" => Some(XF86_PREV_VMODE),
-        "XF86Open" => Some(XF86_OPEN),
-        "XF86Paste" => Some(XF86_PASTE),
-        "XF86Phone" => Some(XF86_PHONE),
-        "XF86PowerOff" => Some(XF86_POWER_OFF),
-        "XF86Reload" => Some(XF86_RELOAD),
-        "XF86Reply" => Some(XF86_REPLY),
-        "XF86RotateWindows" => Some(XF86_ROTATE_WINDOWS),
-        "XF86Save" => Some(XF86_SAVE),
-        "XF86ScreenSaver" => Some(XF86_SCREEN_SAVER),
-        "XF86ScrollDown" => Some(XF86_SCROLL_DOWN),
-        "XF86ScrollUp" => Some(XF86_SCROLL_UP),
-        "XF86Search" => Some(XF86_SEARCH),
-        "XF86Send" => Some(XF86_SEND),
-        "XF86Shop" => Some(XF86_SHOP),
-        "XF86Sleep" => Some(XF86_SLEEP),
-        "XF86TaskPane" => Some(XF86_TASK_PANE),
-        "XF86Tools" => Some(XF86_TOOLS),
-        "XF86WWW" => Some(XF86_WWW),
-        "XF86WakeUp" => Some(XF86_WAKE_UP),
-        "XF86WebCam" => Some(XF86_WEBCAM),
-        "XF86Xfer" => Some(XF86_XFER),
-        _ => None,
-    }
-}
-
-pub fn format_keysym(keysym: Keysym) -> String {
-    match keysym {
-        XK_RETURN => "Return".to_string(),
-        XK_ESCAPE => "Esc".to_string(),
-        XK_SPACE => "Space".to_string(),
-        XK_TAB => "Tab".to_string(),
-        XK_BACKSPACE => "Backspace".to_string(),
-        XK_DELETE => "Del".to_string(),
-        XK_LEFT => "Left".to_string(),
-        XK_RIGHT => "Right".to_string(),
-        XK_UP => "Up".to_string(),
-        XK_DOWN => "Down".to_string(),
-        XK_HOME => "Home".to_string(),
-        XK_END => "End".to_string(),
-        XK_PAGE_UP => "PgUp".to_string(),
-        XK_PAGE_DOWN => "PgDn".to_string(),
-        XK_INSERT => "Ins".to_string(),
-        XK_F1 => "F1".to_string(),
-        XK_F2 => "F2".to_string(),
-        XK_F3 => "F3".to_string(),
-        XK_F4 => "F4".to_string(),
-        XK_F5 => "F5".to_string(),
-        XK_F6 => "F6".to_string(),
-        XK_F7 => "F7".to_string(),
-        XK_F8 => "F8".to_string(),
-        XK_F9 => "F9".to_string(),
-        XK_F10 => "F10".to_string(),
-        XK_F11 => "F11".to_string(),
-        XK_F12 => "F12".to_string(),
-        XK_SLASH => "/".to_string(),
-        XK_COMMA => ",".to_string(),
-        XK_PERIOD => ".".to_string(),
-        XK_MINUS => "-".to_string(),
-        XK_EQUAL => "=".to_string(),
-        XK_GRAVE => "`".to_string(),
-        XK_LEFT_BRACKET => "[".to_string(),
-        XK_RIGHT_BRACKET => "]".to_string(),
-        XK_SEMICOLON => ";".to_string(),
-        XK_QUESTION => "?".to_string(),
-        XK_APOSTROPHE => "'".to_string(),
-        XK_BACKSLASH => "\\".to_string(),
-        XK_PRINT => "Print".to_string(),
-        XK_AMPERSAND => "&".to_string(),
-        XK_AGRAVE => "à".to_string(),
-        XK_CCEDILLA => "ç".to_string(),
-        XK_EGRAVE => "è".to_string(),
-        XK_EACUTE => "é".to_string(),
-        XK_PARENLEFT => "(".to_string(),
-        XK_PARENRIGHT => ")".to_string(),
-        XK_QUOTEDBL => "\"".to_string(),
-        XK_UNDERSCORE => "_".to_string(),
-        XK_HYPHEN => "-".to_string(),
-        XF86_AUDIO_MEDIA => "Media".to_string(),
-        XF86_AUDIO_NEXT => "Next".to_string(),
-        XF86_AUDIO_PAUSE => "Pause".to_string(),
-        XF86_AUDIO_PLAY => "Play".to_string(),
-        XF86_AUDIO_PREV => "Prev".to_string(),
-        XF86_AUDIO_RAISE_VOLUME => "Vol+".to_string(),
-        XF86_AUDIO_LOWER_VOLUME => "Vol-".to_string(),
-        XF86_AUDIO_MUTE => "Mute".to_string(),
-        XF86_AUDIO_MIC_MUTE => "MicMute".to_string(),
-        XF86_AUDIO_RECORD => "Record".to_string(),
-        XF86_AUDIO_REWIND => "Rewind".to_string(),
-        XF86_AUDIO_STOP => "Stop".to_string(),
-        XF86_BACK => "Back".to_string(),
-        XF86_CALCULATOR => "Calculator".to_string(),
-        XF86_CLOSE => "Close".to_string(),
-        XF86_COPY => "Copy".to_string(),
-        XF86_CUT => "Cut".to_string(),
-        XF86_DOS => "DOS".to_string(),
-        XF86_DISPLAY => "Display".to_string(),
-        XF86_DOCUMENTS => "Documents".to_string(),
-        XF86_EJECT => "Eject".to_string(),
-        XF86_EXPLORER => "Explorer".to_string(),
-        XF86_FAVORITES => "Favorites".to_string(),
-        XF86_FINANCE => "Finance".to_string(),
-        XF86_FORWARD => "Forward".to_string(),
-        XF86_GAME => "Game".to_string(),
-        XF86_GO => "Go".to_string(),
-        XF86_HOME_PAGE => "HomePage".to_string(),
-        XF86_MAIL => "Mail".to_string(),
-        XF86_MAIL_FORWARD => "MailForward".to_string(),
-        XF86_MENU_KB => "MenuKB".to_string(),
-        XF86_MESSENGER => "Messenger".to_string(),
-        XF86_MON_BRIGHTNESS_UP => "Bright+".to_string(),
-        XF86_MON_BRIGHTNESS_DOWN => "Bright-".to_string(),
-        XF86_MY_COMPUTER => "MyComputer".to_string(),
-        XF86_NEW => "New".to_string(),
-        XF86_NEXT_VMODE => "NextVMode".to_string(),
-        XF86_PREV_VMODE => "PrevVMode".to_string(),
-        XF86_OPEN => "Open".to_string(),
-        XF86_PASTE => "Paste".to_string(),
-        XF86_PHONE => "Phone".to_string(),
-        XF86_POWER_OFF => "PowerOff".to_string(),
-        XF86_RELOAD => "Reload".to_string(),
-        XF86_REPLY => "Reply".to_string(),
-        XF86_ROTATE_WINDOWS => "RotateWindows".to_string(),
-        XF86_SAVE => "Save".to_string(),
-        XF86_SCREEN_SAVER => "ScreenSaver".to_string(),
-        XF86_SCROLL_DOWN => "ScrollDown".to_string(),
-        XF86_SCROLL_UP => "ScrollUp".to_string(),
-        XF86_SEARCH => "Search".to_string(),
-        XF86_SEND => "Send".to_string(),
-        XF86_SHOP => "Shop".to_string(),
-        XF86_SLEEP => "Sleep".to_string(),
-        XF86_TASK_PANE => "TaskPane".to_string(),
-        XF86_TOOLS => "Tools".to_string(),
-        XF86_WWW => "WWW".to_string(),
-        XF86_WAKE_UP => "WakeUp".to_string(),
-        XF86_WEBCAM => "WebCam".to_string(),
-        XF86_XFER => "Xfer".to_string(),
-        XK_A..=XK_Z => {
-            let ch = (keysym - XK_A + b'A' as u32) as u8 as char;
-            ch.to_string()
-        }
-        XK_0..=XK_9 => {
-            let ch = (keysym - XK_0 + b'0' as u32) as u8 as char;
-            ch.to_string()
-        }
-        _ => format!("0x{:x}", keysym),
-    }
-}
diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs
deleted file mode 100644
index c1d2795..0000000
--- a/src/keyboard/mod.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-pub mod handlers;
-pub mod keysyms;
-
-pub use handlers::{Arg, KeyAction, KeyboardMapping, grab_keys, handle_key_press};
-pub use keysyms::*;
diff --git a/src/layout/grid.rs b/src/layout/grid.rs
deleted file mode 100644
index 12aa836..0000000
--- a/src/layout/grid.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use super::{GapConfig, Layout, WindowGeometry};
-use x11rb::protocol::xproto::Window;
-
-pub struct GridLayout;
-
-impl Layout for GridLayout {
-    fn name(&self) -> &'static str {
-        super::LayoutType::Grid.as_str()
-    }
-
-    fn symbol(&self) -> &'static str {
-        "[#]"
-    }
-
-    fn arrange(
-        &self,
-        windows: &[Window],
-        screen_width: u32,
-        screen_height: u32,
-        gaps: &GapConfig,
-        _master_factor: f32,
-        _num_master: i32,
-        _smartgaps_enabled: bool,
-    ) -> Vec<WindowGeometry> {
-        let window_count = windows.len();
-        if window_count == 0 {
-            return Vec::new();
-        }
-
-        if window_count == 1 {
-            let x = gaps.outer_horizontal as i32;
-            let y = gaps.outer_vertical as i32;
-            let width = screen_width.saturating_sub(2 * gaps.outer_horizontal);
-            let height = screen_height.saturating_sub(2 * gaps.outer_vertical);
-
-            return vec![WindowGeometry {
-                x_coordinate: x,
-                y_coordinate: y,
-                width,
-                height,
-            }];
-        }
-
-        let cols = (window_count as f64).sqrt().ceil() as usize;
-        let rows = (window_count as f64 / cols as f64).ceil() as usize;
-
-        let mut geometries = Vec::new();
-
-        let total_horizontal_gaps =
-            gaps.outer_horizontal * 2 + gaps.inner_horizontal * (cols as u32 - 1);
-        let total_vertical_gaps = gaps.outer_vertical * 2 + gaps.inner_vertical * (rows as u32 - 1);
-
-        let cell_width = screen_width.saturating_sub(total_horizontal_gaps) / cols as u32;
-        let cell_height = screen_height.saturating_sub(total_vertical_gaps) / rows as u32;
-
-        for (index, _window) in windows.iter().enumerate() {
-            let row = index / cols;
-            let col = index % cols;
-
-            let is_last_row = row == rows - 1;
-            let windows_in_last_row = window_count - (rows - 1) * cols;
-
-            let (x, y, width, height) = if is_last_row && windows_in_last_row < cols {
-                let last_row_col = index % cols;
-                let last_row_cell_width =
-                    screen_width.saturating_sub(total_horizontal_gaps.saturating_sub(
-                        gaps.inner_horizontal * (cols as u32 - windows_in_last_row as u32),
-                    )) / windows_in_last_row as u32;
-
-                let x = gaps.outer_horizontal
-                    + last_row_col as u32 * (last_row_cell_width + gaps.inner_horizontal);
-                let y = gaps.outer_vertical + row as u32 * (cell_height + gaps.inner_vertical);
-
-                (x as i32, y as i32, last_row_cell_width, cell_height)
-            } else {
-                let x = gaps.outer_horizontal + col as u32 * (cell_width + gaps.inner_horizontal);
-                let y = gaps.outer_vertical + row as u32 * (cell_height + gaps.inner_vertical);
-
-                (x as i32, y as i32, cell_width, cell_height)
-            };
-
-            geometries.push(WindowGeometry {
-                x_coordinate: x,
-                y_coordinate: y,
-                width,
-                height,
-            });
-        }
-
-        geometries
-    }
-}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
deleted file mode 100644
index e332587..0000000
--- a/src/layout/mod.rs
+++ /dev/null
@@ -1,115 +0,0 @@
-pub mod grid;
-pub mod monocle;
-pub mod normie;
-pub mod scrolling;
-pub mod tabbed;
-pub mod tiling;
-
-use std::str::FromStr;
-
-use x11rb::protocol::xproto::Window;
-
-pub type LayoutBox = Box<dyn Layout>;
-
-pub struct GapConfig {
-    pub inner_horizontal: u32,
-    pub inner_vertical: u32,
-    pub outer_horizontal: u32,
-    pub outer_vertical: u32,
-}
-
-pub enum LayoutType {
-    Tiling,
-    Normie,
-    Grid,
-    Monocle,
-    Tabbed,
-    Scrolling,
-}
-
-impl LayoutType {
-    pub fn to_boxed_layout(&self) -> LayoutBox {
-        match self {
-            Self::Tiling => Box::new(tiling::TilingLayout),
-            Self::Normie => Box::new(normie::NormieLayout),
-            Self::Grid => Box::new(grid::GridLayout),
-            Self::Monocle => Box::new(monocle::MonocleLayout),
-            Self::Tabbed => Box::new(tabbed::TabbedLayout),
-            Self::Scrolling => Box::new(scrolling::ScrollingLayout),
-        }
-    }
-
-    pub fn next(&self) -> Self {
-        match self {
-            Self::Tiling => Self::Normie,
-            Self::Normie => Self::Grid,
-            Self::Grid => Self::Monocle,
-            Self::Monocle => Self::Tabbed,
-            Self::Tabbed => Self::Scrolling,
-            Self::Scrolling => Self::Tiling,
-        }
-    }
-
-    pub fn as_str(&self) -> &'static str {
-        match self {
-            Self::Tiling => "tiling",
-            Self::Normie => "normie",
-            Self::Grid => "grid",
-            Self::Monocle => "monocle",
-            Self::Tabbed => "tabbed",
-            Self::Scrolling => "scrolling",
-        }
-    }
-}
-
-impl FromStr for LayoutType {
-    type Err = String;
-
-    fn from_str(s: &str) -> Result<Self, String> {
-        match s.to_lowercase().as_str() {
-            "tiling" => Ok(Self::Tiling),
-            "normie" | "floating" => Ok(Self::Normie),
-            "grid" => Ok(Self::Grid),
-            "monocle" => Ok(Self::Monocle),
-            "tabbed" => Ok(Self::Tabbed),
-            "scrolling" => Ok(Self::Scrolling),
-            _ => Err(format!("Invalid Layout Type: {}", s)),
-        }
-    }
-}
-
-pub fn layout_from_str(s: &str) -> Result<LayoutBox, String> {
-    let layout_type = LayoutType::from_str(s)?;
-    Ok(layout_type.to_boxed_layout())
-}
-
-pub fn next_layout(current_name: &str) -> &'static str {
-    LayoutType::from_str(current_name)
-        .ok()
-        .map(|layout_type| layout_type.next())
-        .unwrap_or(LayoutType::Tiling)
-        .as_str()
-}
-
-pub trait Layout {
-    fn arrange(
-        &self,
-        windows: &[Window],
-        screen_width: u32,
-        screen_height: u32,
-        gaps: &GapConfig,
-        master_factor: f32,
-        num_master: i32,
-        smartgaps_enabled: bool,
-    ) -> Vec<WindowGeometry>;
-    fn name(&self) -> &'static str;
-    fn symbol(&self) -> &'static str;
-}
-
-#[derive(Clone)]
-pub struct WindowGeometry {
-    pub x_coordinate: i32,
-    pub y_coordinate: i32,
-    pub width: u32,
-    pub height: u32,
-}
diff --git a/src/layout/monocle.rs b/src/layout/monocle.rs
deleted file mode 100644
index 5f511d9..0000000
--- a/src/layout/monocle.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-use super::{GapConfig, Layout, WindowGeometry};
-use x11rb::protocol::xproto::Window;
-
-pub struct MonocleLayout;
-
-impl Layout for MonocleLayout {
-    fn name(&self) -> &'static str {
-        super::LayoutType::Monocle.as_str()
-    }
-
-    fn symbol(&self) -> &'static str {
-        "[M]"
-    }
-
-    fn arrange(
-        &self,
-        windows: &[Window],
-        screen_width: u32,
-        screen_height: u32,
-        gaps: &GapConfig,
-        _master_factor: f32,
-        _num_master: i32,
-        _smartgaps_enabled: bool,
-    ) -> Vec<WindowGeometry> {
-        let window_count = windows.len();
-        if window_count == 0 {
-            return Vec::new();
-        }
-
-        let x = gaps.outer_horizontal as i32;
-        let y = gaps.outer_vertical as i32;
-        let width = screen_width.saturating_sub(2 * gaps.outer_horizontal);
-        let height = screen_height.saturating_sub(2 * gaps.outer_vertical);
-
-        let geometry = WindowGeometry {
-            x_coordinate: x,
-            y_coordinate: y,
-            width,
-            height,
-        };
-
-        vec![geometry; window_count]
-    }
-}
diff --git a/src/layout/normie.rs b/src/layout/normie.rs
deleted file mode 100644
index d299982..0000000
--- a/src/layout/normie.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use super::{GapConfig, Layout, WindowGeometry};
-use x11rb::protocol::xproto::Window;
-
-pub struct NormieLayout;
-
-// This layout should return a no-op similar to DWM.C's "null" mode.
-impl Layout for NormieLayout {
-    fn name(&self) -> &'static str {
-        super::LayoutType::Normie.as_str()
-    }
-
-    fn symbol(&self) -> &'static str {
-        "><>"
-    }
-
-    fn arrange(
-        &self,
-        _windows: &[Window],
-        _screen_width: u32,
-        _screen_height: u32,
-        _gaps: &GapConfig,
-        _master_factor: f32,
-        _num_master: i32,
-        _smartgaps_enabled: bool,
-    ) -> Vec<WindowGeometry> {
-        Vec::new()
-    }
-}
diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs
deleted file mode 100644
index fed8bd6..0000000
--- a/src/layout/scrolling.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use super::{GapConfig, Layout, WindowGeometry};
-use x11rb::protocol::xproto::Window;
-
-pub struct ScrollingLayout;
-
-struct GapValues {
-    outer_horizontal: u32,
-    outer_vertical: u32,
-    inner_vertical: u32,
-}
-
-impl ScrollingLayout {
-    fn getgaps(gaps: &GapConfig, window_count: usize, smartgaps_enabled: bool) -> GapValues {
-        let outer_enabled = if smartgaps_enabled && window_count == 1 {
-            0
-        } else {
-            1
-        };
-
-        GapValues {
-            outer_horizontal: gaps.outer_horizontal * outer_enabled,
-            outer_vertical: gaps.outer_vertical * outer_enabled,
-            inner_vertical: gaps.inner_vertical,
-        }
-    }
-}
-
-impl Layout for ScrollingLayout {
-    fn name(&self) -> &'static str {
-        "scrolling"
-    }
-
-    fn symbol(&self) -> &'static str {
-        "[>>]"
-    }
-
-    fn arrange(
-        &self,
-        windows: &[Window],
-        screen_width: u32,
-        screen_height: u32,
-        gaps: &GapConfig,
-        _master_factor: f32,
-        num_master: i32,
-        smartgaps_enabled: bool,
-    ) -> Vec<WindowGeometry> {
-        let window_count = windows.len();
-        if window_count == 0 {
-            return Vec::new();
-        }
-
-        let gap_values = Self::getgaps(gaps, window_count, smartgaps_enabled);
-
-        let outer_horizontal = gap_values.outer_horizontal;
-        let outer_vertical = gap_values.outer_vertical;
-        let inner_vertical = gap_values.inner_vertical;
-
-        let visible_count = if num_master > 0 {
-            num_master as usize
-        } else {
-            2
-        };
-
-        let available_width = screen_width.saturating_sub(2 * outer_vertical);
-        let available_height = screen_height.saturating_sub(2 * outer_horizontal);
-
-        let total_inner_gaps = if visible_count > 1 {
-            inner_vertical * (visible_count.min(window_count) - 1) as u32
-        } else {
-            0
-        };
-        let window_width = if window_count <= visible_count {
-            let num_windows = window_count as u32;
-            let total_gaps = if num_windows > 1 {
-                inner_vertical * (num_windows - 1)
-            } else {
-                0
-            };
-            (available_width.saturating_sub(total_gaps)) / num_windows
-        } else {
-            (available_width.saturating_sub(total_inner_gaps)) / visible_count as u32
-        };
-
-        let mut geometries = Vec::with_capacity(window_count);
-        let mut x = outer_vertical as i32;
-
-        for _window in windows.iter() {
-            geometries.push(WindowGeometry {
-                x_coordinate: x,
-                y_coordinate: outer_horizontal as i32,
-                width: window_width,
-                height: available_height,
-            });
-
-            x += window_width as i32 + inner_vertical as i32;
-        }
-
-        geometries
-    }
-}
diff --git a/src/layout/tabbed.rs b/src/layout/tabbed.rs
deleted file mode 100644
index ab8f26f..0000000
--- a/src/layout/tabbed.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use super::{GapConfig, Layout, WindowGeometry};
-use x11rb::protocol::xproto::Window;
-
-pub struct TabbedLayout;
-
-pub const TAB_BAR_HEIGHT: u32 = 28;
-
-impl Layout for TabbedLayout {
-    fn name(&self) -> &'static str {
-        super::LayoutType::Tabbed.as_str()
-    }
-
-    fn symbol(&self) -> &'static str {
-        "[=]"
-    }
-
-    fn arrange(
-        &self,
-        windows: &[Window],
-        screen_width: u32,
-        screen_height: u32,
-        gaps: &GapConfig,
-        _master_factor: f32,
-        _num_master: i32,
-        _smartgaps_enabled: bool,
-    ) -> Vec<WindowGeometry> {
-        let window_count = windows.len();
-        if window_count == 0 {
-            return Vec::new();
-        }
-
-        let x = gaps.outer_horizontal as i32;
-        let y = (gaps.outer_vertical + TAB_BAR_HEIGHT) as i32;
-        let width = screen_width.saturating_sub(2 * gaps.outer_horizontal);
-        let height = screen_height
-            .saturating_sub(2 * gaps.outer_vertical)
-            .saturating_sub(TAB_BAR_HEIGHT);
-
-        let geometry = WindowGeometry {
-            x_coordinate: x,
-            y_coordinate: y,
-            width,
-            height,
-        };
-
-        vec![geometry; window_count]
-    }
-}
diff --git a/src/layout/tiling.rs b/src/layout/tiling.rs
deleted file mode 100644
index 43bb490..0000000
--- a/src/layout/tiling.rs
+++ /dev/null
@@ -1,173 +0,0 @@
-use super::{GapConfig, Layout, WindowGeometry};
-use x11rb::protocol::xproto::Window;
-
-pub struct TilingLayout;
-
-struct GapValues {
-    outer_horizontal: u32,
-    outer_vertical: u32,
-    inner_horizontal: u32,
-    inner_vertical: u32,
-}
-
-struct FactValues {
-    master_facts: f32,
-    stack_facts: f32,
-    master_remainder: i32,
-    stack_remainder: i32,
-}
-
-impl TilingLayout {
-    fn getgaps(gaps: &GapConfig, window_count: usize, smartgaps_enabled: bool) -> GapValues {
-        let outer_enabled = if smartgaps_enabled && window_count == 1 {
-            0
-        } else {
-            1
-        };
-        let inner_enabled = 1;
-
-        GapValues {
-            outer_horizontal: gaps.outer_horizontal * outer_enabled,
-            outer_vertical: gaps.outer_vertical * outer_enabled,
-            inner_horizontal: gaps.inner_horizontal * inner_enabled,
-            inner_vertical: gaps.inner_vertical * inner_enabled,
-        }
-    }
-
-    fn getfacts(
-        window_count: usize,
-        num_master: i32,
-        master_size: i32,
-        stack_size: i32,
-    ) -> FactValues {
-        let num_master = num_master.max(0) as usize;
-        let master_facts = window_count.min(num_master) as f32;
-        let stack_facts = if window_count > num_master {
-            (window_count - num_master) as f32
-        } else {
-            0.0
-        };
-
-        let mut master_total = 0;
-        let mut stack_total = 0;
-
-        for i in 0..window_count {
-            if i < num_master {
-                master_total += (master_size as f32 / master_facts) as i32;
-            } else if stack_facts > 0.0 {
-                stack_total += (stack_size as f32 / stack_facts) as i32;
-            }
-        }
-
-        FactValues {
-            master_facts,
-            stack_facts,
-            master_remainder: master_size - master_total,
-            stack_remainder: stack_size - stack_total,
-        }
-    }
-}
-
-impl Layout for TilingLayout {
-    fn name(&self) -> &'static str {
-        super::LayoutType::Tiling.as_str()
-    }
-
-    fn symbol(&self) -> &'static str {
-        "[]="
-    }
-
-    fn arrange(
-        &self,
-        windows: &[Window],
-        screen_width: u32,
-        screen_height: u32,
-        gaps: &GapConfig,
-        master_factor: f32,
-        num_master: i32,
-        smartgaps_enabled: bool,
-    ) -> Vec<WindowGeometry> {
-        let window_count = windows.len();
-        if window_count == 0 {
-            return Vec::new();
-        }
-
-        let gap_values = Self::getgaps(gaps, window_count, smartgaps_enabled);
-
-        let outer_gap_horizontal = gap_values.outer_horizontal;
-        let outer_gap_vertical = gap_values.outer_vertical;
-        let inner_gap_horizontal = gap_values.inner_horizontal;
-        let inner_gap_vertical = gap_values.inner_vertical;
-
-        let mut stack_x = outer_gap_vertical as i32;
-        let mut stack_y = outer_gap_horizontal as i32;
-        let master_x = outer_gap_vertical as i32;
-        let mut master_y = outer_gap_horizontal as i32;
-
-        let num_master_usize = num_master.max(0) as usize;
-        let master_count = window_count.min(num_master_usize);
-        let stack_count = window_count.saturating_sub(num_master_usize);
-
-        let master_height = (screen_height as i32)
-            - (2 * outer_gap_horizontal) as i32
-            - (inner_gap_horizontal as i32 * (master_count.saturating_sub(1)) as i32);
-        let stack_height = (screen_height as i32)
-            - (2 * outer_gap_horizontal) as i32
-            - (inner_gap_horizontal as i32 * stack_count.saturating_sub(1) as i32);
-        let mut stack_width = (screen_width as i32) - (2 * outer_gap_vertical) as i32;
-        let mut master_width = stack_width;
-
-        if num_master > 0 && window_count > num_master_usize {
-            stack_width =
-                ((master_width as f32 - inner_gap_vertical as f32) * (1.0 - master_factor)) as i32;
-            master_width = master_width - inner_gap_vertical as i32 - stack_width;
-            stack_x = master_x + master_width + inner_gap_vertical as i32;
-        }
-
-        let facts = Self::getfacts(window_count, num_master, master_height, stack_height);
-
-        let mut geometries = Vec::new();
-
-        for (i, _window) in windows.iter().enumerate() {
-            if i < num_master_usize {
-                let window_height = (master_height as f32 / facts.master_facts) as i32
-                    + if (i as i32) < facts.master_remainder {
-                        1
-                    } else {
-                        0
-                    };
-
-                geometries.push(WindowGeometry {
-                    x_coordinate: master_x,
-                    y_coordinate: master_y,
-                    width: master_width as u32,
-                    height: window_height as u32,
-                });
-
-                master_y += window_height + inner_gap_horizontal as i32;
-            } else {
-                let window_height = if facts.stack_facts > 0.0 {
-                    (stack_height as f32 / facts.stack_facts) as i32
-                        + if ((i - num_master_usize) as i32) < facts.stack_remainder {
-                            1
-                        } else {
-                            0
-                        }
-                } else {
-                    stack_height
-                };
-
-                geometries.push(WindowGeometry {
-                    x_coordinate: stack_x,
-                    y_coordinate: stack_y,
-                    width: stack_width as u32,
-                    height: window_height as u32,
-                });
-
-                stack_y += window_height + inner_gap_horizontal as i32;
-            }
-        }
-
-        geometries
-    }
-}
diff --git a/src/layouts/floating.zig b/src/layouts/floating.zig
new file mode 100644
index 0000000..27f83fe
--- /dev/null
+++ b/src/layouts/floating.zig
@@ -0,0 +1,6 @@
+const monitor_mod = @import("../monitor.zig");
+
+pub const layout = monitor_mod.Layout{
+    .symbol = "><>",
+    .arrange_fn = null,
+};
diff --git a/src/layouts/monocle.zig b/src/layouts/monocle.zig
new file mode 100644
index 0000000..b6b06f3
--- /dev/null
+++ b/src/layouts/monocle.zig
@@ -0,0 +1,31 @@
+const std = @import("std");
+const client_mod = @import("../client.zig");
+const monitor_mod = @import("../monitor.zig");
+const xlib = @import("../x11/xlib.zig");
+const tiling = @import("tiling.zig");
+
+const Client = client_mod.Client;
+const Monitor = monitor_mod.Monitor;
+
+pub const layout = monitor_mod.Layout{
+    .symbol = "[M]",
+    .arrange_fn = monocle,
+};
+
+pub fn monocle(monitor: *Monitor) void {
+    const gap_h = monitor.gap_outer_h;
+    const gap_v = monitor.gap_outer_v;
+
+    var current = client_mod.next_tiled(monitor.clients);
+    while (current) |client| {
+        tiling.resize(
+            client,
+            monitor.win_x + gap_v,
+            monitor.win_y + gap_h,
+            monitor.win_w - 2 * gap_v - 2 * client.border_width,
+            monitor.win_h - 2 * gap_h - 2 * client.border_width,
+            false,
+        );
+        current = client_mod.next_tiled(client.next);
+    }
+}
diff --git a/src/layouts/scrolling.zig b/src/layouts/scrolling.zig
new file mode 100644
index 0000000..cfb173d
--- /dev/null
+++ b/src/layouts/scrolling.zig
@@ -0,0 +1,114 @@
+const std = @import("std");
+const client_mod = @import("../client.zig");
+const monitor_mod = @import("../monitor.zig");
+const xlib = @import("../x11/xlib.zig");
+const tiling = @import("tiling.zig");
+
+const Client = client_mod.Client;
+const Monitor = monitor_mod.Monitor;
+
+pub const layout = monitor_mod.Layout{
+    .symbol = "[S]",
+    .arrange_fn = scroll,
+};
+
+pub fn scroll(monitor: *Monitor) void {
+    var client_count: u32 = 0;
+    var current = client_mod.next_tiled(monitor.clients);
+    while (current) |_| : (current = client_mod.next_tiled(current.?.next)) {
+        client_count += 1;
+    }
+
+    if (client_count == 0) return;
+
+    const gap_outer_h = monitor.gap_outer_h;
+    const gap_outer_v = monitor.gap_outer_v;
+    const gap_inner_v = monitor.gap_inner_v;
+
+    const visible_count: u32 = @intCast(@max(1, monitor.nmaster));
+    const available_width = monitor.win_w - 2 * gap_outer_v;
+    const available_height = monitor.win_h - 2 * gap_outer_h;
+
+    const total_gaps = gap_inner_v * @as(i32, @intCast(if (visible_count > 1) visible_count - 1 else 0));
+    const window_width = @divTrunc(available_width - total_gaps, @as(i32, @intCast(visible_count)));
+
+    var x_pos: i32 = monitor.win_x + gap_outer_v - monitor.scroll_offset;
+    const y_pos: i32 = monitor.win_y + gap_outer_h;
+    const height = available_height;
+
+    var index: u32 = 0;
+    current = client_mod.next_tiled(monitor.clients);
+    while (current) |client| : (current = client_mod.next_tiled(client.next)) {
+        const window_right = x_pos + window_width;
+        const screen_left = monitor.win_x;
+        const screen_right = monitor.win_x + monitor.win_w;
+        const is_visible = window_right > screen_left and x_pos < screen_right;
+
+        if (is_visible) {
+            tiling.resize(
+                client,
+                x_pos,
+                y_pos,
+                window_width - 2 * client.border_width,
+                height - 2 * client.border_width,
+                false,
+            );
+        } else {
+            tiling.resize_client(
+                client,
+                -2 * window_width,
+                y_pos,
+                window_width - 2 * client.border_width,
+                height - 2 * client.border_width,
+            );
+        }
+        x_pos += window_width + gap_inner_v;
+        index += 1;
+    }
+}
+
+pub fn get_scroll_step(monitor: *Monitor) i32 {
+    const gap_outer_v = monitor.gap_outer_v;
+    const gap_inner_v = monitor.gap_inner_v;
+    const visible_count: u32 = @intCast(@max(1, monitor.nmaster));
+    const available_width = monitor.win_w - 2 * gap_outer_v;
+    const total_gaps = gap_inner_v * @as(i32, @intCast(if (visible_count > 1) visible_count - 1 else 0));
+    const window_width = @divTrunc(available_width - total_gaps, @as(i32, @intCast(visible_count)));
+    return window_width + gap_inner_v;
+}
+
+pub fn get_max_scroll(monitor: *Monitor) i32 {
+    var client_count: u32 = 0;
+    var current = client_mod.next_tiled(monitor.clients);
+    while (current) |_| : (current = client_mod.next_tiled(current.?.next)) {
+        client_count += 1;
+    }
+
+    const visible_count: u32 = @intCast(@max(1, monitor.nmaster));
+    if (client_count <= visible_count) return 0;
+
+    const scroll_step = get_scroll_step(monitor);
+    const scrollable = client_count - visible_count;
+    return scroll_step * @as(i32, @intCast(scrollable));
+}
+
+pub fn get_window_index(monitor: *Monitor, target: *Client) ?u32 {
+    var index: u32 = 0;
+    var current = client_mod.next_tiled(monitor.clients);
+    while (current) |client| : (current = client_mod.next_tiled(client.next)) {
+        if (client == target) return index;
+        index += 1;
+    }
+    return null;
+}
+
+pub fn get_target_scroll_for_window(monitor: *Monitor, target: *Client) i32 {
+    const index = get_window_index(monitor, target) orelse return 0;
+    if (index == 0) return 0;
+
+    const scroll_step = get_scroll_step(monitor);
+    const max_scroll = get_max_scroll(monitor);
+
+    const target_scroll = scroll_step * @as(i32, @intCast(index));
+    return @min(target_scroll, max_scroll);
+}
diff --git a/src/layouts/tiling.zig b/src/layouts/tiling.zig
new file mode 100644
index 0000000..ff8487b
--- /dev/null
+++ b/src/layouts/tiling.zig
@@ -0,0 +1,279 @@
+const std = @import("std");
+const client_mod = @import("../client.zig");
+const monitor_mod = @import("../monitor.zig");
+const xlib = @import("../x11/xlib.zig");
+
+const Client = client_mod.Client;
+const Monitor = monitor_mod.Monitor;
+
+pub const layout = monitor_mod.Layout{
+    .symbol = "[]=",
+    .arrange_fn = tile,
+};
+
+pub var display_handle: ?*xlib.Display = null;
+pub var screen_width: i32 = 0;
+pub var screen_height: i32 = 0;
+pub var bar_height: i32 = 0;
+
+pub fn set_display(display: *xlib.Display) void {
+    display_handle = display;
+}
+
+pub fn set_screen_size(width: i32, height: i32) void {
+    screen_width = width;
+    screen_height = height;
+}
+
+pub fn set_bar_height(height: i32) void {
+    bar_height = height;
+}
+
+pub fn tile(monitor: *Monitor) void {
+    var gap_outer_h: i32 = 0;
+    var gap_outer_v: i32 = 0;
+    var gap_inner_h: i32 = 0;
+    var gap_inner_v: i32 = 0;
+    var client_count: u32 = 0;
+
+    get_gaps(monitor, &gap_outer_h, &gap_outer_v, &gap_inner_h, &gap_inner_v, &client_count);
+    if (client_count == 0) return;
+
+    const nmaster: i32 = monitor.nmaster;
+    const nmaster_count: u32 = @intCast(@max(0, nmaster));
+
+    const master_x: i32 = monitor.win_x + gap_outer_v;
+    var master_y: i32 = monitor.win_y + gap_outer_h;
+    const master_height: i32 = monitor.win_h - 2 * gap_outer_h - gap_inner_h * (@as(i32, @intCast(@min(client_count, nmaster_count))) - 1);
+    var master_width: i32 = monitor.win_w - 2 * gap_outer_v;
+
+    var stack_x: i32 = master_x;
+    var stack_y: i32 = monitor.win_y + gap_outer_h;
+    const stack_height: i32 = monitor.win_h - 2 * gap_outer_h - gap_inner_h * (@as(i32, @intCast(client_count)) - nmaster - 1);
+    var stack_width: i32 = master_width;
+
+    if (nmaster > 0 and client_count > nmaster_count) {
+        stack_width = @intFromFloat(@as(f32, @floatFromInt(master_width - gap_inner_v)) * (1.0 - monitor.mfact));
+        master_width = master_width - gap_inner_v - stack_width;
+        stack_x = master_x + master_width + gap_inner_v;
+    }
+
+    var master_facts: f32 = 0;
+    var stack_facts: f32 = 0;
+    var master_rest: i32 = 0;
+    var stack_rest: i32 = 0;
+    get_facts(monitor, master_height, stack_height, &master_facts, &stack_facts, &master_rest, &stack_rest);
+
+    var index: u32 = 0;
+    var current = client_mod.next_tiled(monitor.clients);
+    while (current) |client| : (current = client_mod.next_tiled(client.next)) {
+        if (index < nmaster_count) {
+            const height = @as(i32, @intFromFloat(@as(f32, @floatFromInt(master_height)) / master_facts)) + (if (index < @as(u32, @intCast(master_rest))) @as(i32, 1) else @as(i32, 0)) - 2 * client.border_width;
+            resize(client, master_x, master_y, master_width - 2 * client.border_width, height, false);
+            master_y += get_client_height(client) + gap_inner_h;
+        } else {
+            const stack_index = index - nmaster_count;
+            const height = @as(i32, @intFromFloat(@as(f32, @floatFromInt(stack_height)) / stack_facts)) + (if (stack_index < @as(u32, @intCast(stack_rest))) @as(i32, 1) else @as(i32, 0)) - 2 * client.border_width;
+            resize(client, stack_x, stack_y, stack_width - 2 * client.border_width, height, false);
+            stack_y += get_client_height(client) + gap_inner_h;
+        }
+        index += 1;
+    }
+}
+
+fn get_gaps(monitor: *Monitor, gap_outer_h: *i32, gap_outer_v: *i32, gap_inner_h: *i32, gap_inner_v: *i32, client_count: *u32) void {
+    var count: u32 = 0;
+    var current = client_mod.next_tiled(monitor.clients);
+    while (current) |client| : (current = client_mod.next_tiled(client.next)) {
+        count += 1;
+    }
+
+    gap_outer_h.* = monitor.gap_outer_h;
+    gap_outer_v.* = monitor.gap_outer_v;
+    gap_inner_h.* = monitor.gap_inner_h;
+    gap_inner_v.* = monitor.gap_inner_v;
+    client_count.* = count;
+}
+
+fn get_facts(monitor: *Monitor, master_size: i32, stack_size: i32, master_factor: *f32, stack_factor: *f32, master_rest: *i32, stack_rest: *i32) void {
+    var count: u32 = 0;
+    var current = client_mod.next_tiled(monitor.clients);
+    while (current) |client| : (current = client_mod.next_tiled(client.next)) {
+        count += 1;
+    }
+
+    const nmaster_count: u32 = @intCast(@max(0, monitor.nmaster));
+    const master_facts: f32 = @floatFromInt(@min(count, nmaster_count));
+    const stack_facts: f32 = @floatFromInt(if (count > nmaster_count) count - nmaster_count else 0);
+
+    var master_total: i32 = 0;
+    var stack_total: i32 = 0;
+
+    if (master_facts > 0) {
+        master_total = @as(i32, @intFromFloat(@as(f32, @floatFromInt(master_size)) / master_facts)) * @as(i32, @intFromFloat(master_facts));
+    }
+    if (stack_facts > 0) {
+        stack_total = @as(i32, @intFromFloat(@as(f32, @floatFromInt(stack_size)) / stack_facts)) * @as(i32, @intFromFloat(stack_facts));
+    }
+
+    master_factor.* = master_facts;
+    stack_factor.* = stack_facts;
+    master_rest.* = master_size - master_total;
+    stack_rest.* = stack_size - stack_total;
+}
+
+fn get_client_width(client: *Client) i32 {
+    return client.width + 2 * client.border_width;
+}
+
+fn get_client_height(client: *Client) i32 {
+    return client.height + 2 * client.border_width;
+}
+
+pub fn apply_size_hints(client: *Client, target_x: *i32, target_y: *i32, target_width: *i32, target_height: *i32, interact: bool) bool {
+    const monitor = client.monitor orelse return false;
+
+    target_width.* = @max(1, target_width.*);
+    target_height.* = @max(1, target_height.*);
+
+    if (interact) {
+        if (target_x.* > screen_width) {
+            target_x.* = screen_width - get_client_width(client);
+        }
+        if (target_y.* > screen_height) {
+            target_y.* = screen_height - get_client_height(client);
+        }
+        if (target_x.* + target_width.* + 2 * client.border_width < 0) {
+            target_x.* = 0;
+        }
+        if (target_y.* + target_height.* + 2 * client.border_width < 0) {
+            target_y.* = 0;
+        }
+    } else {
+        if (target_x.* >= monitor.win_x + monitor.win_w) {
+            target_x.* = monitor.win_x + monitor.win_w - get_client_width(client);
+        }
+        if (target_y.* >= monitor.win_y + monitor.win_h) {
+            target_y.* = monitor.win_y + monitor.win_h - get_client_height(client);
+        }
+        if (target_x.* + target_width.* + 2 * client.border_width <= monitor.win_x) {
+            target_x.* = monitor.win_x;
+        }
+        if (target_y.* + target_height.* + 2 * client.border_width <= monitor.win_y) {
+            target_y.* = monitor.win_y;
+        }
+    }
+
+    if (target_height.* < bar_height) {
+        target_height.* = bar_height;
+    }
+    if (target_width.* < bar_height) {
+        target_width.* = bar_height;
+    }
+
+    if (client.is_floating or monitor.lt[monitor.sel_lt] == null) {
+        const base_is_min = client.base_width == client.min_width and client.base_height == client.min_height;
+
+        var adjusted_width = target_width.*;
+        var adjusted_height = target_height.*;
+
+        if (!base_is_min) {
+            adjusted_width -= client.base_width;
+            adjusted_height -= client.base_height;
+        }
+
+        if (client.min_aspect > 0 and client.max_aspect > 0) {
+            const width_float: f32 = @floatFromInt(adjusted_width);
+            const height_float: f32 = @floatFromInt(adjusted_height);
+            if (client.max_aspect < width_float / height_float) {
+                adjusted_width = @intFromFloat(height_float * client.max_aspect + 0.5);
+            } else if (client.min_aspect < height_float / width_float) {
+                adjusted_height = @intFromFloat(width_float * client.min_aspect + 0.5);
+            }
+        }
+
+        if (base_is_min) {
+            adjusted_width -= client.base_width;
+            adjusted_height -= client.base_height;
+        }
+
+        if (client.increment_width > 0) {
+            adjusted_width -= @mod(adjusted_width, client.increment_width);
+        }
+        if (client.increment_height > 0) {
+            adjusted_height -= @mod(adjusted_height, client.increment_height);
+        }
+
+        target_width.* = @max(adjusted_width + client.base_width, client.min_width);
+        target_height.* = @max(adjusted_height + client.base_height, client.min_height);
+
+        if (client.max_width > 0) {
+            target_width.* = @min(target_width.*, client.max_width);
+        }
+        if (client.max_height > 0) {
+            target_height.* = @min(target_height.*, client.max_height);
+        }
+    }
+
+    return target_x.* != client.x or target_y.* != client.y or target_width.* != client.width or target_height.* != client.height;
+}
+
+pub fn resize(client: *Client, target_x: i32, target_y: i32, target_width: i32, target_height: i32, interact: bool) void {
+    var final_x = target_x;
+    var final_y = target_y;
+    var final_width = target_width;
+    var final_height = target_height;
+
+    if (apply_size_hints(client, &final_x, &final_y, &final_width, &final_height, interact)) {
+        resize_client(client, final_x, final_y, final_width, final_height);
+    }
+}
+
+pub fn resize_client(client: *Client, target_x: i32, target_y: i32, target_width: i32, target_height: i32) void {
+    client.old_x = client.x;
+    client.old_y = client.y;
+    client.old_width = client.width;
+    client.old_height = client.height;
+    client.x = target_x;
+    client.y = target_y;
+    client.width = target_width;
+    client.height = target_height;
+
+    const display = display_handle orelse return;
+
+    var window_changes: xlib.c.XWindowChanges = undefined;
+    window_changes.x = target_x;
+    window_changes.y = target_y;
+    window_changes.width = @intCast(@max(1, target_width));
+    window_changes.height = @intCast(@max(1, target_height));
+    window_changes.border_width = client.border_width;
+
+    _ = xlib.c.XConfigureWindow(
+        display,
+        client.window,
+        xlib.c.CWX | xlib.c.CWY | xlib.c.CWWidth | xlib.c.CWHeight | xlib.c.CWBorderWidth,
+        &window_changes,
+    );
+
+    send_configure(client);
+    _ = xlib.XSync(display, xlib.False);
+}
+
+pub fn send_configure(client: *Client) void {
+    const display = display_handle orelse return;
+
+    var configure_event: xlib.c.XConfigureEvent = undefined;
+    configure_event.type = xlib.c.ConfigureNotify;
+    configure_event.display = display;
+    configure_event.event = client.window;
+    configure_event.window = client.window;
+    configure_event.x = client.x;
+    configure_event.y = client.y;
+    configure_event.width = client.width;
+    configure_event.height = client.height;
+    configure_event.border_width = client.border_width;
+    configure_event.above = xlib.None;
+    configure_event.override_redirect = xlib.False;
+
+    _ = xlib.c.XSendEvent(display, client.window, xlib.False, xlib.StructureNotifyMask, @ptrCast(&configure_event));
+}
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index 378f303..0000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,362 +0,0 @@
-use std::path::PathBuf;
-
-pub mod animations;
-pub mod bar;
-pub mod client;
-pub mod config;
-pub mod errors;
-pub mod keyboard;
-pub mod layout;
-pub mod monitor;
-pub mod overlay;
-pub mod signal;
-pub mod size_hints;
-pub mod tab_bar;
-pub mod window_manager;
-
-pub mod prelude {
-    pub use crate::ColorScheme;
-    pub use crate::LayoutSymbolOverride;
-    pub use crate::WindowRule;
-    pub use crate::bar::{BlockCommand, BlockConfig};
-    pub use crate::keyboard::{Arg, KeyAction, handlers::KeyBinding, keysyms};
-    pub use x11rb::protocol::xproto::KeyButMask;
-}
-
-#[derive(Debug, Clone)]
-pub struct LayoutSymbolOverride {
-    pub name: String,
-    pub symbol: String,
-}
-
-#[derive(Debug, Clone)]
-pub struct WindowRule {
-    pub class: Option<String>,
-    pub instance: Option<String>,
-    pub title: Option<String>,
-    pub tags: Option<u32>,
-    pub focus: Option<bool>,
-    pub is_floating: Option<bool>,
-    pub monitor: Option<usize>,
-}
-
-impl WindowRule {
-    pub fn matches(&self, class: &str, instance: &str, title: &str) -> bool {
-        let class_matches = self
-            .class
-            .as_ref()
-            .is_none_or(|c| class.contains(c.as_str()));
-        let instance_matches = self
-            .instance
-            .as_ref()
-            .is_none_or(|i| instance.contains(i.as_str()));
-        let title_matches = self
-            .title
-            .as_ref()
-            .is_none_or(|t| title.contains(t.as_str()));
-        class_matches && instance_matches && title_matches
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Config {
-    // Meta
-    pub path: Option<PathBuf>,
-
-    // Appearance
-    pub border_width: u32,
-    pub border_focused: u32,
-    pub border_unfocused: u32,
-    pub font: String,
-
-    // Gaps
-    pub gaps_enabled: bool,
-    pub smartgaps_enabled: bool,
-    pub gap_inner_horizontal: u32,
-    pub gap_inner_vertical: u32,
-    pub gap_outer_horizontal: u32,
-    pub gap_outer_vertical: u32,
-
-    // Basics
-    pub terminal: String,
-    pub modkey: x11rb::protocol::xproto::KeyButMask,
-
-    // Tags
-    pub tags: Vec<String>,
-
-    // Layout symbol overrides
-    pub layout_symbols: Vec<LayoutSymbolOverride>,
-
-    // Keybindings
-    pub keybindings: Vec<crate::keyboard::handlers::Key>,
-    pub tag_back_and_forth: bool,
-
-    // Window rules
-    pub window_rules: Vec<WindowRule>,
-
-    // Status bar
-    pub status_blocks: Vec<crate::bar::BlockConfig>,
-
-    // Bar color schemes
-    pub scheme_normal: ColorScheme,
-    pub scheme_occupied: ColorScheme,
-    pub scheme_selected: ColorScheme,
-    pub scheme_urgent: ColorScheme,
-
-    pub autostart: Vec<String>,
-    pub auto_tile: bool,
-    pub hide_vacant_tags: bool,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct ColorScheme {
-    pub foreground: u32,
-    pub background: u32,
-    pub underline: u32,
-}
-
-impl Default for Config {
-    fn default() -> Self {
-        use crate::keyboard::handlers::KeyBinding;
-        use crate::keyboard::{Arg, KeyAction, keysyms};
-        use x11rb::protocol::xproto::KeyButMask;
-
-        const MODKEY: KeyButMask = KeyButMask::MOD4;
-        const SHIFT: KeyButMask = KeyButMask::SHIFT;
-
-        const TERMINAL: &str = "st";
-
-        Self {
-            path: None,
-            border_width: 2,
-            border_focused: 0x6dade3,
-            border_unfocused: 0xbbbbbb,
-            font: "monospace:size=10".to_string(),
-            gaps_enabled: false,
-            smartgaps_enabled: true,
-            gap_inner_horizontal: 0,
-            gap_inner_vertical: 0,
-            gap_outer_horizontal: 0,
-            gap_outer_vertical: 0,
-            terminal: TERMINAL.to_string(),
-            modkey: MODKEY,
-            tags: vec!["1", "2", "3", "4", "5", "6", "7", "8", "9"]
-                .into_iter()
-                .map(String::from)
-                .collect(),
-            layout_symbols: vec![],
-            keybindings: vec![
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_RETURN,
-                    KeyAction::Spawn,
-                    Arg::Str(TERMINAL.to_string()),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_D,
-                    KeyAction::Spawn,
-                    Arg::Array(vec![
-                        "sh".to_string(),
-                        "-c".to_string(),
-                        "dmenu_run -l 10".to_string(),
-                    ]),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_Q,
-                    KeyAction::KillClient,
-                    Arg::None,
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_N,
-                    KeyAction::CycleLayout,
-                    Arg::None,
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_F,
-                    KeyAction::ToggleFullScreen,
-                    Arg::None,
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_A,
-                    KeyAction::ToggleGaps,
-                    Arg::None,
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_Q,
-                    KeyAction::Quit,
-                    Arg::None,
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_R,
-                    KeyAction::Restart,
-                    Arg::None,
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_F,
-                    KeyAction::ToggleFloating,
-                    Arg::None,
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_J,
-                    KeyAction::FocusStack,
-                    Arg::Int(-1),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_K,
-                    KeyAction::FocusStack,
-                    Arg::Int(1),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_1,
-                    KeyAction::ViewTag,
-                    Arg::Int(0),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_2,
-                    KeyAction::ViewTag,
-                    Arg::Int(1),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_3,
-                    KeyAction::ViewTag,
-                    Arg::Int(2),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_4,
-                    KeyAction::ViewTag,
-                    Arg::Int(3),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_5,
-                    KeyAction::ViewTag,
-                    Arg::Int(4),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_6,
-                    KeyAction::ViewTag,
-                    Arg::Int(5),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_7,
-                    KeyAction::ViewTag,
-                    Arg::Int(6),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_8,
-                    KeyAction::ViewTag,
-                    Arg::Int(7),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY],
-                    keysyms::XK_9,
-                    KeyAction::ViewTag,
-                    Arg::Int(8),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_1,
-                    KeyAction::MoveToTag,
-                    Arg::Int(0),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_2,
-                    KeyAction::MoveToTag,
-                    Arg::Int(1),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_3,
-                    KeyAction::MoveToTag,
-                    Arg::Int(2),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_4,
-                    KeyAction::MoveToTag,
-                    Arg::Int(3),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_5,
-                    KeyAction::MoveToTag,
-                    Arg::Int(4),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_6,
-                    KeyAction::MoveToTag,
-                    Arg::Int(5),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_7,
-                    KeyAction::MoveToTag,
-                    Arg::Int(6),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_8,
-                    KeyAction::MoveToTag,
-                    Arg::Int(7),
-                ),
-                KeyBinding::single_key(
-                    vec![MODKEY, SHIFT],
-                    keysyms::XK_9,
-                    KeyAction::MoveToTag,
-                    Arg::Int(8),
-                ),
-            ],
-            tag_back_and_forth: false,
-            window_rules: vec![],
-            status_blocks: vec![crate::bar::BlockConfig {
-                format: "{}".to_string(),
-                command: crate::bar::BlockCommand::DateTime("%a, %b %d - %-I:%M %P".to_string()),
-                interval_secs: 1,
-                color: 0x0db9d7,
-                underline: true,
-            }],
-            scheme_normal: ColorScheme {
-                foreground: 0xbbbbbb,
-                background: 0x1a1b26,
-                underline: 0x444444,
-            },
-            scheme_occupied: ColorScheme {
-                foreground: 0x0db9d7,
-                background: 0x1a1b26,
-                underline: 0x0db9d7,
-            },
-            scheme_selected: ColorScheme {
-                foreground: 0x0db9d7,
-                background: 0x1a1b26,
-                underline: 0xad8ee6,
-            },
-            scheme_urgent: ColorScheme {
-                foreground: 0xff5555,
-                background: 0x1a1b26,
-                underline: 0xff5555,
-            },
-            autostart: vec![],
-            auto_tile: false,
-            hide_vacant_tags: false,
-        }
-    }
-}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..df7e022
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,2291 @@
+const std = @import("std");
+const display_mod = @import("x11/display.zig");
+const events = @import("x11/events.zig");
+const xlib = @import("x11/xlib.zig");
+const client_mod = @import("client.zig");
+const monitor_mod = @import("monitor.zig");
+const tiling = @import("layouts/tiling.zig");
+const monocle = @import("layouts/monocle.zig");
+const floating = @import("layouts/floating.zig");
+const scrolling = @import("layouts/scrolling.zig");
+const animations = @import("animations.zig");
+const bar_mod = @import("bar/bar.zig");
+const blocks_mod = @import("bar/blocks/blocks.zig");
+const config_mod = @import("config/config.zig");
+const lua = @import("config/lua.zig");
+
+const Display = display_mod.Display;
+const Client = client_mod.Client;
+const Monitor = monitor_mod.Monitor;
+
+var running: bool = true;
+var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+
+var wm_protocols: xlib.Atom = 0;
+var wm_delete: xlib.Atom = 0;
+var wm_state: xlib.Atom = 0;
+var wm_take_focus: xlib.Atom = 0;
+
+var net_supported: xlib.Atom = 0;
+var net_wm_name: xlib.Atom = 0;
+var net_wm_state: xlib.Atom = 0;
+var net_wm_check: xlib.Atom = 0;
+var net_wm_state_fullscreen: xlib.Atom = 0;
+var net_active_window: xlib.Atom = 0;
+var net_wm_window_type: xlib.Atom = 0;
+var net_wm_window_type_dialog: xlib.Atom = 0;
+var net_client_list: xlib.Atom = 0;
+
+var wm_check_window: xlib.Window = 0;
+
+var border_color_focused: c_ulong = 0x6dade3;
+var border_color_unfocused: c_ulong = 0x444444;
+var border_width: i32 = 2;
+var gap_outer_v: i32 = 5;
+var gap_outer_h: i32 = 5;
+var gap_inner_h: i32 = 5;
+var gap_inner_v: i32 = 5;
+
+var tags: [9][]const u8 = .{ "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+var cursor_normal: xlib.Cursor = 0;
+var cursor_resize: xlib.Cursor = 0;
+var cursor_move: xlib.Cursor = 0;
+
+const NormalState: c_long = 1;
+const WithdrawnState: c_long = 0;
+const IconicState: c_long = 3;
+const IsViewable: c_int = 2;
+const snap_distance: i32 = 32;
+
+var numlock_mask: c_uint = 0;
+
+var config: config_mod.Config = undefined;
+var display_global: ?*Display = null;
+var config_path_global: ?[]const u8 = null;
+
+var scroll_animation: animations.Scroll_Animation = .{};
+var animation_config: animations.Animation_Config = .{ .duration_ms = 150, .easing = .ease_out };
+
+pub fn main() !void {
+    const allocator = gpa.allocator();
+    defer _ = gpa.deinit();
+
+    std.debug.print("oxwm starting\n", .{});
+
+    var config_path: ?[]const u8 = null;
+    var args = std.process.args();
+    _ = args.skip();
+    while (args.next()) |arg| {
+        if (std.mem.eql(u8, arg, "-c") or std.mem.eql(u8, arg, "--config")) {
+            config_path = args.next();
+        } else if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) {
+            std.debug.print("usage: oxwm [-c config.lua]\n", .{});
+            return;
+        }
+    }
+
+    config = config_mod.Config.init(allocator);
+    defer config.deinit();
+    config_mod.set_config(&config);
+
+    if (lua.init(&config)) {
+        const loaded = if (config_path) |path|
+            lua.load_file(path)
+        else
+            lua.load_config();
+
+        if (loaded) {
+            config_path_global = config_path;
+            if (config_path) |path| {
+                std.debug.print("loaded config from {s}\n", .{path});
+            } else {
+                std.debug.print("loaded config from ~/.config/oxwm/config.lua\n", .{});
+            }
+            apply_config_values();
+        } else {
+            std.debug.print("no config found, using defaults\n", .{});
+            setup_default_keybinds();
+        }
+    } else {
+        std.debug.print("failed to init lua, using defaults\n", .{});
+        setup_default_keybinds();
+    }
+
+    var display = Display.open() catch |err| {
+        std.debug.print("failed to open display: {}\n", .{err});
+        return;
+    };
+    defer display.close();
+
+    display_global = &display;
+
+    std.debug.print("display opened: screen={d} root=0x{x}\n", .{ display.screen, display.root });
+    std.debug.print("screen size: {d}x{d}\n", .{ display.screen_width(), display.screen_height() });
+
+    display.become_window_manager() catch |err| {
+        std.debug.print("failed to become window manager: {}\n", .{err});
+        return;
+    };
+
+    std.debug.print("successfully became window manager\n", .{});
+
+    setup_atoms(&display);
+    setup_cursors(&display);
+    client_mod.init(allocator);
+    monitor_mod.init(allocator);
+    monitor_mod.set_root_window(display.root, display.handle);
+    tiling.set_display(display.handle);
+    tiling.set_screen_size(display.screen_width(), display.screen_height());
+
+    setup_monitors(&display);
+    setup_bars(allocator, &display);
+    grab_keybinds(&display);
+    scan_existing_windows(&display);
+
+    std.debug.print("entering event loop\n", .{});
+    run_event_loop(&display);
+
+    lua.deinit();
+    std.debug.print("oxwm exiting\n", .{});
+}
+
+fn setup_atoms(display: *Display) void {
+    wm_protocols = xlib.XInternAtom(display.handle, "WM_PROTOCOLS", xlib.False);
+    wm_delete = xlib.XInternAtom(display.handle, "WM_DELETE_WINDOW", xlib.False);
+    wm_state = xlib.XInternAtom(display.handle, "WM_STATE", xlib.False);
+    wm_take_focus = xlib.XInternAtom(display.handle, "WM_TAKE_FOCUS", xlib.False);
+
+    net_active_window = xlib.XInternAtom(display.handle, "_NET_ACTIVE_WINDOW", xlib.False);
+    net_supported = xlib.XInternAtom(display.handle, "_NET_SUPPORTED", xlib.False);
+    net_wm_name = xlib.XInternAtom(display.handle, "_NET_WM_NAME", xlib.False);
+    net_wm_state = xlib.XInternAtom(display.handle, "_NET_WM_STATE", xlib.False);
+    net_wm_check = xlib.XInternAtom(display.handle, "_NET_SUPPORTING_WM_CHECK", xlib.False);
+    net_wm_state_fullscreen = xlib.XInternAtom(display.handle, "_NET_WM_STATE_FULLSCREEN", xlib.False);
+    net_wm_window_type = xlib.XInternAtom(display.handle, "_NET_WM_WINDOW_TYPE", xlib.False);
+    net_wm_window_type_dialog = xlib.XInternAtom(display.handle, "_NET_WM_WINDOW_TYPE_DIALOG", xlib.False);
+    net_client_list = xlib.XInternAtom(display.handle, "_NET_CLIENT_LIST", xlib.False);
+
+    const utf8_string = xlib.XInternAtom(display.handle, "UTF8_STRING", xlib.False);
+
+    wm_check_window = xlib.XCreateSimpleWindow(display.handle, display.root, 0, 0, 1, 1, 0, 0, 0);
+    _ = xlib.XChangeProperty(display.handle, wm_check_window, net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&wm_check_window), 1);
+    _ = xlib.XChangeProperty(display.handle, wm_check_window, net_wm_name, utf8_string, 8, xlib.PropModeReplace, "oxwm", 6);
+    _ = xlib.XChangeProperty(display.handle, display.root, net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&wm_check_window), 1);
+
+    var net_atoms = [_]xlib.Atom{ net_supported, net_wm_name, net_wm_state, net_wm_check, net_wm_state_fullscreen, net_active_window, net_wm_window_type, net_wm_window_type_dialog, net_client_list };
+    _ = xlib.XChangeProperty(display.handle, display.root, net_supported, xlib.XA_ATOM, 32, xlib.PropModeReplace, @ptrCast(&net_atoms), net_atoms.len);
+
+    _ = xlib.XDeleteProperty(display.handle, display.root, net_client_list);
+
+    std.debug.print("atoms initialized with EWMH support\n", .{});
+}
+
+fn setup_cursors(display: *Display) void {
+    cursor_normal = xlib.XCreateFontCursor(display.handle, xlib.XC_left_ptr);
+    cursor_resize = xlib.XCreateFontCursor(display.handle, xlib.XC_sizing);
+    cursor_move = xlib.XCreateFontCursor(display.handle, xlib.XC_fleur);
+    _ = xlib.XDefineCursor(display.handle, display.root, cursor_normal);
+}
+
+fn setup_bars(allocator: std.mem.Allocator, display: *Display) void {
+    var current_monitor = monitor_mod.monitors;
+    var last_bar: ?*bar_mod.Bar = null;
+
+    while (current_monitor) |monitor| {
+        const bar = bar_mod.Bar.create(allocator, display.handle, display.screen, monitor, config.font);
+        if (bar) |created_bar| {
+            if (tiling.bar_height == 0) {
+                tiling.set_bar_height(created_bar.height);
+            }
+
+            if (config.blocks.items.len > 0) {
+                for (config.blocks.items) |cfg_block| {
+                    const block = config_block_to_bar_block(cfg_block);
+                    created_bar.add_block(block);
+                }
+            } else {
+                created_bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
+                created_bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
+                created_bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
+            }
+
+            if (last_bar) |prev| {
+                prev.next = created_bar;
+            } else {
+                bar_mod.bars = created_bar;
+            }
+            last_bar = created_bar;
+            std.debug.print("bar created for monitor {d}\n", .{monitor.num});
+        }
+        current_monitor = monitor.next;
+    }
+}
+
+fn config_block_to_bar_block(cfg: config_mod.Block) blocks_mod.Block {
+    return switch (cfg.block_type) {
+        .static => blocks_mod.Block.init_static(cfg.format, cfg.color, cfg.underline),
+        .datetime => blocks_mod.Block.init_datetime(cfg.format, cfg.datetime_format orelse "%H:%M", cfg.interval, cfg.color, cfg.underline),
+        .ram => blocks_mod.Block.init_ram(cfg.format, cfg.interval, cfg.color, cfg.underline),
+        .shell => blocks_mod.Block.init_shell(cfg.format, cfg.command orelse "", cfg.interval, cfg.color, cfg.underline),
+        .battery => blocks_mod.Block.init_battery(
+            cfg.format_charging orelse "",
+            cfg.format_discharging orelse "",
+            cfg.format_full orelse "",
+            cfg.battery_name orelse "BAT0",
+            cfg.interval,
+            cfg.color,
+            cfg.underline,
+        ),
+        .cpu_temp => blocks_mod.Block.init_cpu_temp(
+            cfg.format,
+            cfg.thermal_zone orelse "thermal_zone0",
+            cfg.interval,
+            cfg.color,
+            cfg.underline,
+        ),
+    };
+}
+
+fn setup_monitors(display: *Display) void {
+    std.debug.print("checking xinerama...\n", .{});
+    if (xlib.XineramaIsActive(display.handle) != 0) {
+        std.debug.print("xinerama is active!\n", .{});
+        var screen_count: c_int = 0;
+        const screens = xlib.XineramaQueryScreens(display.handle, &screen_count);
+
+        if (screen_count > 0 and screens != null) {
+            var prev_monitor: ?*Monitor = null;
+            var index: usize = 0;
+
+            while (index < @as(usize, @intCast(screen_count))) : (index += 1) {
+                const screen = screens[index];
+                const mon = monitor_mod.create() orelse continue;
+
+                mon.num = @intCast(index);
+                mon.mon_x = screen.x_org;
+                mon.mon_y = screen.y_org;
+                mon.mon_w = screen.width;
+                mon.mon_h = screen.height;
+                mon.win_x = screen.x_org;
+                mon.win_y = screen.y_org;
+                mon.win_w = screen.width;
+                mon.win_h = screen.height;
+                mon.lt[0] = &tiling.layout;
+                mon.lt[1] = &monocle.layout;
+                mon.lt[2] = &floating.layout;
+                mon.lt[3] = &scrolling.layout;
+                for (0..10) |i| {
+                    mon.pertag.ltidxs[i][0] = mon.lt[0];
+                    mon.pertag.ltidxs[i][1] = mon.lt[1];
+                    mon.pertag.ltidxs[i][2] = mon.lt[2];
+                    mon.pertag.ltidxs[i][3] = mon.lt[3];
+                }
+
+                if (prev_monitor) |prev| {
+                    prev.next = mon;
+                } else {
+                    monitor_mod.monitors = mon;
+                }
+                prev_monitor = mon;
+
+                std.debug.print("monitor {d}: {d}x{d} at ({d},{d})\n", .{ index, mon.mon_w, mon.mon_h, mon.mon_x, mon.mon_y });
+            }
+
+            monitor_mod.selected_monitor = monitor_mod.monitors;
+            _ = xlib.XFree(@ptrCast(screens));
+            return;
+        }
+    } else {
+        std.debug.print("xinerama not active, using single monitor\n", .{});
+    }
+
+    const mon = monitor_mod.create() orelse return;
+    mon.mon_x = 0;
+    mon.mon_y = 0;
+    mon.mon_w = display.screen_width();
+    mon.mon_h = display.screen_height();
+    mon.win_x = 0;
+    mon.win_y = 0;
+    mon.win_w = display.screen_width();
+    mon.win_h = display.screen_height();
+    mon.lt[0] = &tiling.layout;
+    mon.lt[1] = &monocle.layout;
+    mon.lt[2] = &floating.layout;
+    mon.lt[3] = &scrolling.layout;
+    for (0..10) |i| {
+        mon.pertag.ltidxs[i][0] = mon.lt[0];
+        mon.pertag.ltidxs[i][1] = mon.lt[1];
+        mon.pertag.ltidxs[i][2] = mon.lt[2];
+        mon.pertag.ltidxs[i][3] = mon.lt[3];
+    }
+    monitor_mod.monitors = mon;
+    monitor_mod.selected_monitor = mon;
+    std.debug.print("monitor created: {d}x{d}\n", .{ mon.mon_w, mon.mon_h });
+}
+
+fn apply_config_values() void {
+    border_color_focused = config.border_focused;
+    border_color_unfocused = config.border_unfocused;
+    border_width = config.border_width;
+    gap_inner_h = config.gap_inner_h;
+    gap_inner_v = config.gap_inner_v;
+    gap_outer_h = config.gap_outer_h;
+    gap_outer_v = config.gap_outer_v;
+    tags = config.tags;
+}
+
+fn setup_default_keybinds() void {
+    const mod_key: u32 = 1 << 6;
+    const shift_key: u32 = 1 << 0;
+    const control_key: u32 = 1 << 2;
+
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0xff0d, .action = .spawn_terminal }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'd', .action = .spawn, .str_arg = "rofi -show drun" }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 's', .action = .spawn, .str_arg = "maim -s | xclip -selection clipboard -t image/png" }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'q', .action = .kill_client }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'q', .action = .quit }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'r', .action = .reload_config }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'j', .action = .focus_next }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'k', .action = .focus_prev }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'j', .action = .move_next }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'k', .action = .move_prev }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'h', .action = .resize_master, .int_arg = -50 }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'l', .action = .resize_master, .int_arg = 50 }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'i', .action = .inc_master }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'p', .action = .dec_master }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'a', .action = .toggle_gaps }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'f', .action = .toggle_fullscreen }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0x0020, .action = .toggle_floating }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'n', .action = .cycle_layout }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0x002c, .action = .focus_monitor, .int_arg = -1 }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0x002e, .action = .focus_monitor, .int_arg = 1 }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 0x002c, .action = .send_to_monitor, .int_arg = -1 }) catch {};
+    config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 0x002e, .action = .send_to_monitor, .int_arg = 1 }) catch {};
+
+    var tag_index: i32 = 0;
+    while (tag_index < 9) : (tag_index += 1) {
+        const keysym: u64 = @as(u64, '1') + @as(u64, @intCast(tag_index));
+        config.add_keybind(.{ .mod_mask = mod_key, .keysym = keysym, .action = .view_tag, .int_arg = tag_index }) catch {};
+        config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = keysym, .action = .move_to_tag, .int_arg = tag_index }) catch {};
+        config.add_keybind(.{ .mod_mask = mod_key | control_key, .keysym = keysym, .action = .toggle_view_tag, .int_arg = tag_index }) catch {};
+        config.add_keybind(.{ .mod_mask = mod_key | control_key | shift_key, .keysym = keysym, .action = .toggle_tag, .int_arg = tag_index }) catch {};
+    }
+}
+
+fn grab_keybinds(display: *Display) void {
+    update_numlock_mask(display);
+    const modifiers = [_]c_uint{ 0, xlib.LockMask, numlock_mask, numlock_mask | xlib.LockMask };
+
+    _ = xlib.XUngrabKey(display.handle, xlib.AnyKey, xlib.AnyModifier, display.root);
+
+    for (config.keybinds.items) |keybind| {
+        const keycode = xlib.XKeysymToKeycode(display.handle, @intCast(keybind.keysym));
+        if (keycode != 0) {
+            for (modifiers) |modifier| {
+                _ = xlib.XGrabKey(
+                    display.handle,
+                    keycode,
+                    keybind.mod_mask | modifier,
+                    display.root,
+                    xlib.True,
+                    xlib.GrabModeAsync,
+                    xlib.GrabModeAsync,
+                );
+            }
+        }
+    }
+
+    for (config.buttons.items) |button| {
+        if (button.click == .client_win) {
+            for (modifiers) |modifier| {
+                _ = xlib.XGrabButton(
+                    display.handle,
+                    @intCast(button.button),
+                    button.mod_mask | modifier,
+                    display.root,
+                    xlib.True,
+                    xlib.ButtonPressMask | xlib.ButtonReleaseMask | xlib.PointerMotionMask,
+                    xlib.GrabModeAsync,
+                    xlib.GrabModeAsync,
+                    xlib.None,
+                    xlib.None,
+                );
+            }
+        }
+    }
+
+    std.debug.print("grabbed {d} keybinds from config\n", .{config.keybinds.items.len});
+}
+
+fn get_state(display: *Display, window: xlib.Window) c_long {
+    var actual_type: xlib.Atom = 0;
+    var actual_format: c_int = 0;
+    var num_items: c_ulong = 0;
+    var bytes_after: c_ulong = 0;
+    var prop: [*c]u8 = null;
+
+    const result = xlib.XGetWindowProperty(
+        display.handle,
+        window,
+        wm_state,
+        0,
+        2,
+        xlib.False,
+        wm_state,
+        &actual_type,
+        &actual_format,
+        &num_items,
+        &bytes_after,
+        &prop,
+    );
+
+    if (result != 0 or actual_type != wm_state or num_items < 1) {
+        if (prop != null) {
+            _ = xlib.XFree(prop);
+        }
+        return WithdrawnState;
+    }
+
+    const state: c_long = @as(*c_long, @alignCast(@ptrCast(prop))).*;
+    _ = xlib.XFree(prop);
+    return state;
+}
+
+fn scan_existing_windows(display: *Display) void {
+    var root_return: xlib.Window = undefined;
+    var parent_return: xlib.Window = undefined;
+    var children: [*c]xlib.Window = undefined;
+    var num_children: c_uint = undefined;
+
+    if (xlib.XQueryTree(display.handle, display.root, &root_return, &parent_return, &children, &num_children) == 0) {
+        return;
+    }
+
+    var index: c_uint = 0;
+    while (index < num_children) : (index += 1) {
+        var window_attrs: xlib.XWindowAttributes = undefined;
+        if (xlib.XGetWindowAttributes(display.handle, children[index], &window_attrs) == 0) {
+            continue;
+        }
+        if (window_attrs.override_redirect != 0) {
+            continue;
+        }
+        var trans: xlib.Window = 0;
+        if (xlib.XGetTransientForHint(display.handle, children[index], &trans) != 0) {
+            continue;
+        }
+        if (window_attrs.map_state == IsViewable or get_state(display, children[index]) == IconicState) {
+            manage(display, children[index], &window_attrs);
+        }
+    }
+
+    index = 0;
+    while (index < num_children) : (index += 1) {
+        var window_attrs: xlib.XWindowAttributes = undefined;
+        if (xlib.XGetWindowAttributes(display.handle, children[index], &window_attrs) == 0) {
+            continue;
+        }
+        var trans: xlib.Window = 0;
+        if (xlib.XGetTransientForHint(display.handle, children[index], &trans) != 0) {
+            if (window_attrs.map_state == IsViewable or get_state(display, children[index]) == IconicState) {
+                manage(display, children[index], &window_attrs);
+            }
+        }
+    }
+
+    if (children != null) {
+        _ = xlib.XFree(@ptrCast(children));
+    }
+}
+
+fn run_event_loop(display: *Display) void {
+    const x11_fd = xlib.XConnectionNumber(display.handle);
+    var fds = [_]std.posix.pollfd{
+        .{ .fd = x11_fd, .events = std.posix.POLL.IN, .revents = 0 },
+    };
+
+    _ = xlib.XSync(display.handle, xlib.False);
+
+    while (running) {
+        while (xlib.XPending(display.handle) > 0) {
+            var event = display.next_event();
+            handle_event(display, &event);
+        }
+
+        tick_animations();
+
+        var current_bar = bar_mod.bars;
+        while (current_bar) |bar| {
+            bar.update_blocks();
+            bar.draw(display.handle, &tags);
+            current_bar = bar.next;
+        }
+
+        const poll_timeout: i32 = if (scroll_animation.is_active()) 16 else 1000;
+        _ = std.posix.poll(&fds, poll_timeout) catch 0;
+    }
+}
+
+fn handle_event(display: *Display, event: *xlib.XEvent) void {
+    const event_type = events.get_event_type(event);
+
+    if (event_type == .button_press) {
+        std.debug.print("EVENT: button_press received type={d}\n", .{event.type});
+    }
+
+    switch (event_type) {
+        .map_request => handle_map_request(display, &event.xmaprequest),
+        .configure_request => handle_configure_request(display, &event.xconfigurerequest),
+        .key_press => handle_key_press(display, &event.xkey),
+        .destroy_notify => handle_destroy_notify(display, &event.xdestroywindow),
+        .unmap_notify => handle_unmap_notify(display, &event.xunmap),
+        .enter_notify => handle_enter_notify(display, &event.xcrossing),
+        .focus_in => handle_focus_in(display, &event.xfocus),
+        .motion_notify => handle_motion_notify(display, &event.xmotion),
+        .client_message => handle_client_message(display, &event.xclient),
+        .button_press => handle_button_press(display, &event.xbutton),
+        .expose => handle_expose(display, &event.xexpose),
+        .property_notify => handle_property_notify(display, &event.xproperty),
+        else => {},
+    }
+}
+
+fn handle_map_request(display: *Display, event: *xlib.XMapRequestEvent) void {
+    std.debug.print("map_request: window=0x{x}\n", .{event.window});
+
+    var window_attributes: xlib.XWindowAttributes = undefined;
+    if (xlib.XGetWindowAttributes(display.handle, event.window, &window_attributes) == 0) {
+        return;
+    }
+    if (window_attributes.override_redirect != 0) {
+        return;
+    }
+    if (client_mod.window_to_client(event.window) != null) {
+        return;
+    }
+
+    manage(display, event.window, &window_attributes);
+}
+
+fn manage(display: *Display, win: xlib.Window, window_attrs: *xlib.XWindowAttributes) void {
+    const client = client_mod.create(win) orelse return;
+    var trans: xlib.Window = 0;
+
+    client.x = window_attrs.x;
+    client.y = window_attrs.y;
+    client.width = window_attrs.width;
+    client.height = window_attrs.height;
+    client.old_x = window_attrs.x;
+    client.old_y = window_attrs.y;
+    client.old_width = window_attrs.width;
+    client.old_height = window_attrs.height;
+    client.old_border_width = window_attrs.border_width;
+    client.border_width = border_width;
+
+    update_title(display, client);
+
+    if (xlib.XGetTransientForHint(display.handle, win, &trans) != 0) {
+        if (client_mod.window_to_client(trans)) |transient_client| {
+            client.monitor = transient_client.monitor;
+            client.tags = transient_client.tags;
+        }
+    }
+
+    if (client.monitor == null) {
+        client.monitor = monitor_mod.selected_monitor;
+        apply_rules(display, client);
+    }
+
+    const monitor = client.monitor orelse return;
+
+    if (client.x + client.width > monitor.win_x + monitor.win_w) {
+        client.x = monitor.win_x + monitor.win_w - client.width - 2 * client.border_width;
+    }
+    if (client.y + client.height > monitor.win_y + monitor.win_h) {
+        client.y = monitor.win_y + monitor.win_h - client.height - 2 * client.border_width;
+    }
+    client.x = @max(client.x, monitor.win_x);
+    client.y = @max(client.y, monitor.win_y);
+
+    _ = xlib.XSetWindowBorderWidth(display.handle, win, @intCast(client.border_width));
+    _ = xlib.XSetWindowBorder(display.handle, win, border_color_unfocused);
+    tiling.send_configure(client);
+
+    update_window_type(display, client);
+    update_size_hints(display, client);
+    update_wm_hints(display, client);
+
+    _ = xlib.XSelectInput(
+        display.handle,
+        win,
+        xlib.EnterWindowMask | xlib.FocusChangeMask | xlib.PropertyChangeMask | xlib.StructureNotifyMask,
+    );
+    grabbuttons(display, client, false);
+
+    if (!client.is_floating) {
+        client.is_floating = trans != 0 or client.is_fixed;
+        client.old_state = client.is_floating;
+    }
+    if (client.is_floating) {
+        _ = xlib.XRaiseWindow(display.handle, client.window);
+    }
+
+    client_mod.attach_aside(client);
+    client_mod.attach_stack(client);
+
+    _ = xlib.XChangeProperty(display.handle, display.root, net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
+    _ = xlib.XMoveResizeWindow(display.handle, client.window, client.x + 2 * display.screen_width(), client.y, @intCast(client.width), @intCast(client.height));
+    set_client_state(display, client, NormalState);
+
+    if (client.monitor == monitor_mod.selected_monitor) {
+        const selmon = monitor_mod.selected_monitor orelse return;
+        unfocus_client(display, selmon.sel, false);
+    }
+    monitor.sel = client;
+
+    if (is_scrolling_layout(monitor)) {
+        monitor.scroll_offset = 0;
+    }
+
+    arrange(monitor);
+    _ = xlib.XMapWindow(display.handle, win);
+    focus(display, null);
+}
+
+fn handle_configure_request(display: *Display, event: *xlib.XConfigureRequestEvent) void {
+    const client = client_mod.window_to_client(event.window);
+
+    if (client) |managed_client| {
+        if ((event.value_mask & xlib.c.CWBorderWidth) != 0) {
+            managed_client.border_width = event.border_width;
+        } else if (managed_client.is_floating or (managed_client.monitor != null and managed_client.monitor.?.lt[managed_client.monitor.?.sel_lt] == null)) {
+            const monitor = managed_client.monitor orelse return;
+            if ((event.value_mask & xlib.c.CWX) != 0) {
+                managed_client.old_x = managed_client.x;
+                managed_client.x = monitor.mon_x + event.x;
+            }
+            if ((event.value_mask & xlib.c.CWY) != 0) {
+                managed_client.old_y = managed_client.y;
+                managed_client.y = monitor.mon_y + event.y;
+            }
+            if ((event.value_mask & xlib.c.CWWidth) != 0) {
+                managed_client.old_width = managed_client.width;
+                managed_client.width = event.width;
+            }
+            if ((event.value_mask & xlib.c.CWHeight) != 0) {
+                managed_client.old_height = managed_client.height;
+                managed_client.height = event.height;
+            }
+            const client_full_width = managed_client.width + managed_client.border_width * 2;
+            const client_full_height = managed_client.height + managed_client.border_width * 2;
+            if ((managed_client.x + managed_client.width) > monitor.mon_x + monitor.mon_w and managed_client.is_floating) {
+                managed_client.x = monitor.mon_x + @divTrunc(monitor.mon_w, 2) - @divTrunc(client_full_width, 2);
+            }
+            if ((managed_client.y + managed_client.height) > monitor.mon_y + monitor.mon_h and managed_client.is_floating) {
+                managed_client.y = monitor.mon_y + @divTrunc(monitor.mon_h, 2) - @divTrunc(client_full_height, 2);
+            }
+            if (((event.value_mask & (xlib.c.CWX | xlib.c.CWY)) != 0) and ((event.value_mask & (xlib.c.CWWidth | xlib.c.CWHeight)) == 0)) {
+                tiling.send_configure(managed_client);
+            }
+            if (client_mod.is_visible(managed_client)) {
+                _ = xlib.XMoveResizeWindow(display.handle, managed_client.window, managed_client.x, managed_client.y, @intCast(managed_client.width), @intCast(managed_client.height));
+            }
+        } else {
+            tiling.send_configure(managed_client);
+        }
+    } else {
+        var changes: xlib.XWindowChanges = undefined;
+        changes.x = event.x;
+        changes.y = event.y;
+        changes.width = event.width;
+        changes.height = event.height;
+        changes.border_width = event.border_width;
+        changes.sibling = event.above;
+        changes.stack_mode = event.detail;
+        _ = xlib.XConfigureWindow(display.handle, event.window, @intCast(event.value_mask), &changes);
+    }
+    _ = xlib.XSync(display.handle, xlib.False);
+}
+
+fn handle_key_press(display: *Display, event: *xlib.XKeyEvent) void {
+    const keysym = xlib.XKeycodeToKeysym(display.handle, @intCast(event.keycode), 0);
+    const clean_state = event.state & ~@as(c_uint, xlib.LockMask | xlib.Mod2Mask);
+
+    for (config.keybinds.items) |keybind| {
+        if (keysym == keybind.keysym and clean_state == keybind.mod_mask) {
+            execute_action(display, keybind.action, keybind.int_arg, keybind.str_arg);
+            return;
+        }
+    }
+}
+
+fn execute_action(display: *Display, action: config_mod.Action, int_arg: i32, str_arg: ?[]const u8) void {
+    switch (action) {
+        .spawn_terminal => spawn_terminal(),
+        .spawn => {
+            if (str_arg) |cmd| {
+                spawn_command(cmd);
+            }
+        },
+        .kill_client => kill_focused(display),
+        .quit => {
+            std.debug.print("quit keybind pressed\n", .{});
+            running = false;
+        },
+        .reload_config => reload_config(display),
+        .restart => {},
+        .focus_next => focusstack(display, 1),
+        .focus_prev => focusstack(display, -1),
+        .move_next => movestack(display, 1),
+        .move_prev => movestack(display, -1),
+        .resize_master => setmfact(@as(f32, @floatFromInt(int_arg)) / 1000.0),
+        .inc_master => incnmaster(1),
+        .dec_master => incnmaster(-1),
+        .toggle_floating => toggle_floating(display),
+        .toggle_fullscreen => toggle_fullscreen(display),
+        .toggle_gaps => toggle_gaps(),
+        .cycle_layout => cycle_layout(),
+        .set_layout => set_layout(str_arg),
+        .set_layout_tiling => {},
+        .set_layout_floating => {},
+        .view_tag => {
+            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
+            view(display, tag_mask);
+        },
+        .view_next_tag => view_adjacent_tag(display, 1),
+        .view_prev_tag => view_adjacent_tag(display, -1),
+        .view_next_nonempty_tag => view_adjacent_nonempty_tag(display, 1),
+        .view_prev_nonempty_tag => view_adjacent_nonempty_tag(display, -1),
+        .move_to_tag => {
+            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
+            tag_client(display, tag_mask);
+        },
+        .toggle_view_tag => {
+            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
+            toggle_view(display, tag_mask);
+        },
+        .toggle_tag => {
+            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
+            toggle_client_tag(display, tag_mask);
+        },
+        .focus_monitor => focusmon(display, int_arg),
+        .send_to_monitor => sendmon(display, int_arg),
+        .scroll_left => {
+            scroll_layout(-1);
+        },
+        .scroll_right => {
+            scroll_layout(1);
+        },
+    }
+}
+
+fn reload_config(display: *Display) void {
+    std.debug.print("reloading config...\n", .{});
+
+    ungrab_keybinds(display);
+
+    config.keybinds.clearRetainingCapacity();
+    config.rules.clearRetainingCapacity();
+    config.blocks.clearRetainingCapacity();
+
+    lua.deinit();
+    _ = lua.init(&config);
+
+    const loaded = if (config_path_global) |path|
+        lua.load_file(path)
+    else
+        lua.load_config();
+
+    if (loaded) {
+        if (config_path_global) |path| {
+            std.debug.print("reloaded config from {s}\n", .{path});
+        } else {
+            std.debug.print("reloaded config from ~/.config/oxwm/config.lua\n", .{});
+        }
+        apply_config_values();
+    } else {
+        std.debug.print("reload failed, restoring defaults\n", .{});
+        setup_default_keybinds();
+    }
+
+    bar_mod.destroy_bars(gpa.allocator(), display.handle);
+    setup_bars(gpa.allocator(), display);
+    rebuild_bar_blocks();
+
+    grab_keybinds(display);
+}
+
+fn rebuild_bar_blocks() void {
+    var current_bar = bar_mod.bars;
+    while (current_bar) |bar| {
+        bar.clear_blocks();
+        if (config.blocks.items.len > 0) {
+            for (config.blocks.items) |cfg_block| {
+                const block = config_block_to_bar_block(cfg_block);
+                bar.add_block(block);
+            }
+        } else {
+            bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
+            bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
+            bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
+        }
+        current_bar = bar.next;
+    }
+}
+
+fn ungrab_keybinds(display: *Display) void {
+    _ = xlib.XUngrabKey(display.handle, xlib.AnyKey, xlib.AnyModifier, display.root);
+}
+
+fn spawn_child_setup() void {
+    _ = std.c.setsid();
+    if (display_global) |display| {
+        const display_fd = xlib.XConnectionNumber(display.handle);
+        std.posix.close(@intCast(display_fd));
+    }
+    const sigchld_handler = std.posix.Sigaction{
+        .handler = .{ .handler = std.posix.SIG.DFL },
+        .mask = std.mem.zeroes(std.posix.sigset_t),
+        .flags = 0,
+    };
+    std.posix.sigaction(std.posix.SIG.CHLD, &sigchld_handler, null);
+}
+
+fn spawn_command(cmd: []const u8) void {
+    const pid = std.posix.fork() catch return;
+    if (pid == 0) {
+        spawn_child_setup();
+        var cmd_buf: [1024]u8 = undefined;
+        if (cmd.len >= cmd_buf.len) {
+            std.posix.exit(1);
+        }
+        @memcpy(cmd_buf[0..cmd.len], cmd);
+        cmd_buf[cmd.len] = 0;
+        const argv = [_:null]?[*:0]const u8{ "sh", "-c", @ptrCast(&cmd_buf) };
+        _ = std.posix.execvpeZ("sh", &argv, std.c.environ) catch {};
+        std.posix.exit(1);
+    }
+}
+
+fn spawn_terminal() void {
+    const pid = std.posix.fork() catch return;
+    if (pid == 0) {
+        spawn_child_setup();
+        var term_buf: [256]u8 = undefined;
+        const terminal = config.terminal;
+        if (terminal.len >= term_buf.len) {
+            std.posix.exit(1);
+        }
+        @memcpy(term_buf[0..terminal.len], terminal);
+        term_buf[terminal.len] = 0;
+        const term_ptr: [*:0]const u8 = @ptrCast(&term_buf);
+        const argv = [_:null]?[*:0]const u8{term_ptr};
+        _ = std.posix.execvpeZ(term_ptr, &argv, std.c.environ) catch {};
+        std.posix.exit(1);
+    }
+}
+
+fn movestack(display: *Display, direction: i32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const current = monitor.sel orelse return;
+
+    if (current.is_floating) {
+        return;
+    }
+
+    var target: ?*Client = null;
+
+    if (direction > 0) {
+        target = current.next;
+        while (target) |client| {
+            if (client_mod.is_visible(client) and !client.is_floating) {
+                break;
+            }
+            target = client.next;
+        }
+        if (target == null) {
+            target = monitor.clients;
+            while (target) |client| {
+                if (client == current) {
+                    break;
+                }
+                if (client_mod.is_visible(client) and !client.is_floating) {
+                    break;
+                }
+                target = client.next;
+            }
+        }
+    } else {
+        var prev: ?*Client = null;
+        var iter = monitor.clients;
+        while (iter) |client| {
+            if (client == current) {
+                break;
+            }
+            if (client_mod.is_visible(client) and !client.is_floating) {
+                prev = client;
+            }
+            iter = client.next;
+        }
+        if (prev == null) {
+            iter = current.next;
+            while (iter) |client| {
+                if (client_mod.is_visible(client) and !client.is_floating) {
+                    prev = client;
+                }
+                iter = client.next;
+            }
+        }
+        target = prev;
+    }
+
+    if (target) |swap_client| {
+        if (swap_client != current) {
+            client_mod.swap_clients(current, swap_client);
+            arrange(monitor);
+            focus(display, current);
+        }
+    }
+}
+
+fn toggle_view(display: *Display, tag_mask: u32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const new_tags = monitor.tagset[monitor.sel_tags] ^ tag_mask;
+    if (new_tags != 0) {
+        monitor.tagset[monitor.sel_tags] = new_tags;
+
+        if (new_tags == ~@as(u32, 0)) {
+            monitor.pertag.prevtag = monitor.pertag.curtag;
+            monitor.pertag.curtag = 0;
+        }
+
+        if ((new_tags & (@as(u32, 1) << @intCast(monitor.pertag.curtag -| 1))) == 0) {
+            monitor.pertag.prevtag = monitor.pertag.curtag;
+            var i: u32 = 0;
+            while (i < 9) : (i += 1) {
+                if ((new_tags & (@as(u32, 1) << @intCast(i))) != 0) break;
+            }
+            monitor.pertag.curtag = i + 1;
+        }
+
+        monitor.nmaster = monitor.pertag.nmasters[monitor.pertag.curtag];
+        monitor.mfact = monitor.pertag.mfacts[monitor.pertag.curtag];
+        monitor.sel_lt = monitor.pertag.sellts[monitor.pertag.curtag];
+
+        focus_top_client(display, monitor);
+        arrange(monitor);
+        bar_mod.invalidate_bars();
+    }
+}
+
+fn toggle_client_tag(display: *Display, tag_mask: u32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const client = monitor.sel orelse return;
+    const new_tags = client.tags ^ tag_mask;
+    if (new_tags != 0) {
+        client.tags = new_tags;
+        focus_top_client(display, monitor);
+        arrange(monitor);
+        bar_mod.invalidate_bars();
+    }
+}
+
+fn toggle_gaps() void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    if (monitor.gap_inner_h == 0) {
+        monitor.gap_inner_h = gap_inner_v;
+        monitor.gap_inner_v = gap_inner_v;
+        monitor.gap_outer_h = gap_outer_h;
+        monitor.gap_outer_v = gap_outer_v;
+    } else {
+        monitor.gap_inner_h = 0;
+        monitor.gap_inner_v = 0;
+        monitor.gap_outer_h = 0;
+        monitor.gap_outer_v = 0;
+    }
+    arrange(monitor);
+}
+
+fn kill_focused(display: *Display) void {
+    const selected = monitor_mod.selected_monitor orelse return;
+    const client = selected.sel orelse return;
+    std.debug.print("killing window: 0x{x}\n", .{client.window});
+
+    if (!send_event(display, client, wm_delete)) {
+        _ = xlib.XGrabServer(display.handle);
+        _ = xlib.XKillClient(display.handle, client.window);
+        _ = xlib.XSync(display.handle, xlib.False);
+        _ = xlib.XUngrabServer(display.handle);
+    }
+}
+
+fn toggle_fullscreen(display: *Display) void {
+    const selected = monitor_mod.selected_monitor orelse return;
+    const client = selected.sel orelse return;
+    set_fullscreen(display, client, !client.is_fullscreen);
+}
+
+fn set_fullscreen(display: *Display, client: *Client, fullscreen: bool) void {
+    const monitor = client.monitor orelse return;
+
+    if (fullscreen and !client.is_fullscreen) {
+        var fullscreen_atom = net_wm_state_fullscreen;
+        _ = xlib.XChangeProperty(
+            display.handle,
+            client.window,
+            net_wm_state,
+            xlib.XA_ATOM,
+            32,
+            xlib.PropModeReplace,
+            @ptrCast(&fullscreen_atom),
+            1,
+        );
+        client.is_fullscreen = true;
+        client.old_state = client.is_floating;
+        client.old_border_width = client.border_width;
+        client.border_width = 0;
+        client.is_floating = true;
+
+        _ = xlib.XSetWindowBorderWidth(display.handle, client.window, 0);
+        tiling.resize_client(client, monitor.mon_x, monitor.mon_y, monitor.mon_w, monitor.mon_h);
+        _ = xlib.XRaiseWindow(display.handle, client.window);
+
+        std.debug.print("fullscreen enabled: window=0x{x}\n", .{client.window});
+    } else if (!fullscreen and client.is_fullscreen) {
+        var no_atom: xlib.Atom = 0;
+        _ = xlib.XChangeProperty(
+            display.handle,
+            client.window,
+            net_wm_state,
+            xlib.XA_ATOM,
+            32,
+            xlib.PropModeReplace,
+            @ptrCast(&no_atom),
+            0,
+        );
+        client.is_fullscreen = false;
+        client.is_floating = client.old_state;
+        client.border_width = client.old_border_width;
+
+        client.x = client.old_x;
+        client.y = client.old_y;
+        client.width = client.old_width;
+        client.height = client.old_height;
+
+        tiling.resize_client(client, client.x, client.y, client.width, client.height);
+        arrange(monitor);
+
+        std.debug.print("fullscreen disabled: window=0x{x}\n", .{client.window});
+    }
+}
+
+fn view(display: *Display, tag_mask: u32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    if (tag_mask == monitor.tagset[monitor.sel_tags]) {
+        return;
+    }
+    monitor.sel_tags ^= 1;
+    if (tag_mask != 0) {
+        monitor.tagset[monitor.sel_tags] = tag_mask;
+        monitor.pertag.prevtag = monitor.pertag.curtag;
+
+        if (tag_mask == ~@as(u32, 0)) {
+            monitor.pertag.curtag = 0;
+        } else {
+            var i: u32 = 0;
+            while (i < 9) : (i += 1) {
+                if ((tag_mask & (@as(u32, 1) << @intCast(i))) != 0) break;
+            }
+            monitor.pertag.curtag = i + 1;
+        }
+    } else {
+        const tmp = monitor.pertag.prevtag;
+        monitor.pertag.prevtag = monitor.pertag.curtag;
+        monitor.pertag.curtag = tmp;
+    }
+
+    monitor.nmaster = monitor.pertag.nmasters[monitor.pertag.curtag];
+    monitor.mfact = monitor.pertag.mfacts[monitor.pertag.curtag];
+    monitor.sel_lt = monitor.pertag.sellts[monitor.pertag.curtag];
+
+    focus_top_client(display, monitor);
+    arrange(monitor);
+    bar_mod.invalidate_bars();
+    std.debug.print("view: tag_mask={d}\n", .{monitor.tagset[monitor.sel_tags]});
+}
+
+fn view_adjacent_tag(display: *Display, direction: i32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const current_tag = monitor.pertag.curtag;
+    var new_tag: i32 = @intCast(current_tag);
+
+    new_tag += direction;
+    if (new_tag < 1) new_tag = 9;
+    if (new_tag > 9) new_tag = 1;
+
+    const tag_mask: u32 = @as(u32, 1) << @intCast(new_tag - 1);
+    view(display, tag_mask);
+}
+
+fn view_adjacent_nonempty_tag(display: *Display, direction: i32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const current_tag = monitor.pertag.curtag;
+    var new_tag: i32 = @intCast(current_tag);
+
+    var attempts: i32 = 0;
+    while (attempts < 9) : (attempts += 1) {
+        new_tag += direction;
+        if (new_tag < 1) new_tag = 9;
+        if (new_tag > 9) new_tag = 1;
+
+        const tag_mask: u32 = @as(u32, 1) << @intCast(new_tag - 1);
+        if (has_clients_on_tag(monitor, tag_mask)) {
+            view(display, tag_mask);
+            return;
+        }
+    }
+}
+
+fn has_clients_on_tag(monitor: *monitor_mod.Monitor, tag_mask: u32) bool {
+    var client = monitor.clients;
+    while (client) |c| {
+        if ((c.tags & tag_mask) != 0) {
+            return true;
+        }
+        client = c.next;
+    }
+    return false;
+}
+
+fn tag_client(display: *Display, tag_mask: u32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const client = monitor.sel orelse return;
+    if (tag_mask == 0) {
+        return;
+    }
+    client.tags = tag_mask;
+    focus_top_client(display, monitor);
+    arrange(monitor);
+    bar_mod.invalidate_bars();
+    std.debug.print("tag_client: window=0x{x} tag_mask={d}\n", .{ client.window, tag_mask });
+}
+
+fn focus_top_client(display: *Display, monitor: *Monitor) void {
+    var visible_client = monitor.stack;
+    while (visible_client) |client| {
+        if (client_mod.is_visible(client)) {
+            focus(display, client);
+            return;
+        }
+        visible_client = client.stack_next;
+    }
+    monitor.sel = null;
+    _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
+}
+
+fn focusstack(display: *Display, direction: i32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const current = monitor.sel orelse return;
+
+    var next_client: ?*Client = null;
+
+    if (direction > 0) {
+        next_client = current.next;
+        while (next_client) |client| {
+            if (client_mod.is_visible(client)) {
+                break;
+            }
+            next_client = client.next;
+        }
+        if (next_client == null) {
+            next_client = monitor.clients;
+            while (next_client) |client| {
+                if (client_mod.is_visible(client)) {
+                    break;
+                }
+                next_client = client.next;
+            }
+        }
+    } else {
+        var prev: ?*Client = null;
+        var iter = monitor.clients;
+        while (iter) |client| {
+            if (client == current) {
+                break;
+            }
+            if (client_mod.is_visible(client)) {
+                prev = client;
+            }
+            iter = client.next;
+        }
+        if (prev == null) {
+            iter = current.next;
+            while (iter) |client| {
+                if (client_mod.is_visible(client)) {
+                    prev = client;
+                }
+                iter = client.next;
+            }
+        }
+        next_client = prev;
+    }
+
+    if (next_client) |client| {
+        focus(display, client);
+        if (client.monitor) |client_monitor| {
+            restack(display, client_monitor);
+        }
+    }
+}
+
+fn toggle_floating(_: *Display) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const client = monitor.sel orelse return;
+
+    if (client.is_fullscreen) {
+        return;
+    }
+
+    client.is_floating = !client.is_floating;
+
+    if (client.is_floating) {
+        tiling.resize(client, client.x, client.y, client.width, client.height, false);
+    }
+
+    arrange(monitor);
+    std.debug.print("toggle_floating: window=0x{x} floating={}\n", .{ client.window, client.is_floating });
+}
+
+fn incnmaster(delta: i32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const new_val = @max(0, monitor.nmaster + delta);
+    monitor.nmaster = new_val;
+    monitor.pertag.nmasters[monitor.pertag.curtag] = new_val;
+    arrange(monitor);
+    std.debug.print("incnmaster: nmaster={d}\n", .{monitor.nmaster});
+}
+
+fn setmfact(delta: f32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const new_mfact = monitor.mfact + delta;
+    if (new_mfact < 0.05 or new_mfact > 0.95) {
+        return;
+    }
+    monitor.mfact = new_mfact;
+    monitor.pertag.mfacts[monitor.pertag.curtag] = new_mfact;
+    arrange(monitor);
+    std.debug.print("setmfact: mfact={d:.2}\n", .{monitor.mfact});
+}
+
+fn cycle_layout() void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const new_lt = (monitor.sel_lt + 1) % 4;
+    monitor.sel_lt = new_lt;
+    monitor.pertag.sellts[monitor.pertag.curtag] = new_lt;
+    if (new_lt != 3) {
+        monitor.scroll_offset = 0;
+    }
+    arrange(monitor);
+    bar_mod.invalidate_bars();
+    if (monitor.lt[monitor.sel_lt]) |layout| {
+        std.debug.print("cycle_layout: {s}\n", .{layout.symbol});
+    }
+}
+
+fn set_layout(layout_name: ?[]const u8) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const name = layout_name orelse return;
+
+    const new_lt: u32 = if (std.mem.eql(u8, name, "tiling") or std.mem.eql(u8, name, "[]="))
+        0
+    else if (std.mem.eql(u8, name, "monocle") or std.mem.eql(u8, name, "[M]"))
+        1
+    else if (std.mem.eql(u8, name, "floating") or std.mem.eql(u8, name, "><>"))
+        2
+    else if (std.mem.eql(u8, name, "scrolling") or std.mem.eql(u8, name, "[S]"))
+        3
+    else {
+        std.debug.print("set_layout: unknown layout '{s}'\n", .{name});
+        return;
+    };
+
+    monitor.sel_lt = new_lt;
+    monitor.pertag.sellts[monitor.pertag.curtag] = new_lt;
+    if (new_lt != 3) {
+        monitor.scroll_offset = 0;
+    }
+    arrange(monitor);
+    bar_mod.invalidate_bars();
+    if (monitor.lt[monitor.sel_lt]) |layout| {
+        std.debug.print("set_layout: {s}\n", .{layout.symbol});
+    }
+}
+
+fn focusmon(display: *Display, direction: i32) void {
+    const selmon = monitor_mod.selected_monitor orelse return;
+    const target = monitor_mod.dir_to_monitor(direction) orelse return;
+    if (target == selmon) {
+        return;
+    }
+    unfocus_client(display, selmon.sel, false);
+    monitor_mod.selected_monitor = target;
+    focus(display, null);
+    std.debug.print("focusmon: monitor {d}\n", .{target.num});
+}
+
+fn sendmon(display: *Display, direction: i32) void {
+    const source_monitor = monitor_mod.selected_monitor orelse return;
+    const client = source_monitor.sel orelse return;
+    const target = monitor_mod.dir_to_monitor(direction) orelse return;
+
+    if (target == source_monitor) {
+        return;
+    }
+
+    client_mod.detach(client);
+    client_mod.detach_stack(client);
+    client.monitor = target;
+    client.tags = target.tagset[target.sel_tags];
+    client_mod.attach_aside(client);
+    client_mod.attach_stack(client);
+
+    focus_top_client(display, source_monitor);
+    arrange(source_monitor);
+    arrange(target);
+
+    std.debug.print("sendmon: window=0x{x} to monitor {d}\n", .{ client.window, target.num });
+}
+
+fn snap_x(client: *Client, new_x: i32, monitor: *Monitor) i32 {
+    const client_width = client.width + 2 * client.border_width;
+    if (@abs(monitor.win_x - new_x) < snap_distance) {
+        return monitor.win_x;
+    } else if (@abs((monitor.win_x + monitor.win_w) - (new_x + client_width)) < snap_distance) {
+        return monitor.win_x + monitor.win_w - client_width;
+    }
+    return new_x;
+}
+
+fn snap_y(client: *Client, new_y: i32, monitor: *Monitor) i32 {
+    const client_height = client.height + 2 * client.border_width;
+    if (@abs(monitor.win_y - new_y) < snap_distance) {
+        return monitor.win_y;
+    } else if (@abs((monitor.win_y + monitor.win_h) - (new_y + client_height)) < snap_distance) {
+        return monitor.win_y + monitor.win_h - client_height;
+    }
+    return new_y;
+}
+
+fn movemouse(display: *Display) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const client = monitor.sel orelse return;
+
+    if (client.is_fullscreen) {
+        return;
+    }
+
+    restack(display, monitor);
+
+    const was_floating = client.is_floating;
+    if (!client.is_floating) {
+        client.is_floating = true;
+    }
+
+    var root_x: c_int = undefined;
+    var root_y: c_int = undefined;
+    var dummy_win: xlib.Window = undefined;
+    var dummy_int: c_int = undefined;
+    var dummy_uint: c_uint = undefined;
+
+    _ = xlib.XQueryPointer(display.handle, display.root, &dummy_win, &dummy_win, &root_x, &root_y, &dummy_int, &dummy_int, &dummy_uint);
+
+    const grab_result = xlib.XGrabPointer(
+        display.handle,
+        display.root,
+        xlib.False,
+        xlib.ButtonPressMask | xlib.ButtonReleaseMask | xlib.PointerMotionMask,
+        xlib.GrabModeAsync,
+        xlib.GrabModeAsync,
+        xlib.None,
+        cursor_move,
+        xlib.CurrentTime,
+    );
+
+    if (grab_result != xlib.GrabSuccess) {
+        return;
+    }
+
+    const start_x = client.x;
+    const start_y = client.y;
+    const pointer_start_x = root_x;
+    const pointer_start_y = root_y;
+    var last_time: c_ulong = 0;
+
+    var event: xlib.XEvent = undefined;
+    var done = false;
+
+    while (!done) {
+        _ = xlib.XNextEvent(display.handle, &event);
+
+        switch (event.type) {
+            xlib.MotionNotify => {
+                const motion = &event.xmotion;
+                if ((motion.time - last_time) < (1000 / 60)) {
+                    continue;
+                }
+                last_time = motion.time;
+                const delta_x = motion.x_root - pointer_start_x;
+                const delta_y = motion.y_root - pointer_start_y;
+                var new_x = start_x + delta_x;
+                var new_y = start_y + delta_y;
+                if (client.monitor) |client_monitor| {
+                    new_x = snap_x(client, new_x, client_monitor);
+                    new_y = snap_y(client, new_y, client_monitor);
+                }
+                tiling.resize(client, new_x, new_y, client.width, client.height, true);
+            },
+            xlib.ButtonRelease => {
+                done = true;
+            },
+            else => {},
+        }
+    }
+
+    _ = xlib.XUngrabPointer(display.handle, xlib.CurrentTime);
+
+    const new_mon = monitor_mod.rect_to_monitor(client.x, client.y, client.width, client.height);
+    if (new_mon != null and new_mon != monitor) {
+        client_mod.detach(client);
+        client_mod.detach_stack(client);
+        client.monitor = new_mon;
+        client.tags = new_mon.?.tagset[new_mon.?.sel_tags];
+        client_mod.attach_aside(client);
+        client_mod.attach_stack(client);
+        monitor_mod.selected_monitor = new_mon;
+        focus(display, client);
+        arrange(monitor);
+        arrange(new_mon.?);
+    } else {
+        arrange(monitor);
+    }
+
+    if (config.auto_tile and !was_floating) {
+        const drop_monitor = client.monitor orelse return;
+        const center_x = client.x + @divTrunc(client.width, 2);
+        const center_y = client.y + @divTrunc(client.height, 2);
+
+        if (client_mod.tiled_window_at(client, drop_monitor, center_x, center_y)) |target| {
+            client_mod.insert_before(client, target);
+        }
+
+        client.is_floating = false;
+        arrange(drop_monitor);
+    }
+}
+
+fn resizemouse(display: *Display) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    const client = monitor.sel orelse return;
+
+    if (client.is_fullscreen) {
+        return;
+    }
+
+    restack(display, monitor);
+
+    if (!client.is_floating) {
+        client.is_floating = true;
+    }
+
+    const grab_result = xlib.XGrabPointer(
+        display.handle,
+        display.root,
+        xlib.False,
+        xlib.ButtonPressMask | xlib.ButtonReleaseMask | xlib.PointerMotionMask,
+        xlib.GrabModeAsync,
+        xlib.GrabModeAsync,
+        xlib.None,
+        cursor_resize,
+        xlib.CurrentTime,
+    );
+
+    if (grab_result != xlib.GrabSuccess) {
+        return;
+    }
+
+    _ = xlib.XWarpPointer(display.handle, xlib.None, client.window, 0, 0, 0, 0, client.width + client.border_width - 1, client.height + client.border_width - 1);
+
+    var event: xlib.XEvent = undefined;
+    var done = false;
+    var last_time: c_ulong = 0;
+
+    while (!done) {
+        _ = xlib.XNextEvent(display.handle, &event);
+
+        switch (event.type) {
+            xlib.MotionNotify => {
+                const motion = &event.xmotion;
+                if ((motion.time - last_time) < (1000 / 60)) {
+                    continue;
+                }
+                last_time = motion.time;
+                var new_width = @max(1, motion.x_root - client.x - 2 * client.border_width + 1);
+                var new_height = @max(1, motion.y_root - client.y - 2 * client.border_width + 1);
+                if (client.monitor) |client_monitor| {
+                    const client_right = client.x + new_width + 2 * client.border_width;
+                    const client_bottom = client.y + new_height + 2 * client.border_width;
+                    const mon_right = client_monitor.win_x + client_monitor.win_w;
+                    const mon_bottom = client_monitor.win_y + client_monitor.win_h;
+                    if (@abs(mon_right - client_right) < snap_distance) {
+                        new_width = @max(1, mon_right - client.x - 2 * client.border_width);
+                    }
+                    if (@abs(mon_bottom - client_bottom) < snap_distance) {
+                        new_height = @max(1, mon_bottom - client.y - 2 * client.border_width);
+                    }
+                }
+                tiling.resize(client, client.x, client.y, new_width, new_height, true);
+            },
+            xlib.ButtonRelease => {
+                done = true;
+            },
+            else => {},
+        }
+    }
+
+    _ = xlib.XUngrabPointer(display.handle, xlib.CurrentTime);
+    arrange(monitor);
+}
+
+fn handle_expose(display: *Display, event: *xlib.XExposeEvent) void {
+    if (event.count != 0) return;
+
+    if (bar_mod.window_to_bar(event.window)) |bar| {
+        bar.invalidate();
+        bar.draw(display.handle, &tags);
+    }
+}
+
+fn clean_mask(mask: c_uint) c_uint {
+    const lock: c_uint = @intCast(xlib.LockMask);
+    const shift: c_uint = @intCast(xlib.ShiftMask);
+    const ctrl: c_uint = @intCast(xlib.ControlMask);
+    const mod1: c_uint = @intCast(xlib.Mod1Mask);
+    const mod2: c_uint = @intCast(xlib.Mod2Mask);
+    const mod3: c_uint = @intCast(xlib.Mod3Mask);
+    const mod4: c_uint = @intCast(xlib.Mod4Mask);
+    const mod5: c_uint = @intCast(xlib.Mod5Mask);
+    return mask & ~(lock | numlock_mask) & (shift | ctrl | mod1 | mod2 | mod3 | mod4 | mod5);
+}
+
+fn handle_button_press(display: *Display, event: *xlib.XButtonEvent) void {
+    std.debug.print("button_press: window=0x{x} subwindow=0x{x}\n", .{ event.window, event.subwindow });
+
+    const clicked_monitor = monitor_mod.window_to_monitor(event.window);
+    if (clicked_monitor) |monitor| {
+        if (monitor != monitor_mod.selected_monitor) {
+            if (monitor_mod.selected_monitor) |selmon| {
+                unfocus_client(display, selmon.sel, true);
+            }
+            monitor_mod.selected_monitor = monitor;
+            focus(display, null);
+        }
+    }
+
+    if (bar_mod.window_to_bar(event.window)) |bar| {
+        const clicked_tag = bar.handle_click(event.x, &tags);
+        if (clicked_tag) |tag_index| {
+            const tag_mask: u32 = @as(u32, 1) << @intCast(tag_index);
+            view(display, tag_mask);
+        }
+        return;
+    }
+
+    const click_client = client_mod.window_to_client(event.window);
+    if (click_client) |found_client| {
+        focus(display, found_client);
+        if (monitor_mod.selected_monitor) |selmon| {
+            restack(display, selmon);
+        }
+        _ = xlib.XAllowEvents(display.handle, xlib.ReplayPointer, xlib.CurrentTime);
+    }
+
+    const clean_state = clean_mask(event.state);
+    for (config.buttons.items) |button| {
+        if (button.click != .client_win) continue;
+        const button_clean_mask = clean_mask(button.mod_mask);
+        if (clean_state == button_clean_mask and event.button == button.button) {
+            switch (button.action) {
+                .move_mouse => movemouse(display),
+                .resize_mouse => resizemouse(display),
+                .toggle_floating => {
+                    if (click_client) |found_client| {
+                        found_client.is_floating = !found_client.is_floating;
+                        if (monitor_mod.selected_monitor) |monitor| {
+                            arrange(monitor);
+                        }
+                    }
+                },
+            }
+            return;
+        }
+    }
+}
+
+fn handle_client_message(display: *Display, event: *xlib.XClientMessageEvent) void {
+    const client = client_mod.window_to_client(event.window) orelse return;
+
+    if (event.message_type == net_wm_state) {
+        const action = event.data.l[0];
+        const first_property = @as(xlib.Atom, @intCast(event.data.l[1]));
+        const second_property = @as(xlib.Atom, @intCast(event.data.l[2]));
+
+        if (first_property == net_wm_state_fullscreen or second_property == net_wm_state_fullscreen) {
+            const net_wm_state_remove = 0;
+            const net_wm_state_add = 1;
+            const net_wm_state_toggle = 2;
+
+            if (action == net_wm_state_add) {
+                set_fullscreen(display, client, true);
+            } else if (action == net_wm_state_remove) {
+                set_fullscreen(display, client, false);
+            } else if (action == net_wm_state_toggle) {
+                set_fullscreen(display, client, !client.is_fullscreen);
+            }
+        }
+    } else if (event.message_type == net_active_window) {
+        const selected = monitor_mod.selected_monitor orelse return;
+        if (client != selected.sel and !client.is_urgent) {
+            set_urgent(display, client, true);
+        }
+    }
+}
+
+fn handle_destroy_notify(display: *Display, event: *xlib.XDestroyWindowEvent) void {
+    const client = client_mod.window_to_client(event.window) orelse return;
+    std.debug.print("destroy_notify: window=0x{x}\n", .{event.window});
+    unmanage(display, client);
+}
+
+fn handle_unmap_notify(display: *Display, event: *xlib.XUnmapEvent) void {
+    const client = client_mod.window_to_client(event.window) orelse return;
+    std.debug.print("unmap_notify: window=0x{x}\n", .{event.window});
+    unmanage(display, client);
+}
+
+fn unmanage(display: *Display, client: *Client) void {
+    const client_monitor = client.monitor;
+
+    var next_focus: ?*Client = null;
+    if (client_monitor) |monitor| {
+        if (monitor.sel == client and is_scrolling_layout(monitor)) {
+            next_focus = client.next;
+            if (next_focus == null) {
+                var prev: ?*Client = null;
+                var iter = monitor.clients;
+                while (iter) |c| {
+                    if (c == client) break;
+                    prev = c;
+                    iter = c.next;
+                }
+                next_focus = prev;
+            }
+        }
+    }
+
+    client_mod.detach(client);
+    client_mod.detach_stack(client);
+
+    if (client_monitor) |monitor| {
+        if (monitor.sel == client) {
+            monitor.sel = if (next_focus) |nf| nf else monitor.stack;
+        }
+        if (is_scrolling_layout(monitor)) {
+            const target = if (monitor.sel) |sel| scrolling.get_target_scroll_for_window(monitor, sel) else 0;
+            if (target == 0) {
+                monitor.scroll_offset = scrolling.get_scroll_step(monitor);
+            } else {
+                monitor.scroll_offset = 0;
+            }
+        }
+        arrange(monitor);
+    }
+
+    if (client_monitor) |monitor| {
+        if (monitor.sel) |selected| {
+            focus(display, selected);
+        }
+    }
+
+    client_mod.destroy(client);
+    update_client_list(display);
+    bar_mod.invalidate_bars();
+}
+
+fn handle_enter_notify(display: *Display, event: *xlib.XCrossingEvent) void {
+    if ((event.mode != xlib.NotifyNormal or event.detail == xlib.NotifyInferior) and event.window != display.root) {
+        return;
+    }
+
+    const client = client_mod.window_to_client(event.window);
+    const target_mon = if (client) |c| c.monitor else monitor_mod.window_to_monitor(event.window);
+    const selmon = monitor_mod.selected_monitor;
+
+    if (target_mon != selmon) {
+        if (selmon) |sel| {
+            unfocus_client(display, sel.sel, true);
+        }
+        monitor_mod.selected_monitor = target_mon;
+    } else if (client == null) {
+        return;
+    } else if (selmon) |sel| {
+        if (client.? == sel.sel) {
+            return;
+        }
+    }
+
+    focus(display, client);
+}
+
+fn handle_focus_in(display: *Display, event: *xlib.XFocusChangeEvent) void {
+    const selmon = monitor_mod.selected_monitor orelse return;
+    const selected = selmon.sel orelse return;
+    if (event.window != selected.window) {
+        set_focus(display, selected);
+    }
+}
+
+fn set_focus(display: *Display, client: *Client) void {
+    if (!client.never_focus) {
+        _ = xlib.XSetInputFocus(display.handle, client.window, xlib.RevertToPointerRoot, xlib.CurrentTime);
+        _ = xlib.XChangeProperty(display.handle, display.root, net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
+    }
+    _ = send_event(display, client, wm_take_focus);
+}
+
+var last_motion_monitor: ?*Monitor = null;
+
+fn handle_motion_notify(display: *Display, event: *xlib.XMotionEvent) void {
+    if (event.window != display.root) {
+        return;
+    }
+
+    const target_mon = monitor_mod.rect_to_monitor(event.x_root, event.y_root, 1, 1);
+    if (target_mon != last_motion_monitor and last_motion_monitor != null) {
+        if (monitor_mod.selected_monitor) |selmon| {
+            unfocus_client(display, selmon.sel, true);
+        }
+        monitor_mod.selected_monitor = target_mon;
+        focus(display, null);
+    }
+    last_motion_monitor = target_mon;
+}
+
+fn handle_property_notify(display: *Display, event: *xlib.XPropertyEvent) void {
+    if (event.state == xlib.PropertyDelete) {
+        return;
+    }
+
+    const client = client_mod.window_to_client(event.window) orelse return;
+
+    if (event.atom == xlib.XA_WM_TRANSIENT_FOR) {
+        var trans: xlib.Window = 0;
+        if (!client.is_floating and xlib.XGetTransientForHint(display.handle, client.window, &trans) != 0) {
+            client.is_floating = client_mod.window_to_client(trans) != null;
+            if (client.is_floating) {
+                if (client.monitor) |monitor| {
+                    arrange(monitor);
+                }
+            }
+        }
+    } else if (event.atom == xlib.XA_WM_NORMAL_HINTS) {
+        client.hints_valid = false;
+    } else if (event.atom == xlib.XA_WM_HINTS) {
+        update_wm_hints(display, client);
+        bar_mod.invalidate_bars();
+    } else if (event.atom == xlib.XA_WM_NAME or event.atom == net_wm_name) {
+        update_title(display, client);
+    } else if (event.atom == net_wm_window_type) {
+        update_window_type(display, client);
+    }
+}
+
+fn unfocus_client(display: *Display, client: ?*Client, reset_input_focus: bool) void {
+    const unfocus_target = client orelse return;
+    grabbuttons(display, unfocus_target, false);
+    _ = xlib.XSetWindowBorder(display.handle, unfocus_target.window, border_color_unfocused);
+    if (reset_input_focus) {
+        _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
+        _ = xlib.XDeleteProperty(display.handle, display.root, net_active_window);
+    }
+}
+
+fn set_client_state(display: *Display, client: *Client, state: c_long) void {
+    var data: [2]c_long = .{ state, xlib.None };
+    _ = xlib.c.XChangeProperty(display.handle, client.window, wm_state, wm_state, 32, xlib.PropModeReplace, @ptrCast(&data), 2);
+}
+
+fn update_numlock_mask(display: *Display) void {
+    numlock_mask = 0;
+    const modmap = xlib.XGetModifierMapping(display.handle);
+    if (modmap == null) return;
+    defer _ = xlib.XFreeModifiermap(modmap);
+
+    const numlock_keycode = xlib.XKeysymToKeycode(display.handle, xlib.XK_Num_Lock);
+
+    var modifier_index: usize = 0;
+    while (modifier_index < 8) : (modifier_index += 1) {
+        var key_index: usize = 0;
+        while (key_index < @as(usize, @intCast(modmap.*.max_keypermod))) : (key_index += 1) {
+            const keycode = modmap.*.modifiermap[modifier_index * @as(usize, @intCast(modmap.*.max_keypermod)) + key_index];
+            if (keycode == numlock_keycode) {
+                numlock_mask = @as(c_uint, 1) << @intCast(modifier_index);
+            }
+        }
+    }
+}
+
+fn grabbuttons(display: *Display, client: *Client, focused: bool) void {
+    update_numlock_mask(display);
+    const modifiers = [_]c_uint{ 0, xlib.LockMask, numlock_mask, numlock_mask | xlib.LockMask };
+
+    _ = xlib.XUngrabButton(display.handle, xlib.AnyButton, xlib.AnyModifier, client.window);
+    if (!focused) {
+        _ = xlib.XGrabButton(
+            display.handle,
+            xlib.AnyButton,
+            xlib.AnyModifier,
+            client.window,
+            xlib.False,
+            xlib.ButtonPressMask | xlib.ButtonReleaseMask,
+            xlib.GrabModeSync,
+            xlib.GrabModeSync,
+            xlib.None,
+            xlib.None,
+        );
+    }
+    for (config.buttons.items) |button| {
+        if (button.click == .client_win) {
+            for (modifiers) |modifier| {
+                _ = xlib.XGrabButton(
+                    display.handle,
+                    @intCast(button.button),
+                    button.mod_mask | modifier,
+                    client.window,
+                    xlib.False,
+                    xlib.ButtonPressMask | xlib.ButtonReleaseMask,
+                    xlib.GrabModeAsync,
+                    xlib.GrabModeSync,
+                    xlib.None,
+                    xlib.None,
+                );
+            }
+        }
+    }
+}
+
+fn focus(display: *Display, target_client: ?*Client) void {
+    const selmon = monitor_mod.selected_monitor orelse return;
+
+    var focus_client = target_client;
+    if (focus_client == null or !client_mod.is_visible(focus_client.?)) {
+        focus_client = selmon.stack;
+        while (focus_client) |iter| {
+            if (client_mod.is_visible(iter)) break;
+            focus_client = iter.stack_next;
+        }
+    }
+
+    if (selmon.sel != null and selmon.sel != focus_client) {
+        unfocus_client(display, selmon.sel, false);
+    }
+
+    if (focus_client) |client| {
+        if (client.monitor != selmon) {
+            monitor_mod.selected_monitor = client.monitor;
+        }
+        if (client.is_urgent) {
+            set_urgent(display, client, false);
+        }
+        client_mod.detach_stack(client);
+        client_mod.attach_stack(client);
+        grabbuttons(display, client, true);
+        _ = xlib.XSetWindowBorder(display.handle, client.window, border_color_focused);
+        if (!client.never_focus) {
+            _ = xlib.XSetInputFocus(display.handle, client.window, xlib.RevertToPointerRoot, xlib.CurrentTime);
+            _ = xlib.XChangeProperty(display.handle, display.root, net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
+        }
+        _ = send_event(display, client, wm_take_focus);
+    } else {
+        _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
+        _ = xlib.XDeleteProperty(display.handle, display.root, net_active_window);
+    }
+
+    const current_selmon = monitor_mod.selected_monitor orelse return;
+    current_selmon.sel = focus_client;
+
+    if (focus_client) |client| {
+        if (is_scrolling_layout(current_selmon)) {
+            scroll_to_window(client, true);
+        }
+    }
+
+    bar_mod.invalidate_bars();
+}
+
+fn restack(display: *Display, monitor: *Monitor) void {
+    bar_mod.invalidate_bars();
+    const selected_client = monitor.sel orelse return;
+
+    if (selected_client.is_floating or monitor.lt[monitor.sel_lt] == null) {
+        _ = xlib.XRaiseWindow(display.handle, selected_client.window);
+    }
+
+    if (monitor.lt[monitor.sel_lt] != null) {
+        var window_changes: xlib.c.XWindowChanges = undefined;
+        window_changes.stack_mode = xlib.c.Below;
+        window_changes.sibling = monitor.bar_win;
+
+        var current = monitor.stack;
+        while (current) |client| {
+            if (!client.is_floating and client_mod.is_visible(client)) {
+                _ = xlib.c.XConfigureWindow(display.handle, client.window, xlib.c.CWSibling | xlib.c.CWStackMode, &window_changes);
+                window_changes.sibling = client.window;
+            }
+            current = client.stack_next;
+        }
+    }
+
+    _ = xlib.XSync(display.handle, xlib.False);
+
+    var discard_event: xlib.XEvent = undefined;
+    while (xlib.c.XCheckMaskEvent(display.handle, xlib.EnterWindowMask, &discard_event) != 0) {}
+}
+
+fn arrange(monitor: *Monitor) void {
+    if (display_global) |display| {
+        showhide(display, monitor);
+    }
+    if (monitor.lt[monitor.sel_lt]) |layout| {
+        if (layout.arrange_fn) |arrange_fn| {
+            arrange_fn(monitor);
+        }
+    }
+    if (display_global) |display| {
+        restack(display, monitor);
+    }
+}
+
+fn tick_animations() void {
+    if (!scroll_animation.is_active()) return;
+
+    const monitor = monitor_mod.selected_monitor orelse return;
+    if (scroll_animation.update()) |new_offset| {
+        monitor.scroll_offset = new_offset;
+        arrange(monitor);
+    }
+}
+
+fn is_scrolling_layout(monitor: *Monitor) bool {
+    if (monitor.lt[monitor.sel_lt]) |layout| {
+        return layout.arrange_fn == scrolling.layout.arrange_fn;
+    }
+    return false;
+}
+
+fn scroll_layout(direction: i32) void {
+    const monitor = monitor_mod.selected_monitor orelse return;
+    if (!is_scrolling_layout(monitor)) return;
+
+    const scroll_step = scrolling.get_scroll_step(monitor);
+    const max_scroll = scrolling.get_max_scroll(monitor);
+
+    const current = if (scroll_animation.is_active())
+        scroll_animation.target()
+    else
+        monitor.scroll_offset;
+
+    var target = current + direction * scroll_step;
+    target = @max(0, @min(target, max_scroll));
+
+    scroll_animation.start(monitor.scroll_offset, target, animation_config);
+}
+
+fn scroll_to_window(client: *Client, animate: bool) void {
+    const monitor = client.monitor orelse return;
+    if (!is_scrolling_layout(monitor)) return;
+
+    const target = scrolling.get_target_scroll_for_window(monitor, client);
+
+    if (animate) {
+        scroll_animation.start(monitor.scroll_offset, target, animation_config);
+    } else {
+        monitor.scroll_offset = target;
+        arrange(monitor);
+    }
+}
+
+fn showhide_client(display: *Display, client: ?*Client) void {
+    const target = client orelse return;
+    if (client_mod.is_visible(target)) {
+        _ = xlib.XMoveWindow(display.handle, target.window, target.x, target.y);
+        const monitor = target.monitor orelse return;
+        if ((monitor.lt[monitor.sel_lt] == null or target.is_floating) and !target.is_fullscreen) {
+            tiling.resize(target, target.x, target.y, target.width, target.height, false);
+        }
+        showhide_client(display, target.stack_next);
+    } else {
+        showhide_client(display, target.stack_next);
+        const client_width = target.width + 2 * target.border_width;
+        _ = xlib.XMoveWindow(display.handle, target.window, -2 * client_width, target.y);
+    }
+}
+
+fn showhide(display: *Display, monitor: *Monitor) void {
+    showhide_client(display, monitor.stack);
+}
+
+fn update_size_hints(display: *Display, client: *Client) void {
+    var size_hints: xlib.XSizeHints = undefined;
+    var msize: c_long = 0;
+
+    if (xlib.XGetWMNormalHints(display.handle, client.window, &size_hints, &msize) == 0) {
+        size_hints.flags = xlib.PSize;
+    }
+
+    if ((size_hints.flags & xlib.PBaseSize) != 0) {
+        client.base_width = size_hints.base_width;
+        client.base_height = size_hints.base_height;
+    } else if ((size_hints.flags & xlib.PMinSize) != 0) {
+        client.base_width = size_hints.min_width;
+        client.base_height = size_hints.min_height;
+    } else {
+        client.base_width = 0;
+        client.base_height = 0;
+    }
+
+    if ((size_hints.flags & xlib.PResizeInc) != 0) {
+        client.increment_width = size_hints.width_inc;
+        client.increment_height = size_hints.height_inc;
+    } else {
+        client.increment_width = 0;
+        client.increment_height = 0;
+    }
+
+    if ((size_hints.flags & xlib.PMaxSize) != 0) {
+        client.max_width = size_hints.max_width;
+        client.max_height = size_hints.max_height;
+    } else {
+        client.max_width = 0;
+        client.max_height = 0;
+    }
+
+    if ((size_hints.flags & xlib.PMinSize) != 0) {
+        client.min_width = size_hints.min_width;
+        client.min_height = size_hints.min_height;
+    } else if ((size_hints.flags & xlib.PBaseSize) != 0) {
+        client.min_width = size_hints.base_width;
+        client.min_height = size_hints.base_height;
+    } else {
+        client.min_width = 0;
+        client.min_height = 0;
+    }
+
+    if ((size_hints.flags & xlib.PAspect) != 0) {
+        client.min_aspect = @as(f32, @floatFromInt(size_hints.min_aspect.y)) / @as(f32, @floatFromInt(size_hints.min_aspect.x));
+        client.max_aspect = @as(f32, @floatFromInt(size_hints.max_aspect.x)) / @as(f32, @floatFromInt(size_hints.max_aspect.y));
+    } else {
+        client.min_aspect = 0.0;
+        client.max_aspect = 0.0;
+    }
+
+    client.is_fixed = (client.max_width != 0 and client.max_height != 0 and client.max_width == client.min_width and client.max_height == client.min_height);
+    client.hints_valid = true;
+}
+
+fn update_wm_hints(display: *Display, client: *Client) void {
+    const wmh = xlib.XGetWMHints(display.handle, client.window);
+    if (wmh) |hints| {
+        defer _ = xlib.XFree(@ptrCast(hints));
+
+        if (client == (monitor_mod.selected_monitor orelse return).sel and (hints.*.flags & xlib.XUrgencyHint) != 0) {
+            hints.*.flags = hints.*.flags & ~@as(c_long, xlib.XUrgencyHint);
+            _ = xlib.XSetWMHints(display.handle, client.window, hints);
+        } else {
+            client.is_urgent = (hints.*.flags & xlib.XUrgencyHint) != 0;
+        }
+
+        if ((hints.*.flags & xlib.InputHint) != 0) {
+            client.never_focus = hints.*.input == 0;
+        } else {
+            client.never_focus = false;
+        }
+    }
+}
+
+fn update_window_type(display: *Display, client: *Client) void {
+    const state = get_atom_prop(display, client, net_wm_state);
+    const window_type = get_atom_prop(display, client, net_wm_window_type);
+
+    if (state == net_wm_state_fullscreen) {
+        set_fullscreen(display, client, true);
+    }
+    if (window_type == net_wm_window_type_dialog) {
+        client.is_floating = true;
+    }
+}
+
+fn update_title(display: *Display, client: *Client) void {
+    if (!get_text_prop(display, client.window, net_wm_name, &client.name)) {
+        _ = get_text_prop(display, client.window, xlib.XA_WM_NAME, &client.name);
+    }
+    if (client.name[0] == 0) {
+        @memcpy(client.name[0..6], "broken");
+    }
+}
+
+fn get_atom_prop(display: *Display, client: *Client, prop: xlib.Atom) xlib.Atom {
+    var actual_type: xlib.Atom = undefined;
+    var actual_format: c_int = undefined;
+    var num_items: c_ulong = undefined;
+    var bytes_after: c_ulong = undefined;
+    var prop_data: [*c]u8 = undefined;
+
+    if (xlib.XGetWindowProperty(display.handle, client.window, prop, 0, @sizeOf(xlib.Atom), xlib.False, xlib.XA_ATOM, &actual_type, &actual_format, &num_items, &bytes_after, &prop_data) == 0 and prop_data != null) {
+        const atom: xlib.Atom = @as(*xlib.Atom, @ptrCast(@alignCast(prop_data))).*;
+        _ = xlib.XFree(@ptrCast(prop_data));
+        return atom;
+    }
+    return 0;
+}
+
+fn get_text_prop(display: *Display, window: xlib.Window, atom: xlib.Atom, text: *[256]u8) bool {
+    var name: xlib.XTextProperty = undefined;
+    text[0] = 0;
+
+    if (xlib.XGetTextProperty(display.handle, window, &name, atom) == 0 or name.nitems == 0) {
+        return false;
+    }
+
+    if (name.encoding == xlib.XA_STRING) {
+        const len = @min(name.nitems, 255);
+        @memcpy(text[0..len], name.value[0..len]);
+        text[len] = 0;
+    } else {
+        var list: [*c][*c]u8 = undefined;
+        var count: c_int = undefined;
+        if (xlib.XmbTextPropertyToTextList(display.handle, &name, &list, &count) >= xlib.Success and count > 0 and list[0] != null) {
+            const str = std.mem.sliceTo(list[0], 0);
+            const copy_len = @min(str.len, 255);
+            @memcpy(text[0..copy_len], str[0..copy_len]);
+            text[copy_len] = 0;
+            xlib.XFreeStringList(list);
+        }
+    }
+    text[255] = 0;
+    _ = xlib.XFree(@ptrCast(name.value));
+    return true;
+}
+
+fn apply_rules(display: *Display, client: *Client) void {
+    var class_hint: xlib.XClassHint = .{ .res_name = null, .res_class = null };
+    _ = xlib.XGetClassHint(display.handle, client.window, &class_hint);
+
+    const class_str: []const u8 = if (class_hint.res_class) |ptr| std.mem.sliceTo(ptr, 0) else "";
+    const instance_str: []const u8 = if (class_hint.res_name) |ptr| std.mem.sliceTo(ptr, 0) else "";
+
+    client.is_floating = false;
+    client.tags = 0;
+
+    for (config.rules.items) |rule| {
+        const class_matches = if (rule.class) |rc| std.mem.indexOf(u8, class_str, rc) != null else true;
+        const instance_matches = if (rule.instance) |ri| std.mem.indexOf(u8, instance_str, ri) != null else true;
+        const title_matches = if (rule.title) |rt| std.mem.indexOf(u8, std.mem.sliceTo(&client.name, 0), rt) != null else true;
+
+        if (class_matches and instance_matches and title_matches) {
+            client.is_floating = rule.is_floating;
+            client.tags |= rule.tags;
+            if (rule.monitor >= 0) {
+                var target = monitor_mod.monitors;
+                var index: i32 = 0;
+                while (target) |mon| {
+                    if (index == rule.monitor) {
+                        client.monitor = mon;
+                        break;
+                    }
+                    index += 1;
+                    target = mon.next;
+                }
+            }
+        }
+    }
+
+    if (class_hint.res_class) |ptr| {
+        _ = xlib.XFree(@ptrCast(ptr));
+    }
+    if (class_hint.res_name) |ptr| {
+        _ = xlib.XFree(@ptrCast(ptr));
+    }
+
+    const monitor = client.monitor orelse return;
+    if (client.tags == 0) {
+        client.tags = monitor.tagset[monitor.sel_tags];
+    }
+}
+
+fn update_client_list(display: *Display) void {
+    _ = xlib.XDeleteProperty(display.handle, display.root, net_client_list);
+
+    var current_monitor = monitor_mod.monitors;
+    while (current_monitor) |monitor| {
+        var current_client = monitor.clients;
+        while (current_client) |client| {
+            _ = xlib.XChangeProperty(display.handle, display.root, net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
+            current_client = client.next;
+        }
+        current_monitor = monitor.next;
+    }
+}
+
+fn send_event(display: *Display, client: *Client, protocol: xlib.Atom) bool {
+    var protocols: [*c]xlib.Atom = undefined;
+    var num_protocols: c_int = 0;
+    var exists = false;
+
+    if (xlib.XGetWMProtocols(display.handle, client.window, &protocols, &num_protocols) != 0) {
+        var index: usize = 0;
+        while (index < @as(usize, @intCast(num_protocols))) : (index += 1) {
+            if (protocols[index] == protocol) {
+                exists = true;
+                break;
+            }
+        }
+        _ = xlib.XFree(@ptrCast(protocols));
+    }
+
+    if (exists) {
+        var event: xlib.XEvent = undefined;
+        event.type = xlib.ClientMessage;
+        event.xclient.window = client.window;
+        event.xclient.message_type = wm_protocols;
+        event.xclient.format = 32;
+        event.xclient.data.l[0] = @intCast(protocol);
+        event.xclient.data.l[1] = xlib.CurrentTime;
+        _ = xlib.XSendEvent(display.handle, client.window, xlib.False, xlib.NoEventMask, &event);
+    }
+    return exists;
+}
+
+fn set_urgent(display: *Display, client: *Client, urgent: bool) void {
+    client.is_urgent = urgent;
+    const wmh = xlib.XGetWMHints(display.handle, client.window);
+    if (wmh) |hints| {
+        if (urgent) {
+            hints.*.flags = hints.*.flags | xlib.XUrgencyHint;
+        } else {
+            hints.*.flags = hints.*.flags & ~@as(c_long, xlib.XUrgencyHint);
+        }
+        _ = xlib.XSetWMHints(display.handle, client.window, hints);
+        _ = xlib.XFree(@ptrCast(hints));
+    }
+}
diff --git a/src/monitor.rs b/src/monitor.rs
deleted file mode 100644
index caa18ad..0000000
--- a/src/monitor.rs
+++ /dev/null
@@ -1,198 +0,0 @@
-use crate::client::TagMask;
-use crate::errors::WmError;
-use x11rb::protocol::xinerama::ConnectionExt as _;
-use x11rb::protocol::xproto::{Screen, Window};
-use x11rb::rust_connection::RustConnection;
-
-type WmResult<T> = Result<T, WmError>;
-
-#[derive(Debug, Clone)]
-pub struct Pertag {
-    pub current_tag: usize,
-    pub previous_tag: usize,
-    pub num_masters: Vec<i32>,
-    pub master_factors: Vec<f32>,
-    pub layouts: Vec<String>,
-    pub show_bars: Vec<bool>,
-}
-
-impl Pertag {
-    pub fn new(
-        num_tags: usize,
-        default_num_master: i32,
-        default_master_factor: f32,
-        default_show_bar: bool,
-        default_layout: &str,
-    ) -> Self {
-        let len = num_tags + 1;
-        Self {
-            current_tag: 1,
-            previous_tag: 1,
-            num_masters: vec![default_num_master; len],
-            master_factors: vec![default_master_factor; len],
-            layouts: vec![default_layout.to_string(); len],
-            show_bars: vec![default_show_bar; len],
-        }
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Monitor {
-    pub layout_symbol: String,
-    pub master_factor: f32,
-    pub num_master: i32,
-    pub monitor_number: usize,
-    pub bar_y_position: i32,
-    pub screen_x: i32,
-    pub screen_y: i32,
-    pub screen_width: i32,
-    pub screen_height: i32,
-    pub window_area_x: i32,
-    pub window_area_y: i32,
-    pub window_area_width: i32,
-    pub window_area_height: i32,
-    pub gap_inner_horizontal: i32,
-    pub gap_inner_vertical: i32,
-    pub gap_outer_horizontal: i32,
-    pub gap_outer_vertical: i32,
-    pub selected_tags_index: usize,
-    pub selected_layout_index: usize,
-    pub tagset: [u32; 2],
-    pub show_bar: bool,
-    pub top_bar: bool,
-    pub clients_head: Option<Window>,
-    pub selected_client: Option<Window>,
-    pub stack_head: Option<Window>,
-    pub bar_window: Option<Window>,
-    pub layout_indices: [usize; 2],
-    pub scroll_offset: i32,
-    pub pertag: Option<Pertag>,
-}
-
-impl Monitor {
-    pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
-        Self {
-            layout_symbol: String::from("[]"),
-            master_factor: 0.55,
-            num_master: 1,
-            monitor_number: 0,
-            bar_y_position: 0,
-            screen_x: x,
-            screen_y: y,
-            screen_width: width as i32,
-            screen_height: height as i32,
-            window_area_x: x,
-            window_area_y: y,
-            window_area_width: width as i32,
-            window_area_height: height as i32,
-            gap_inner_horizontal: 3,
-            gap_inner_vertical: 3,
-            gap_outer_horizontal: 3,
-            gap_outer_vertical: 3,
-            selected_tags_index: 0,
-            selected_layout_index: 0,
-            tagset: [1, 1],
-            show_bar: true,
-            top_bar: true,
-            clients_head: None,
-            selected_client: None,
-            stack_head: None,
-            bar_window: None,
-            layout_indices: [0, 1],
-            scroll_offset: 0,
-            pertag: None,
-        }
-    }
-
-    pub fn init_pertag(&mut self, num_tags: usize, default_layout: &str) {
-        self.pertag = Some(Pertag::new(
-            num_tags,
-            self.num_master,
-            self.master_factor,
-            self.show_bar,
-            default_layout,
-        ));
-    }
-
-    pub fn contains_point(&self, x: i32, y: i32) -> bool {
-        x >= self.screen_x
-            && x < self.screen_x + self.screen_width
-            && y >= self.screen_y
-            && y < self.screen_y + self.screen_height
-    }
-
-    pub fn get_selected_tag(&self) -> TagMask {
-        self.tagset[self.selected_tags_index]
-    }
-}
-
-pub fn detect_monitors(
-    connection: &RustConnection,
-    screen: &Screen,
-    _root: Window,
-) -> WmResult<Vec<Monitor>> {
-    let fallback_monitors = || {
-        vec![Monitor::new(
-            0,
-            0,
-            screen.width_in_pixels as u32,
-            screen.height_in_pixels as u32,
-        )]
-    };
-
-    let mut monitors = Vec::<Monitor>::new();
-
-    let xinerama_active = connection
-        .xinerama_is_active()
-        .ok()
-        .and_then(|cookie| cookie.reply().ok())
-        .is_some_and(|reply| reply.state != 0);
-
-    if xinerama_active {
-        let Ok(xinerama_cookie) = connection.xinerama_query_screens() else {
-            return Ok(fallback_monitors());
-        };
-        let Ok(xinerama_reply) = xinerama_cookie.reply() else {
-            return Ok(fallback_monitors());
-        };
-
-        for screen_info in &xinerama_reply.screen_info {
-            let has_valid_dimensions = screen_info.width > 0 && screen_info.height > 0;
-            if !has_valid_dimensions {
-                continue;
-            }
-
-            let x_position = screen_info.x_org as i32;
-            let y_position = screen_info.y_org as i32;
-            let width_in_pixels = screen_info.width as u32;
-            let height_in_pixels = screen_info.height as u32;
-
-            let is_duplicate_monitor = monitors.iter().any(|monitor| {
-                monitor.screen_x == x_position
-                    && monitor.screen_y == y_position
-                    && monitor.screen_width == width_in_pixels as i32
-                    && monitor.screen_height == height_in_pixels as i32
-            });
-
-            if !is_duplicate_monitor {
-                monitors.push(Monitor::new(
-                    x_position,
-                    y_position,
-                    width_in_pixels,
-                    height_in_pixels,
-                ));
-            }
-        }
-    }
-
-    if monitors.is_empty() {
-        monitors = fallback_monitors();
-    }
-
-    monitors.sort_by(|a, b| match a.screen_y.cmp(&b.screen_y) {
-        std::cmp::Ordering::Equal => a.screen_x.cmp(&b.screen_x),
-        other => other,
-    });
-
-    Ok(monitors)
-}
diff --git a/src/monitor.zig b/src/monitor.zig
new file mode 100644
index 0000000..495ee91
--- /dev/null
+++ b/src/monitor.zig
@@ -0,0 +1,154 @@
+const std = @import("std");
+const xlib = @import("x11/xlib.zig");
+const Client = @import("client.zig").Client;
+
+pub const Layout = struct {
+    symbol: []const u8,
+    arrange_fn: ?*const fn (*Monitor) void,
+};
+
+pub const Pertag = struct {
+    curtag: u32 = 1,
+    prevtag: u32 = 1,
+    nmasters: [10]i32 = [_]i32{1} ** 10,
+    mfacts: [10]f32 = [_]f32{0.55} ** 10,
+    sellts: [10]u32 = [_]u32{0} ** 10,
+    ltidxs: [10][4]?*const Layout = [_][4]?*const Layout{.{ null, null, null, null }} ** 10,
+    showbars: [10]bool = [_]bool{true} ** 10,
+};
+
+pub const Monitor = struct {
+    lt_symbol: [16]u8 = std.mem.zeroes([16]u8),
+    mfact: f32 = 0.55,
+    nmaster: i32 = 1,
+    num: i32 = 0,
+    bar_y: i32 = 0,
+    mon_x: i32 = 0,
+    mon_y: i32 = 0,
+    mon_w: i32 = 0,
+    mon_h: i32 = 0,
+    win_x: i32 = 0,
+    win_y: i32 = 0,
+    win_w: i32 = 0,
+    win_h: i32 = 0,
+    gap_inner_h: i32 = 0,
+    gap_inner_v: i32 = 0,
+    gap_outer_h: i32 = 0,
+    gap_outer_v: i32 = 0,
+    scroll_offset: i32 = 0,
+    sel_tags: u32 = 0,
+    sel_lt: u32 = 0,
+    tagset: [2]u32 = .{ 1, 1 },
+    show_bar: bool = true,
+    top_bar: bool = true,
+    clients: ?*Client = null,
+    sel: ?*Client = null,
+    stack: ?*Client = null,
+    next: ?*Monitor = null,
+    bar_win: xlib.Window = 0,
+    lt: [4]?*const Layout = .{ null, null, null, null },
+    pertag: Pertag = Pertag{},
+};
+
+pub var monitors: ?*Monitor = null;
+pub var selected_monitor: ?*Monitor = null;
+
+var allocator: std.mem.Allocator = undefined;
+
+pub fn init(alloc: std.mem.Allocator) void {
+    allocator = alloc;
+}
+
+pub fn create() ?*Monitor {
+    const mon = allocator.create(Monitor) catch return null;
+    mon.* = Monitor{};
+    return mon;
+}
+
+pub fn destroy(mon: *Monitor) void {
+    allocator.destroy(mon);
+}
+
+var root_window: xlib.Window = 0;
+var display_handle: ?*xlib.Display = null;
+
+pub fn set_root_window(root: xlib.Window, display: *xlib.Display) void {
+    root_window = root;
+    display_handle = display;
+}
+
+pub fn window_to_monitor(win: xlib.Window) ?*Monitor {
+    if (win == root_window and display_handle != null) {
+        var root_x: c_int = undefined;
+        var root_y: c_int = undefined;
+        var dummy_win: xlib.Window = undefined;
+        var dummy_int: c_int = undefined;
+        var dummy_uint: c_uint = undefined;
+        if (xlib.XQueryPointer(display_handle.?, root_window, &dummy_win, &dummy_win, &root_x, &root_y, &dummy_int, &dummy_int, &dummy_uint) != 0) {
+            return rect_to_monitor(root_x, root_y, 1, 1);
+        }
+    }
+
+    var current = monitors;
+    while (current) |monitor| {
+        if (monitor.bar_win == win) {
+            return monitor;
+        }
+        current = monitor.next;
+    }
+
+    const client = @import("client.zig").window_to_client(win);
+    if (client) |found_client| {
+        return found_client.monitor;
+    }
+
+    return selected_monitor;
+}
+
+pub fn rect_to_monitor(x: i32, y: i32, width: i32, height: i32) ?*Monitor {
+    var result = selected_monitor;
+    var max_area: i32 = 0;
+
+    var current = monitors;
+    while (current) |monitor| {
+        const intersect_x = @max(0, @min(x + width, monitor.win_x + monitor.win_w) - @max(x, monitor.win_x));
+        const intersect_y = @max(0, @min(y + height, monitor.win_y + monitor.win_h) - @max(y, monitor.win_y));
+        const area = intersect_x * intersect_y;
+        if (area > max_area) {
+            max_area = area;
+            result = monitor;
+        }
+        current = monitor.next;
+    }
+    return result;
+}
+
+pub fn dir_to_monitor(direction: i32) ?*Monitor {
+    var target: ?*Monitor = null;
+
+    if (direction > 0) {
+        target = if (selected_monitor) |current| current.next else null;
+        if (target == null) {
+            target = monitors;
+        }
+    } else if (selected_monitor == monitors) {
+        var last = monitors;
+        while (last) |iter| {
+            if (iter.next == null) {
+                target = iter;
+                break;
+            }
+            last = iter.next;
+        }
+    } else {
+        var previous = monitors;
+        while (previous) |iter| {
+            if (iter.next == selected_monitor) {
+                target = iter;
+                break;
+            }
+            previous = iter.next;
+        }
+    }
+    return target;
+}
diff --git a/src/overlay/error.rs b/src/overlay/error.rs
deleted file mode 100644
index 128729e..0000000
--- a/src/overlay/error.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-use super::{Overlay, OverlayBase};
-use crate::bar::font::Font;
-use crate::errors::{ConfigError, X11Error};
-use x11rb::connection::Connection;
-use x11rb::protocol::xproto::*;
-use x11rb::rust_connection::RustConnection;
-
-const PADDING: i16 = 20;
-const LINE_SPACING: i16 = 5;
-const BORDER_WIDTH: u16 = 2;
-const BORDER_COLOR: u32 = 0xff5555;
-
-pub struct ErrorOverlay {
-    base: OverlayBase,
-    lines: Vec<String>,
-}
-
-impl ErrorOverlay {
-    pub fn new(
-        connection: &RustConnection,
-        screen: &Screen,
-        screen_num: usize,
-        display: *mut x11::xlib::Display,
-        _font: &Font,
-        _max_width: u16,
-    ) -> Result<Self, X11Error> {
-        let base = OverlayBase::new(
-            connection,
-            screen,
-            screen_num,
-            display,
-            400,
-            200,
-            BORDER_WIDTH,
-            BORDER_COLOR,
-            0x1a1a1a,
-            0xffffff,
-        )?;
-
-        Ok(ErrorOverlay {
-            base,
-            lines: Vec::new(),
-        })
-    }
-
-    pub fn show_error(
-        &mut self,
-        connection: &RustConnection,
-        font: &Font,
-        error: ConfigError,
-        monitor_x: i16,
-        monitor_y: i16,
-        screen_width: u16,
-        screen_height: u16,
-    ) -> Result<(), X11Error> {
-        let max_line_width = (screen_width as i16 / 2 - PADDING * 4).max(300) as u16;
-        let error_with_instruction = format!("{}\n\nFix the config file and reload.", error);
-        self.lines = self.wrap_text(&error_with_instruction, font, max_line_width);
-
-        let mut content_width = 0u16;
-        for line in &self.lines {
-            let line_width = font.text_width(line);
-            if line_width > content_width {
-                content_width = line_width;
-            }
-        }
-
-        let width = content_width + (PADDING as u16 * 2);
-        let line_height = font.height() + LINE_SPACING as u16;
-        let height = (self.lines.len() as u16 * line_height) + (PADDING as u16 * 2);
-
-        let x = monitor_x + ((screen_width - width) / 2) as i16;
-        let y = monitor_y + ((screen_height - height) / 2) as i16;
-
-        self.base.configure(connection, x, y, width, height)?;
-        self.base.is_visible = true;
-        self.draw(connection, font)?;
-        self.base.show(connection)?;
-        Ok(())
-    }
-
-    fn wrap_text(&self, text: &str, font: &Font, max_width: u16) -> Vec<String> {
-        let mut lines = Vec::new();
-        for paragraph in text.lines() {
-            if paragraph.trim().is_empty() {
-                lines.push(String::new());
-                continue;
-            }
-
-            let words: Vec<&str> = paragraph.split_whitespace().collect();
-            let mut current_line = String::new();
-
-            for word in words {
-                let test_line = if current_line.is_empty() {
-                    word.to_string()
-                } else {
-                    format!("{} {}", current_line, word)
-                };
-                if font.text_width(&test_line) <= max_width {
-                    current_line = test_line;
-                } else {
-                    if !current_line.is_empty() {
-                        lines.push(current_line);
-                    }
-                    current_line = word.to_string();
-                }
-            }
-            if !current_line.is_empty() {
-                lines.push(current_line);
-            }
-        }
-        lines
-    }
-}
-
-impl Overlay for ErrorOverlay {
-    fn window(&self) -> Window {
-        self.base.window
-    }
-
-    fn is_visible(&self) -> bool {
-        self.base.is_visible
-    }
-
-    fn hide(&mut self, connection: &RustConnection) -> Result<(), X11Error> {
-        self.base.hide(connection)?;
-        self.lines.clear();
-        Ok(())
-    }
-
-    fn draw(&self, connection: &RustConnection, font: &Font) -> Result<(), X11Error> {
-        if !self.base.is_visible {
-            return Ok(());
-        }
-        self.base.draw_background(connection)?;
-        let line_height = font.height() + LINE_SPACING as u16;
-        let mut y = PADDING + font.ascent();
-        for line in &self.lines {
-            self.base
-                .font_draw
-                .draw_text(font, self.base.foreground_color, PADDING, y, line);
-            y += line_height as i16;
-        }
-        connection.flush()?;
-        self.base.font_draw.sync();
-        Ok(())
-    }
-}
diff --git a/src/overlay/keybind.rs b/src/overlay/keybind.rs
deleted file mode 100644
index 278bf28..0000000
--- a/src/overlay/keybind.rs
+++ /dev/null
@@ -1,320 +0,0 @@
-use super::{Overlay, OverlayBase};
-use crate::bar::font::Font;
-use crate::errors::X11Error;
-use crate::keyboard::KeyAction;
-use crate::keyboard::handlers::{KeyBinding, KeyPress};
-use std::time::Instant;
-use x11rb::connection::Connection;
-use x11rb::protocol::xproto::*;
-use x11rb::rust_connection::RustConnection;
-
-const PADDING: i16 = 24;
-const KEY_ACTION_SPACING: i16 = 20;
-const LINE_SPACING: i16 = 8;
-const BORDER_WIDTH: u16 = 4;
-const BORDER_COLOR: u32 = 0x7fccff;
-const TITLE_BOTTOM_MARGIN: i16 = 20;
-const INPUT_SUPPRESS_MS: u128 = 200;
-
-pub struct KeybindOverlay {
-    base: OverlayBase,
-    keybindings: Vec<(String, String)>,
-    key_bg_color: u32,
-    modkey: KeyButMask,
-    last_shown_at: Option<Instant>,
-    max_key_width: u16,
-}
-
-impl KeybindOverlay {
-    pub fn new(
-        connection: &RustConnection,
-        screen: &Screen,
-        screen_num: usize,
-        display: *mut x11::xlib::Display,
-        modkey: KeyButMask,
-    ) -> Result<Self, X11Error> {
-        let base = OverlayBase::new(
-            connection,
-            screen,
-            screen_num,
-            display,
-            800,
-            600,
-            BORDER_WIDTH,
-            BORDER_COLOR,
-            0x1a1a1a,
-            0xffffff,
-        )?;
-
-        Ok(KeybindOverlay {
-            base,
-            keybindings: Vec::new(),
-            key_bg_color: 0x2a2a2a,
-            modkey,
-            last_shown_at: None,
-            max_key_width: 0,
-        })
-    }
-
-    pub fn show(
-        &mut self,
-        connection: &RustConnection,
-        font: &Font,
-        keybindings: &[KeyBinding],
-        monitor_x: i16,
-        monitor_y: i16,
-        screen_width: u16,
-        screen_height: u16,
-    ) -> Result<(), X11Error> {
-        self.keybindings = self.collect_keybindings(keybindings);
-
-        let title = "Important Keybindings";
-        let title_width = font.text_width(title);
-
-        let mut max_key_width = 0u16;
-        let mut max_action_width = 0u16;
-
-        for (key, action) in &self.keybindings {
-            let key_width = font.text_width(key);
-            let action_width = font.text_width(action);
-            if key_width > max_key_width {
-                max_key_width = key_width;
-            }
-            if action_width > max_action_width {
-                max_action_width = action_width;
-            }
-        }
-
-        let content_width = max_key_width + KEY_ACTION_SPACING as u16 + max_action_width;
-        let min_width = title_width.max(content_width);
-
-        let width = min_width + (PADDING as u16 * 2);
-
-        let line_height = font.height() + LINE_SPACING as u16;
-        let title_height = font.height() + TITLE_BOTTOM_MARGIN as u16;
-        let height =
-            title_height + (self.keybindings.len() as u16 * line_height) + (PADDING as u16 * 2);
-
-        let x = monitor_x + ((screen_width - width) / 2) as i16;
-        let y = monitor_y + ((screen_height - height) / 2) as i16;
-
-        self.base.configure(connection, x, y, width, height)?;
-
-        self.last_shown_at = Some(Instant::now());
-        self.max_key_width = max_key_width;
-
-        self.base.is_visible = true;
-        self.draw(connection, font)?;
-
-        self.base.show(connection)?;
-
-        Ok(())
-    }
-
-    pub fn toggle(
-        &mut self,
-        connection: &RustConnection,
-        font: &Font,
-        keybindings: &[KeyBinding],
-        monitor_x: i16,
-        monitor_y: i16,
-        screen_width: u16,
-        screen_height: u16,
-    ) -> Result<(), X11Error> {
-        if self.base.is_visible {
-            self.hide(connection)?;
-        } else {
-            self.show(
-                connection,
-                font,
-                keybindings,
-                monitor_x,
-                monitor_y,
-                screen_width,
-                screen_height,
-            )?;
-        }
-        Ok(())
-    }
-
-    pub fn should_suppress_input(&self) -> bool {
-        if let Some(shown_at) = self.last_shown_at {
-            shown_at.elapsed().as_millis() < INPUT_SUPPRESS_MS
-        } else {
-            false
-        }
-    }
-
-    fn collect_keybindings(&self, keybindings: &[KeyBinding]) -> Vec<(String, String)> {
-        let mut result = Vec::new();
-
-        let priority_actions = [
-            KeyAction::ShowKeybindOverlay,
-            KeyAction::Quit,
-            KeyAction::Restart,
-            KeyAction::KillClient,
-            KeyAction::Spawn,
-            KeyAction::SpawnTerminal,
-            KeyAction::ToggleFullScreen,
-            KeyAction::ToggleFloating,
-            KeyAction::CycleLayout,
-            KeyAction::FocusStack,
-            KeyAction::ViewTag,
-        ];
-
-        for &action in &priority_actions {
-            let binding = keybindings
-                .iter()
-                .filter(|kb| kb.func == action)
-                .min_by_key(|kb| kb.keys.len());
-
-            if let Some(binding) = binding
-                && !binding.keys.is_empty()
-            {
-                let key_str = self.format_key_combo(&binding.keys[0]);
-                let action_str = self.action_description(binding);
-                result.push((key_str, action_str));
-            }
-        }
-
-        result
-    }
-
-    fn format_key_combo(&self, key: &KeyPress) -> String {
-        let mut parts = Vec::new();
-
-        for modifier in &key.modifiers {
-            let mod_str = match *modifier {
-                m if m == self.modkey => "Mod",
-                KeyButMask::SHIFT => "Shift",
-                KeyButMask::CONTROL => "Ctrl",
-                KeyButMask::MOD1 => "Alt",
-                KeyButMask::MOD4 => "Super",
-                _ => continue,
-            };
-            parts.push(mod_str.to_string());
-        }
-
-        parts.push(crate::keyboard::keysyms::format_keysym(key.keysym));
-
-        parts.join(" + ")
-    }
-
-    fn action_description(&self, binding: &KeyBinding) -> String {
-        use crate::keyboard::Arg;
-
-        match binding.func {
-            KeyAction::ShowKeybindOverlay => "Show This Keybind Help".to_string(),
-            KeyAction::Quit => "Quit Window Manager".to_string(),
-            KeyAction::Restart => "Restart Window Manager".to_string(),
-            KeyAction::KillClient => "Close Focused Window".to_string(),
-            KeyAction::Spawn => match &binding.arg {
-                Arg::Str(cmd) => format!("Launch: {}", cmd),
-                Arg::Array(arr) if !arr.is_empty() => format!("Launch: {}", arr[0]),
-                _ => "Launch Program".to_string(),
-            },
-            KeyAction::SpawnTerminal => "Launch Terminal".to_string(),
-            KeyAction::FocusStack => "Focus Next/Previous Window".to_string(),
-            KeyAction::MoveStack => "Move Window Up/Down Stack".to_string(),
-            KeyAction::ViewTag => match &binding.arg {
-                Arg::Int(n) => format!("View Workspace {}", n),
-                _ => "View Workspace".to_string(),
-            },
-            KeyAction::ViewNextTag => "View Next Workspace".to_string(),
-            KeyAction::ViewPreviousTag => "View Previous Workspace".to_string(),
-            KeyAction::ViewNextNonEmptyTag => "View Next Non-Empty Workspace".to_string(),
-            KeyAction::ViewPreviousNonEmptyTag => "View Previous Non-Empty Workspace".to_string(),
-            KeyAction::ToggleView => match &binding.arg {
-                Arg::Int(n) => format!("Toggle View Workspace {}", n),
-                _ => "Toggle View Workspace".to_string(),
-            },
-            KeyAction::MoveToTag => "Move Window to Workspace".to_string(),
-            KeyAction::ToggleTag => "Toggle Window on Workspace".to_string(),
-            KeyAction::ToggleGaps => "Toggle Window Gaps".to_string(),
-            KeyAction::ToggleFullScreen => "Toggle Fullscreen Mode".to_string(),
-            KeyAction::ToggleFloating => "Toggle Floating Mode".to_string(),
-            KeyAction::ChangeLayout => "Change Layout".to_string(),
-            KeyAction::CycleLayout => "Cycle Through Layouts".to_string(),
-            KeyAction::FocusMonitor => "Focus Next Monitor".to_string(),
-            KeyAction::TagMonitor => "Send Window to Monitor".to_string(),
-            KeyAction::SetMasterFactor => "Adjust Master Area Size".to_string(),
-            KeyAction::IncNumMaster => "Adjust Number of Master Windows".to_string(),
-            KeyAction::ScrollLeft => "Scroll Layout Left".to_string(),
-            KeyAction::ScrollRight => "Scroll Layout Right".to_string(),
-            KeyAction::None => "No Action".to_string(),
-        }
-    }
-}
-
-impl Overlay for KeybindOverlay {
-    fn window(&self) -> Window {
-        self.base.window
-    }
-
-    fn is_visible(&self) -> bool {
-        self.base.is_visible
-    }
-
-    fn hide(&mut self, connection: &RustConnection) -> Result<(), X11Error> {
-        self.base.hide(connection)?;
-        self.last_shown_at = None;
-        self.keybindings.clear();
-        Ok(())
-    }
-
-    fn draw(&self, connection: &RustConnection, font: &Font) -> Result<(), X11Error> {
-        if !self.base.is_visible {
-            return Ok(());
-        }
-
-        self.base.draw_background(connection)?;
-
-        let title = "Important Keybindings";
-        let title_width = font.text_width(title);
-        let title_x = ((self.base.width - title_width) / 2) as i16;
-        let title_y = PADDING + font.ascent();
-
-        self.base
-            .font_draw
-            .draw_text(font, self.base.foreground_color, title_x, title_y, title);
-
-        let line_height = font.height() + LINE_SPACING as u16;
-        let mut y = PADDING + font.height() as i16 + TITLE_BOTTOM_MARGIN + font.ascent();
-
-        for (key, action) in &self.keybindings {
-            let key_width = font.text_width(key);
-            let key_x = PADDING;
-
-            connection.change_gc(
-                self.base.graphics_context,
-                &ChangeGCAux::new().foreground(self.key_bg_color),
-            )?;
-            connection.poly_fill_rectangle(
-                self.base.window,
-                self.base.graphics_context,
-                &[Rectangle {
-                    x: key_x - 4,
-                    y: y - font.ascent() - 2,
-                    width: key_width + 8,
-                    height: font.height() + 4,
-                }],
-            )?;
-
-            self.base
-                .font_draw
-                .draw_text(font, self.base.foreground_color, key_x, y, key);
-
-            let action_x = PADDING + self.max_key_width as i16 + KEY_ACTION_SPACING;
-            self.base
-                .font_draw
-                .draw_text(font, self.base.foreground_color, action_x, y, action);
-
-            y += line_height as i16;
-        }
-
-        connection.flush()?;
-        self.base.font_draw.sync();
-
-        Ok(())
-    }
-}
diff --git a/src/overlay/mod.rs b/src/overlay/mod.rs
deleted file mode 100644
index bd87f63..0000000
--- a/src/overlay/mod.rs
+++ /dev/null
@@ -1,156 +0,0 @@
-use crate::bar::font::{Font, FontDraw};
-use crate::errors::X11Error;
-use x11rb::COPY_DEPTH_FROM_PARENT;
-use x11rb::connection::Connection;
-use x11rb::protocol::xproto::*;
-use x11rb::rust_connection::RustConnection;
-
-pub mod error;
-pub mod keybind;
-
-pub use error::ErrorOverlay;
-pub use keybind::KeybindOverlay;
-
-pub trait Overlay {
-    fn window(&self) -> Window;
-    fn is_visible(&self) -> bool;
-    fn hide(&mut self, connection: &RustConnection) -> Result<(), X11Error>;
-    fn draw(&self, connection: &RustConnection, font: &Font) -> Result<(), X11Error>;
-}
-
-pub struct OverlayBase {
-    pub window: Window,
-    pub width: u16,
-    pub height: u16,
-    pub graphics_context: Gcontext,
-    pub font_draw: FontDraw,
-    pub is_visible: bool,
-    pub background_color: u32,
-    pub foreground_color: u32,
-}
-
-impl OverlayBase {
-    pub fn new(
-        connection: &RustConnection,
-        screen: &Screen,
-        screen_num: usize,
-        display: *mut x11::xlib::Display,
-        width: u16,
-        height: u16,
-        border_width: u16,
-        border_color: u32,
-        background_color: u32,
-        foreground_color: u32,
-    ) -> Result<Self, X11Error> {
-        let window = connection.generate_id()?;
-        let graphics_context = connection.generate_id()?;
-
-        connection.create_window(
-            COPY_DEPTH_FROM_PARENT,
-            window,
-            screen.root,
-            0,
-            0,
-            width,
-            height,
-            border_width,
-            WindowClass::INPUT_OUTPUT,
-            screen.root_visual,
-            &CreateWindowAux::new()
-                .background_pixel(background_color)
-                .border_pixel(border_color)
-                .event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS | EventMask::KEY_PRESS)
-                .override_redirect(1),
-        )?;
-
-        connection.create_gc(
-            graphics_context,
-            window,
-            &CreateGCAux::new()
-                .foreground(foreground_color)
-                .background(background_color),
-        )?;
-
-        connection.flush()?;
-
-        let visual = unsafe { x11::xlib::XDefaultVisual(display, screen_num as i32) };
-        let colormap = unsafe { x11::xlib::XDefaultColormap(display, screen_num as i32) };
-
-        let font_draw = FontDraw::new(display, window as x11::xlib::Drawable, visual, colormap)?;
-
-        Ok(OverlayBase {
-            window,
-            width,
-            height,
-            graphics_context,
-            font_draw,
-            is_visible: false,
-            background_color,
-            foreground_color,
-        })
-    }
-
-    pub fn configure(
-        &mut self,
-        connection: &RustConnection,
-        x: i16,
-        y: i16,
-        width: u16,
-        height: u16,
-    ) -> Result<(), X11Error> {
-        self.width = width;
-        self.height = height;
-
-        connection.configure_window(
-            self.window,
-            &ConfigureWindowAux::new()
-                .x(x as i32)
-                .y(y as i32)
-                .width(width as u32)
-                .height(height as u32),
-        )?;
-
-        Ok(())
-    }
-
-    pub fn show(&mut self, connection: &RustConnection) -> Result<(), X11Error> {
-        connection.configure_window(
-            self.window,
-            &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
-        )?;
-
-        connection.map_window(self.window)?;
-        connection.flush()?;
-
-        self.is_visible = true;
-
-        Ok(())
-    }
-
-    pub fn hide(&mut self, connection: &RustConnection) -> Result<(), X11Error> {
-        if self.is_visible {
-            connection.unmap_window(self.window)?;
-            connection.flush()?;
-            self.is_visible = false;
-        }
-        Ok(())
-    }
-
-    pub fn draw_background(&self, connection: &RustConnection) -> Result<(), X11Error> {
-        connection.change_gc(
-            self.graphics_context,
-            &ChangeGCAux::new().foreground(self.background_color),
-        )?;
-        connection.poly_fill_rectangle(
-            self.window,
-            self.graphics_context,
-            &[Rectangle {
-                x: 0,
-                y: 0,
-                width: self.width,
-                height: self.height,
-            }],
-        )?;
-        Ok(())
-    }
-}
diff --git a/src/signal.rs b/src/signal.rs
deleted file mode 100644
index c5cec0b..0000000
--- a/src/signal.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use std::process::{Command, Stdio};
-
-pub fn spawn_detached(cmd: &str) {
-    if let Ok(mut child) = Command::new("sh")
-        .arg("-c")
-        .arg(format!("({}) &", cmd))
-        .stdin(Stdio::null())
-        .stdout(Stdio::null())
-        .stderr(Stdio::null())
-        .spawn()
-    {
-        let _ = child.wait();
-    }
-}
-
-pub fn spawn_detached_with_args(program: &str, args: &[&str]) {
-    let escaped_args: Vec<String> = args.iter().map(|a| shell_escape(a)).collect();
-    let full_cmd = if escaped_args.is_empty() {
-        program.to_string()
-    } else {
-        format!("{} {}", program, escaped_args.join(" "))
-    };
-    spawn_detached(&full_cmd)
-}
-
-fn shell_escape(s: &str) -> String {
-    if s.contains(|c: char| c.is_whitespace() || c == '\'' || c == '"' || c == '\\') {
-        format!("'{}'", s.replace('\'', "'\\''"))
-    } else {
-        s.to_string()
-    }
-}
diff --git a/src/size_hints.rs b/src/size_hints.rs
deleted file mode 100644
index 22543dd..0000000
--- a/src/size_hints.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-#![allow(dead_code)]
-
-pub mod flags {
-    pub const P_MIN_SIZE: u32 = 1 << 4;
-    pub const P_MAX_SIZE: u32 = 1 << 5;
-    pub const P_RESIZE_INC: u32 = 1 << 6;
-    pub const P_ASPECT: u32 = 1 << 7;
-    pub const P_BASE_SIZE: u32 = 1 << 8;
-}
-
-pub mod offset {
-    pub const FLAGS: usize = 0;
-    pub const MIN_WIDTH: usize = 5;
-    pub const MIN_HEIGHT: usize = 6;
-    pub const MAX_WIDTH: usize = 7;
-    pub const MAX_HEIGHT: usize = 8;
-    pub const WIDTH_INC: usize = 9;
-    pub const HEIGHT_INC: usize = 10;
-    pub const MIN_ASPECT_X: usize = 11;
-    pub const MIN_ASPECT_Y: usize = 12;
-    pub const MAX_ASPECT_X: usize = 13;
-    pub const MAX_ASPECT_Y: usize = 14;
-    pub const BASE_WIDTH: usize = 15;
-    pub const BASE_HEIGHT: usize = 16;
-}
diff --git a/src/tab_bar.rs b/src/tab_bar.rs
deleted file mode 100644
index a9835da..0000000
--- a/src/tab_bar.rs
+++ /dev/null
@@ -1,278 +0,0 @@
-use crate::ColorScheme;
-use crate::bar::font::{DrawingSurface, Font};
-use crate::errors::X11Error;
-use crate::layout::tabbed::TAB_BAR_HEIGHT;
-use x11rb::COPY_DEPTH_FROM_PARENT;
-use x11rb::connection::Connection;
-use x11rb::protocol::xproto::*;
-use x11rb::rust_connection::RustConnection;
-
-pub struct TabBar {
-    window: Window,
-    width: u16,
-    height: u16,
-    x_offset: i16,
-    y_offset: i16,
-    graphics_context: Gcontext,
-    display: *mut x11::xlib::Display,
-    surface: DrawingSurface,
-    scheme_normal: ColorScheme,
-    scheme_selected: ColorScheme,
-}
-
-impl TabBar {
-    pub fn new(
-        connection: &RustConnection,
-        screen: &Screen,
-        screen_num: usize,
-        display: *mut x11::xlib::Display,
-        _font: &Font,
-        x: i16,
-        y: i16,
-        width: u16,
-        scheme_normal: ColorScheme,
-        scheme_selected: ColorScheme,
-        cursor: u32,
-    ) -> Result<Self, X11Error> {
-        let window = connection.generate_id()?;
-        let graphics_context = connection.generate_id()?;
-
-        let height = TAB_BAR_HEIGHT as u16;
-
-        connection.create_window(
-            COPY_DEPTH_FROM_PARENT,
-            window,
-            screen.root,
-            x,
-            y,
-            width,
-            height,
-            0,
-            WindowClass::INPUT_OUTPUT,
-            screen.root_visual,
-            &CreateWindowAux::new()
-                .background_pixel(scheme_normal.background)
-                .event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS)
-                .override_redirect(1),
-        )?;
-
-        connection.create_gc(
-            graphics_context,
-            window,
-            &CreateGCAux::new()
-                .foreground(scheme_normal.foreground)
-                .background(scheme_normal.background),
-        )?;
-
-        unsafe {
-            x11::xlib::XDefineCursor(display, window as u64, cursor as u64);
-        }
-
-        connection.map_window(window)?;
-        connection.flush()?;
-
-        let visual = unsafe { x11::xlib::XDefaultVisual(display, screen_num as i32) };
-        let colormap = unsafe { x11::xlib::XDefaultColormap(display, screen_num as i32) };
-
-        let surface = DrawingSurface::new(
-            display,
-            window as x11::xlib::Drawable,
-            width as u32,
-            height as u32,
-            visual,
-            colormap,
-        )?;
-
-        Ok(Self {
-            window,
-            width,
-            height,
-            x_offset: x,
-            y_offset: y,
-            graphics_context,
-            display,
-            surface,
-            scheme_normal,
-            scheme_selected,
-        })
-    }
-
-    pub fn window(&self) -> Window {
-        self.window
-    }
-
-    pub fn draw(
-        &mut self,
-        connection: &RustConnection,
-        font: &Font,
-        windows: &[(Window, String)],
-        focused_window: Option<Window>,
-    ) -> Result<(), X11Error> {
-        connection.change_gc(
-            self.graphics_context,
-            &ChangeGCAux::new().foreground(self.scheme_normal.background),
-        )?;
-        connection.flush()?;
-
-        unsafe {
-            let gc =
-                x11::xlib::XCreateGC(self.display, self.surface.pixmap(), 0, std::ptr::null_mut());
-            x11::xlib::XSetForeground(self.display, gc, self.scheme_normal.background as u64);
-            x11::xlib::XFillRectangle(
-                self.display,
-                self.surface.pixmap(),
-                gc,
-                0,
-                0,
-                self.width as u32,
-                self.height as u32,
-            );
-            x11::xlib::XFreeGC(self.display, gc);
-        }
-
-        if windows.is_empty() {
-            self.copy_pixmap_to_window();
-            return Ok(());
-        }
-
-        let tab_width = self.width / windows.len() as u16;
-        let mut x_position: i16 = 0;
-
-        for (index, &(window, ref title)) in windows.iter().enumerate() {
-            let is_focused = Some(window) == focused_window;
-            let scheme = if is_focused {
-                &self.scheme_selected
-            } else {
-                &self.scheme_normal
-            };
-
-            let display_title = if title.is_empty() {
-                format!("Window {}", index + 1)
-            } else {
-                title.clone()
-            };
-
-            let text_width = font.text_width(&display_title);
-            let text_x = x_position + ((tab_width.saturating_sub(text_width)) / 2) as i16;
-
-            let top_padding = 6;
-            let text_y = top_padding + font.ascent();
-
-            self.surface.font_draw().draw_text(
-                font,
-                scheme.foreground,
-                text_x,
-                text_y,
-                &display_title,
-            );
-
-            if is_focused {
-                let underline_height = 3;
-                let underline_y = self.height as i16 - underline_height;
-
-                unsafe {
-                    let gc = x11::xlib::XCreateGC(
-                        self.display,
-                        self.surface.pixmap(),
-                        0,
-                        std::ptr::null_mut(),
-                    );
-                    x11::xlib::XSetForeground(self.display, gc, scheme.underline as u64);
-                    x11::xlib::XFillRectangle(
-                        self.display,
-                        self.surface.pixmap(),
-                        gc,
-                        x_position as i32,
-                        underline_y as i32,
-                        tab_width as u32,
-                        underline_height as u32,
-                    );
-                    x11::xlib::XFreeGC(self.display, gc);
-                }
-            }
-
-            x_position += tab_width as i16;
-        }
-
-        self.copy_pixmap_to_window();
-        Ok(())
-    }
-
-    fn copy_pixmap_to_window(&self) {
-        unsafe {
-            let gc =
-                x11::xlib::XCreateGC(self.display, self.window as u64, 0, std::ptr::null_mut());
-            x11::xlib::XCopyArea(
-                self.display,
-                self.surface.pixmap(),
-                self.window as u64,
-                gc,
-                0,
-                0,
-                self.width as u32,
-                self.height as u32,
-                0,
-                0,
-            );
-            x11::xlib::XFreeGC(self.display, gc);
-        }
-    }
-
-    pub fn get_clicked_window(&self, windows: &[(Window, String)], click_x: i16) -> Option<Window> {
-        if windows.is_empty() {
-            return None;
-        }
-
-        let tab_width = self.width / windows.len() as u16;
-        let tab_index = (click_x as u16 / tab_width) as usize;
-
-        windows.get(tab_index).map(|&(win, _)| win)
-    }
-
-    pub fn reposition(
-        &mut self,
-        connection: &RustConnection,
-        x: i16,
-        y: i16,
-        width: u16,
-    ) -> Result<(), X11Error> {
-        self.x_offset = x;
-        self.y_offset = y;
-        self.width = width;
-
-        connection.configure_window(
-            self.window,
-            &ConfigureWindowAux::new()
-                .x(x as i32)
-                .y(y as i32)
-                .width(width as u32),
-        )?;
-
-        let visual = unsafe { x11::xlib::XDefaultVisual(self.display, 0) };
-        let colormap = unsafe { x11::xlib::XDefaultColormap(self.display, 0) };
-
-        self.surface = DrawingSurface::new(
-            self.display,
-            self.window as x11::xlib::Drawable,
-            width as u32,
-            self.height as u32,
-            visual,
-            colormap,
-        )?;
-
-        connection.flush()?;
-        Ok(())
-    }
-
-    pub fn hide(&self, connection: &RustConnection) -> Result<(), X11Error> {
-        connection.unmap_window(self.window)?;
-        connection.flush()?;
-        Ok(())
-    }
-
-    pub fn show(&self, connection: &RustConnection) -> Result<(), X11Error> {
-        connection.map_window(self.window)?;
-        connection.flush()?;
-        Ok(())
-    }
-}
diff --git a/src/window_manager.rs b/src/window_manager.rs
deleted file mode 100644
index 5114c1f..0000000
--- a/src/window_manager.rs
+++ /dev/null
@@ -1,4793 +0,0 @@
-use crate::Config;
-use crate::animations::{AnimationConfig, ScrollAnimation};
-use crate::bar::Bar;
-use crate::client::{Client, TagMask};
-use crate::errors::{ConfigError, WmError};
-use crate::keyboard::{self, Arg, KeyAction, handlers};
-use crate::layout::GapConfig;
-use crate::layout::tiling::TilingLayout;
-use crate::layout::{Layout, LayoutBox, LayoutType, layout_from_str, next_layout};
-use crate::monitor::{Monitor, detect_monitors};
-use crate::overlay::{ErrorOverlay, KeybindOverlay, Overlay};
-use std::collections::{HashMap, HashSet};
-
-use x11rb::connection::Connection;
-use x11rb::protocol::Event;
-use x11rb::protocol::xproto::*;
-use x11rb::rust_connection::RustConnection;
-
-enum Control {
-    Continue,
-    Quit,
-}
-
-pub fn tag_mask(tag: usize) -> TagMask {
-    1 << tag
-}
-
-pub fn unmask_tag(mask: TagMask) -> usize {
-    mask.trailing_zeros() as usize
-}
-
-struct AtomCache {
-    net_supported: Atom,
-    net_supporting_wm_check: Atom,
-    net_current_desktop: Atom,
-    net_client_info: Atom,
-    wm_state: Atom,
-    wm_protocols: Atom,
-    wm_delete_window: Atom,
-    net_wm_state: Atom,
-    net_wm_state_fullscreen: Atom,
-    net_wm_window_type: Atom,
-    net_wm_window_type_dialog: Atom,
-    wm_name: Atom,
-    net_wm_name: Atom,
-    utf8_string: Atom,
-    net_active_window: Atom,
-    wm_take_focus: Atom,
-    net_client_list: Atom,
-}
-
-impl AtomCache {
-    fn new(connection: &RustConnection) -> WmResult<Self> {
-        let net_supported = connection
-            .intern_atom(false, b"_NET_SUPPORTED")?
-            .reply()?
-            .atom;
-
-        let net_supporting_wm_check = connection
-            .intern_atom(false, b"_NET_SUPPORTING_WM_CHECK")?
-            .reply()?
-            .atom;
-
-        let net_current_desktop = connection
-            .intern_atom(false, b"_NET_CURRENT_DESKTOP")?
-            .reply()?
-            .atom;
-
-        let net_client_info = connection
-            .intern_atom(false, b"_NET_CLIENT_INFO")?
-            .reply()?
-            .atom;
-
-        let wm_state = connection.intern_atom(false, b"WM_STATE")?.reply()?.atom;
-
-        let wm_protocols = connection
-            .intern_atom(false, b"WM_PROTOCOLS")?
-            .reply()?
-            .atom;
-
-        let wm_delete_window = connection
-            .intern_atom(false, b"WM_DELETE_WINDOW")?
-            .reply()?
-            .atom;
-
-        let net_wm_state = connection
-            .intern_atom(false, b"_NET_WM_STATE")?
-            .reply()?
-            .atom;
-
-        let net_wm_state_fullscreen = connection
-            .intern_atom(false, b"_NET_WM_STATE_FULLSCREEN")?
-            .reply()?
-            .atom;
-
-        let net_wm_window_type = connection
-            .intern_atom(false, b"_NET_WM_WINDOW_TYPE")?
-            .reply()?
-            .atom;
-
-        let net_wm_window_type_dialog = connection
-            .intern_atom(false, b"_NET_WM_WINDOW_TYPE_DIALOG")?
-            .reply()?
-            .atom;
-
-        let wm_name = AtomEnum::WM_NAME.into();
-        let net_wm_name = connection
-            .intern_atom(false, b"_NET_WM_NAME")?
-            .reply()?
-            .atom;
-        let utf8_string = connection.intern_atom(false, b"UTF8_STRING")?.reply()?.atom;
-        let net_active_window = connection
-            .intern_atom(false, b"_NET_ACTIVE_WINDOW")?
-            .reply()?
-            .atom;
-
-        let wm_take_focus = connection
-            .intern_atom(false, b"WM_TAKE_FOCUS")?
-            .reply()?
-            .atom;
-
-        let net_client_list = connection
-            .intern_atom(false, b"_NET_CLIENT_LIST")?
-            .reply()?
-            .atom;
-
-        Ok(Self {
-            net_supported,
-            net_supporting_wm_check,
-            net_current_desktop,
-            net_client_info,
-            wm_state,
-            wm_protocols,
-            wm_delete_window,
-            net_wm_state,
-            net_wm_state_fullscreen,
-            net_wm_window_type,
-            net_wm_window_type_dialog,
-            wm_name,
-            net_wm_name,
-            utf8_string,
-            net_active_window,
-            wm_take_focus,
-            net_client_list,
-        })
-    }
-}
-
-pub struct WindowManager {
-    config: Config,
-    connection: RustConnection,
-    screen_number: usize,
-    root: Window,
-    _wm_check_window: Window,
-    screen: Screen,
-    windows: Vec<Window>,
-    clients: HashMap<Window, Client>,
-    layout: LayoutBox,
-    gaps_enabled: bool,
-    floating_windows: HashSet<Window>,
-    fullscreen_windows: HashSet<Window>,
-    bars: Vec<Bar>,
-    tab_bars: Vec<crate::tab_bar::TabBar>,
-    show_bar: bool,
-    monitors: Vec<Monitor>,
-    selected_monitor: usize,
-    atoms: AtomCache,
-    previous_focused: Option<Window>,
-    display: *mut x11::xlib::Display,
-    font: crate::bar::font::Font,
-    keychord_state: keyboard::handlers::KeychordState,
-    current_key: usize,
-    keyboard_mapping: Option<keyboard::KeyboardMapping>,
-    error_message: Option<String>,
-    overlay: ErrorOverlay,
-    keybind_overlay: KeybindOverlay,
-    scroll_animation: ScrollAnimation,
-    animation_config: AnimationConfig,
-}
-
-type WmResult<T> = Result<T, WmError>;
-
-impl WindowManager {
-    pub fn new(config: Config) -> WmResult<Self> {
-        let (connection, screen_number) = x11rb::connect(None)?;
-        let root = connection.setup().roots[screen_number].root;
-        let screen = connection.setup().roots[screen_number].clone();
-
-        connection
-            .change_window_attributes(
-                root,
-                &ChangeWindowAttributesAux::new().event_mask(
-                    EventMask::SUBSTRUCTURE_REDIRECT
-                        | EventMask::SUBSTRUCTURE_NOTIFY
-                        | EventMask::PROPERTY_CHANGE
-                        | EventMask::KEY_PRESS
-                        | EventMask::BUTTON_PRESS
-                        | EventMask::POINTER_MOTION,
-                ),
-            )?
-            .check()?;
-
-        let ignore_modifiers = [
-            0,
-            u16::from(ModMask::LOCK),
-            u16::from(ModMask::M2),
-            u16::from(ModMask::LOCK | ModMask::M2),
-        ];
-
-        for &ignore_mask in &ignore_modifiers {
-            let grab_mask = u16::from(config.modkey) | ignore_mask;
-
-            connection.grab_button(
-                false,
-                root,
-                EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE,
-                GrabMode::SYNC,
-                GrabMode::ASYNC,
-                x11rb::NONE,
-                x11rb::NONE,
-                ButtonIndex::M1,
-                grab_mask.into(),
-            )?;
-
-            connection.grab_button(
-                false,
-                root,
-                EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE,
-                GrabMode::SYNC,
-                GrabMode::ASYNC,
-                x11rb::NONE,
-                x11rb::NONE,
-                ButtonIndex::M3,
-                grab_mask.into(),
-            )?;
-        }
-
-        let mut monitors = detect_monitors(&connection, &screen, root)?;
-        for monitor in monitors.iter_mut() {
-            monitor.init_pertag(config.tags.len(), "tiling");
-        }
-
-        let display = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) };
-        if display.is_null() {
-            return Err(WmError::X11(crate::errors::X11Error::DisplayOpenFailed));
-        }
-
-        // C has better C interop than rust.
-        let normal_cursor = unsafe { x11::xlib::XCreateFontCursor(display, 68) };
-
-        unsafe {
-            x11::xlib::XDefineCursor(display, root as u64, normal_cursor);
-        }
-
-        let font = crate::bar::font::Font::new(display, screen_number as i32, &config.font)?;
-
-        let mut bars = Vec::new();
-        for monitor in monitors.iter() {
-            let bar = Bar::new(
-                &connection,
-                &screen,
-                screen_number,
-                &config,
-                display,
-                &font,
-                monitor.screen_x as i16,
-                monitor.screen_y as i16,
-                monitor.screen_width as u16,
-                normal_cursor as u32,
-            )?;
-            bars.push(bar);
-        }
-
-        let bar_height = font.height() as f32 * 1.4;
-        let mut tab_bars = Vec::new();
-        for monitor in monitors.iter() {
-            let tab_bar = crate::tab_bar::TabBar::new(
-                &connection,
-                &screen,
-                screen_number,
-                display,
-                &font,
-                (monitor.screen_x + config.gap_outer_horizontal as i32) as i16,
-                (monitor.screen_y as f32 + bar_height + config.gap_outer_vertical as f32) as i16,
-                monitor
-                    .screen_width
-                    .saturating_sub(2 * config.gap_outer_horizontal as i32) as u16,
-                config.scheme_occupied,
-                config.scheme_selected,
-                normal_cursor as u32,
-            )?;
-            tab_bars.push(tab_bar);
-        }
-
-        let gaps_enabled = config.gaps_enabled;
-
-        let atoms = AtomCache::new(&connection)?;
-
-        let supported_atoms: Vec<Atom> = vec![
-            atoms.net_supported,
-            atoms.net_supporting_wm_check,
-            atoms.net_wm_state,
-            atoms.net_wm_state_fullscreen,
-            atoms.net_wm_window_type,
-            atoms.net_wm_window_type_dialog,
-            atoms.net_active_window,
-            atoms.net_wm_name,
-            atoms.net_current_desktop,
-            atoms.net_client_info,
-            atoms.net_client_list,
-        ];
-        let supported_bytes: Vec<u8> = supported_atoms
-            .iter()
-            .flat_map(|a| a.to_ne_bytes())
-            .collect();
-        connection.change_property(
-            PropMode::REPLACE,
-            root,
-            atoms.net_supported,
-            AtomEnum::ATOM,
-            32,
-            supported_atoms.len() as u32,
-            &supported_bytes,
-        )?;
-
-        let wm_check_window = connection.generate_id()?;
-        connection.create_window(
-            screen.root_depth,
-            wm_check_window,
-            root,
-            0,
-            0,
-            1,
-            1,
-            0,
-            WindowClass::INPUT_OUTPUT,
-            0,
-            &CreateWindowAux::new(),
-        )?;
-
-        connection.change_property(
-            PropMode::REPLACE,
-            wm_check_window,
-            atoms.net_supporting_wm_check,
-            AtomEnum::WINDOW,
-            32,
-            1,
-            &wm_check_window.to_ne_bytes(),
-        )?;
-
-        connection.change_property(
-            PropMode::REPLACE,
-            wm_check_window,
-            atoms.net_wm_name,
-            atoms.utf8_string,
-            8,
-            4,
-            b"oxwm",
-        )?;
-
-        connection.change_property(
-            PropMode::REPLACE,
-            root,
-            atoms.net_supporting_wm_check,
-            AtomEnum::WINDOW,
-            32,
-            1,
-            &wm_check_window.to_ne_bytes(),
-        )?;
-
-        let overlay = ErrorOverlay::new(
-            &connection,
-            &screen,
-            screen_number,
-            display,
-            &font,
-            screen.width_in_pixels,
-        )?;
-
-        let keybind_overlay =
-            KeybindOverlay::new(&connection, &screen, screen_number, display, config.modkey)?;
-
-        let mut window_manager = Self {
-            config,
-            connection,
-            screen_number,
-            root,
-            _wm_check_window: wm_check_window,
-            screen,
-            windows: Vec::new(),
-            clients: HashMap::new(),
-            layout: Box::new(TilingLayout),
-            gaps_enabled,
-            floating_windows: HashSet::new(),
-            fullscreen_windows: HashSet::new(),
-            bars,
-            tab_bars,
-            show_bar: true,
-            monitors,
-            selected_monitor: 0,
-            atoms,
-            previous_focused: None,
-            display,
-            font,
-            keychord_state: keyboard::handlers::KeychordState::Idle,
-            current_key: 0,
-            keyboard_mapping: None,
-            error_message: None,
-            overlay,
-            keybind_overlay,
-            scroll_animation: ScrollAnimation::new(),
-            animation_config: AnimationConfig::default(),
-        };
-
-        for tab_bar in &window_manager.tab_bars {
-            tab_bar.hide(&window_manager.connection)?;
-        }
-
-        window_manager.scan_existing_windows()?;
-        window_manager.update_bar()?;
-        window_manager.run_autostart_commands();
-
-        Ok(window_manager)
-    }
-
-    pub fn show_startup_config_error(&mut self, error: ConfigError) {
-        let monitor = &self.monitors[self.selected_monitor];
-        let monitor_x = monitor.screen_x as i16;
-        let monitor_y = monitor.screen_y as i16;
-        let screen_width = monitor.screen_width as u16;
-        let screen_height = monitor.screen_height as u16;
-
-        if let Err(e) = self.overlay.show_error(
-            &self.connection,
-            &self.font,
-            error,
-            monitor_x,
-            monitor_y,
-            screen_width,
-            screen_height,
-        ) {
-            eprintln!("Failed to show config error overlay: {:?}", e);
-        }
-    }
-
-    fn try_reload_config(&mut self) -> Result<(), ConfigError> {
-        let lua_path = self
-            .config
-            .path
-            .as_ref()
-            .ok_or(ConfigError::NoConfigPathSet)?;
-
-        if !lua_path.exists() {
-            return Err(ConfigError::NoConfigAtPath);
-        }
-
-        let config_str =
-            std::fs::read_to_string(lua_path).map_err(|e| ConfigError::CouldNotReadConfig(e))?;
-
-        let config_dir = lua_path.parent();
-
-        let new_config = crate::config::parse_lua_config(&config_str, config_dir)?;
-
-        let lua_path = self.config.path.take();
-
-        self.config = new_config;
-        self.config.path = lua_path;
-        self.error_message = None;
-
-        for bar in &mut self.bars {
-            bar.update_from_config(&self.config);
-        }
-
-        Ok(())
-    }
-
-    fn scan_existing_windows(&mut self) -> WmResult<()> {
-        let tree = self.connection.query_tree(self.root)?.reply()?;
-        let net_client_info = self.atoms.net_client_info;
-        let wm_state_atom = self.atoms.wm_state;
-
-        for &window in &tree.children {
-            if self.bars.iter().any(|bar| bar.window() == window) {
-                continue;
-            }
-
-            let Ok(attrs) = self.connection.get_window_attributes(window)?.reply() else {
-                continue;
-            };
-
-            if attrs.override_redirect {
-                continue;
-            }
-
-            if attrs.map_state == MapState::VIEWABLE {
-                let _tag = self.get_saved_tag(window, net_client_info)?;
-                self.windows.push(window);
-                continue;
-            }
-
-            if attrs.map_state == MapState::UNMAPPED {
-                let has_wm_state = self
-                    .connection
-                    .get_property(false, window, wm_state_atom, AtomEnum::ANY, 0, 2)?
-                    .reply()
-                    .is_ok_and(|prop| !prop.value.is_empty());
-
-                if !has_wm_state {
-                    continue;
-                }
-
-                let has_wm_class = self
-                    .connection
-                    .get_property(false, window, AtomEnum::WM_CLASS, AtomEnum::STRING, 0, 1024)?
-                    .reply()
-                    .is_ok_and(|prop| !prop.value.is_empty());
-
-                if has_wm_class {
-                    let _tag = self.get_saved_tag(window, net_client_info)?;
-                    self.connection.map_window(window)?;
-                    self.windows.push(window);
-                }
-            }
-        }
-
-        if let Some(&first) = self.windows.first() {
-            self.focus(Some(first))?;
-        }
-
-        self.apply_layout()?;
-        Ok(())
-    }
-
-    fn get_saved_tag(&self, window: Window, net_client_info: Atom) -> WmResult<TagMask> {
-        match self
-            .connection
-            .get_property(false, window, net_client_info, AtomEnum::CARDINAL, 0, 2)?
-            .reply()
-        {
-            Ok(prop) if prop.value.len() >= 4 => {
-                let tags = u32::from_ne_bytes([
-                    prop.value[0],
-                    prop.value[1],
-                    prop.value[2],
-                    prop.value[3],
-                ]);
-
-                if tags != 0 && tags < (1 << self.config.tags.len()) {
-                    return Ok(tags);
-                }
-            }
-            Ok(_) => {}
-            Err(e) => {
-                eprintln!("No _NET_CLIENT_INFO property ({})", e);
-            }
-        }
-
-        Ok(self
-            .monitors
-            .get(self.selected_monitor)
-            .map(|m| m.tagset[m.selected_tags_index])
-            .unwrap_or(tag_mask(0)))
-    }
-
-    fn save_client_tag(&self, window: Window, tag: TagMask) -> WmResult<()> {
-        let net_client_info = self.atoms.net_client_info;
-
-        let bytes = tag.to_ne_bytes().to_vec();
-
-        self.connection.change_property(
-            PropMode::REPLACE,
-            window,
-            net_client_info,
-            AtomEnum::CARDINAL,
-            32,
-            1,
-            &bytes,
-        )?;
-
-        self.connection.flush()?;
-        Ok(())
-    }
-
-    fn set_wm_state(&self, window: Window, state: u32) -> WmResult<()> {
-        let wm_state_atom = self.atoms.wm_state;
-
-        let data = [state, 0u32];
-        let bytes: Vec<u8> = data.iter().flat_map(|&v| v.to_ne_bytes()).collect();
-
-        self.connection.change_property(
-            PropMode::REPLACE,
-            window,
-            wm_state_atom,
-            wm_state_atom,
-            32,
-            2,
-            &bytes,
-        )?;
-
-        self.connection.flush()?;
-        Ok(())
-    }
-
-    fn update_client_list(&self) -> WmResult<()> {
-        let window_bytes: Vec<u8> = self
-            .windows
-            .iter()
-            .flat_map(|window| window.to_ne_bytes())
-            .collect();
-
-        self.connection.change_property(
-            PropMode::REPLACE,
-            self.root,
-            self.atoms.net_client_list,
-            AtomEnum::WINDOW,
-            32,
-            self.windows.len() as u32,
-            &window_bytes,
-        )?;
-
-        Ok(())
-    }
-
-    pub fn run(&mut self) -> WmResult<()> {
-        println!("oxwm started on display {}", self.screen_number);
-
-        self.grab_keys()?;
-        self.update_bar()?;
-
-        let mut last_bar_update = std::time::Instant::now();
-        const BAR_UPDATE_INTERVAL_MS: u64 = 100;
-
-        loop {
-            match self.connection.poll_for_event_with_sequence()? {
-                Some((event, _sequence)) => {
-                    if matches!(self.handle_event(event)?, Control::Quit) {
-                        return Ok(());
-                    }
-                }
-                None => {
-                    if last_bar_update.elapsed().as_millis() >= BAR_UPDATE_INTERVAL_MS as u128 {
-                        if let Some(bar) = self.bars.get_mut(self.selected_monitor) {
-                            bar.update_blocks();
-                        }
-                        if self.bars.iter().any(|bar| bar.needs_redraw()) {
-                            self.update_bar()?;
-                        }
-                        last_bar_update = std::time::Instant::now();
-                    }
-
-                    self.tick_animations()?;
-
-                    self.connection.flush()?;
-                    std::thread::sleep(std::time::Duration::from_millis(16));
-                }
-            }
-        }
-    }
-
-    fn toggle_floating(&mut self) -> WmResult<()> {
-        let focused = self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client);
-
-        if focused.is_none() {
-            return Ok(());
-        }
-        let focused = focused.unwrap();
-
-        if let Some(client) = self.clients.get(&focused)
-            && client.is_fullscreen
-        {
-            return Ok(());
-        }
-
-        let (is_fixed, x, y, w, h) = if let Some(client) = self.clients.get(&focused) {
-            (
-                client.is_fixed,
-                client.x_position as i32,
-                client.y_position as i32,
-                client.width as u32,
-                client.height as u32,
-            )
-        } else {
-            return Ok(());
-        };
-
-        let was_floating = self.floating_windows.contains(&focused);
-
-        if was_floating {
-            self.floating_windows.remove(&focused);
-            if let Some(client) = self.clients.get_mut(&focused) {
-                client.is_floating = false;
-            }
-        } else {
-            self.floating_windows.insert(focused);
-            if let Some(client) = self.clients.get_mut(&focused) {
-                client.is_floating = is_fixed || !client.is_floating;
-            }
-
-            self.connection.configure_window(
-                focused,
-                &ConfigureWindowAux::new()
-                    .x(x)
-                    .y(y)
-                    .width(w)
-                    .height(h)
-                    .stack_mode(StackMode::ABOVE),
-            )?;
-        }
-
-        self.apply_layout()?;
-        Ok(())
-    }
-
-    fn set_master_factor(&mut self, delta: f32) -> WmResult<()> {
-        if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-            let new_mfact = (monitor.master_factor + delta).clamp(0.05, 0.95);
-            monitor.master_factor = new_mfact;
-            if let Some(ref mut pertag) = monitor.pertag {
-                pertag.master_factors[pertag.current_tag] = new_mfact;
-            }
-            self.apply_layout()?;
-        }
-        Ok(())
-    }
-
-    fn inc_num_master(&mut self, delta: i32) -> WmResult<()> {
-        if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-            let new_nmaster = (monitor.num_master + delta).max(0);
-            monitor.num_master = new_nmaster;
-            if let Some(ref mut pertag) = monitor.pertag {
-                pertag.num_masters[pertag.current_tag] = new_nmaster;
-            }
-            self.apply_layout()?;
-        }
-        Ok(())
-    }
-
-    fn tick_animations(&mut self) -> WmResult<()> {
-        if self.scroll_animation.is_active() {
-            if let Some(new_offset) = self.scroll_animation.update() {
-                if let Some(m) = self.monitors.get_mut(self.selected_monitor) {
-                    m.scroll_offset = new_offset;
-                }
-                self.apply_layout()?;
-                self.update_bar()?;
-            }
-        }
-        Ok(())
-    }
-
-    fn scroll_layout(&mut self, direction: i32) -> WmResult<()> {
-        if self.layout.name() != "scrolling" {
-            return Ok(());
-        }
-
-        let monitor_index = self.selected_monitor;
-        let monitor = match self.monitors.get(monitor_index) {
-            Some(m) => m.clone(),
-            None => return Ok(()),
-        };
-
-        let visible_count = if monitor.num_master > 0 {
-            monitor.num_master as usize
-        } else {
-            2
-        };
-
-        let mut tiled_count = 0;
-        let mut current = self.next_tiled(monitor.clients_head, &monitor);
-        while let Some(window) = current {
-            tiled_count += 1;
-            if let Some(client) = self.clients.get(&window) {
-                current = self.next_tiled(client.next, &monitor);
-            } else {
-                break;
-            }
-        }
-
-        if tiled_count <= visible_count {
-            if let Some(m) = self.monitors.get_mut(monitor_index) {
-                m.scroll_offset = 0;
-            }
-            return Ok(());
-        }
-
-        let outer_gap = if self.gaps_enabled {
-            self.config.gap_outer_vertical
-        } else {
-            0
-        };
-        let inner_gap = if self.gaps_enabled {
-            self.config.gap_inner_vertical
-        } else {
-            0
-        };
-
-        let available_width = monitor.screen_width - 2 * outer_gap as i32;
-        let total_inner_gaps = inner_gap as i32 * (visible_count - 1) as i32;
-        let window_width = (available_width - total_inner_gaps) / visible_count as i32;
-        let scroll_amount = window_width + inner_gap as i32;
-
-        let total_width =
-            tiled_count as i32 * window_width + (tiled_count - 1) as i32 * inner_gap as i32;
-        let max_scroll = (total_width - available_width).max(0);
-
-        let current_offset = monitor.scroll_offset;
-        let target_offset = if self.scroll_animation.is_active() {
-            self.scroll_animation.target() + direction * scroll_amount
-        } else {
-            current_offset + direction * scroll_amount
-        };
-        let target_offset = target_offset.clamp(0, max_scroll);
-
-        self.scroll_animation
-            .start(current_offset, target_offset, &self.animation_config);
-
-        Ok(())
-    }
-
-    fn scroll_to_window(&mut self, target_window: Window, animate: bool) -> WmResult<()> {
-        if self.layout.name() != "scrolling" {
-            return Ok(());
-        }
-
-        let monitor_index = self.selected_monitor;
-        let monitor = match self.monitors.get(monitor_index) {
-            Some(m) => m.clone(),
-            None => return Ok(()),
-        };
-
-        let visible_count = if monitor.num_master > 0 {
-            monitor.num_master as usize
-        } else {
-            2
-        };
-
-        let outer_gap = if self.gaps_enabled {
-            self.config.gap_outer_vertical
-        } else {
-            0
-        };
-        let inner_gap = if self.gaps_enabled {
-            self.config.gap_inner_vertical
-        } else {
-            0
-        };
-
-        let mut tiled_windows = Vec::new();
-        let mut current = self.next_tiled(monitor.clients_head, &monitor);
-        while let Some(window) = current {
-            tiled_windows.push(window);
-            if let Some(client) = self.clients.get(&window) {
-                current = self.next_tiled(client.next, &monitor);
-            } else {
-                break;
-            }
-        }
-
-        let target_idx = tiled_windows.iter().position(|&w| w == target_window);
-        let target_idx = match target_idx {
-            Some(idx) => idx,
-            None => return Ok(()),
-        };
-
-        let tiled_count = tiled_windows.len();
-        if tiled_count <= visible_count {
-            if animate && monitor.scroll_offset != 0 {
-                self.scroll_animation
-                    .start(monitor.scroll_offset, 0, &self.animation_config);
-            } else if let Some(m) = self.monitors.get_mut(monitor_index) {
-                m.scroll_offset = 0;
-            }
-            return Ok(());
-        }
-
-        let available_width = monitor.screen_width - 2 * outer_gap as i32;
-        let total_inner_gaps = inner_gap as i32 * (visible_count - 1) as i32;
-        let window_width = (available_width - total_inner_gaps) / visible_count as i32;
-        let scroll_step = window_width + inner_gap as i32;
-
-        let total_width =
-            tiled_count as i32 * window_width + (tiled_count - 1) as i32 * inner_gap as i32;
-        let max_scroll = (total_width - available_width).max(0);
-
-        let target_scroll = (target_idx as i32) * scroll_step;
-        let new_offset = target_scroll.clamp(0, max_scroll);
-
-        let current_offset = monitor.scroll_offset;
-        if current_offset != new_offset {
-            if animate {
-                self.scroll_animation
-                    .start(current_offset, new_offset, &self.animation_config);
-            } else {
-                if let Some(m) = self.monitors.get_mut(monitor_index) {
-                    m.scroll_offset = new_offset;
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    fn toggle_bar(&mut self) -> WmResult<()> {
-        if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-            monitor.show_bar = !monitor.show_bar;
-            self.show_bar = monitor.show_bar;
-            if let Some(ref mut pertag) = monitor.pertag {
-                pertag.show_bars[pertag.current_tag] = monitor.show_bar;
-            }
-        }
-        self.apply_layout()?;
-        self.update_bar()?;
-        Ok(())
-    }
-
-    fn get_layout_symbol(&self) -> String {
-        let layout_name = self.layout.name();
-
-        if layout_name == "scrolling" {
-            if let Some(monitor) = self.monitors.get(self.selected_monitor) {
-                let visible_count = if monitor.num_master > 0 {
-                    monitor.num_master as usize
-                } else {
-                    2
-                };
-
-                let mut tiled_count = 0;
-                let mut current = self.next_tiled(monitor.clients_head, monitor);
-                while let Some(window) = current {
-                    tiled_count += 1;
-                    if let Some(client) = self.clients.get(&window) {
-                        current = self.next_tiled(client.next, monitor);
-                    } else {
-                        break;
-                    }
-                }
-
-                if tiled_count > 0 {
-                    let outer_gap = if self.gaps_enabled {
-                        self.config.gap_outer_vertical
-                    } else {
-                        0
-                    };
-                    let inner_gap = if self.gaps_enabled {
-                        self.config.gap_inner_vertical
-                    } else {
-                        0
-                    };
-
-                    let available_width = monitor.screen_width - 2 * outer_gap as i32;
-                    let total_inner_gaps =
-                        inner_gap as i32 * (visible_count.min(tiled_count) - 1) as i32;
-                    let window_width = if tiled_count <= visible_count {
-                        (available_width - total_inner_gaps) / tiled_count as i32
-                    } else {
-                        (available_width - inner_gap as i32 * (visible_count - 1) as i32)
-                            / visible_count as i32
-                    };
-
-                    let scroll_step = window_width + inner_gap as i32;
-                    let first_visible = if scroll_step > 0 {
-                        (monitor.scroll_offset / scroll_step) + 1
-                    } else {
-                        1
-                    };
-                    let last_visible =
-                        (first_visible + visible_count as i32 - 1).min(tiled_count as i32);
-
-                    return format!("[{}-{}/{}]", first_visible, last_visible, tiled_count);
-                }
-            }
-        }
-
-        self.config
-            .layout_symbols
-            .iter()
-            .find(|l| l.name == layout_name)
-            .map(|l| l.symbol.clone())
-            .unwrap_or_else(|| self.layout.symbol().to_string())
-    }
-
-    fn get_keychord_indicator(&self) -> Option<String> {
-        match &self.keychord_state {
-            keyboard::handlers::KeychordState::Idle => None,
-            keyboard::handlers::KeychordState::InProgress {
-                candidates,
-                keys_pressed,
-            } => {
-                if candidates.is_empty() {
-                    return None;
-                }
-
-                let binding = &self.config.keybindings[candidates[0]];
-                let mut indicator = String::new();
-
-                for (i, key_press) in binding.keys.iter().take(*keys_pressed).enumerate() {
-                    if i > 0 {
-                        indicator.push(' ');
-                    }
-
-                    for modifier in &key_press.modifiers {
-                        indicator.push_str(Self::format_modifier(*modifier));
-                        indicator.push('+');
-                    }
-
-                    indicator.push_str(&keyboard::keysyms::format_keysym(key_press.keysym));
-                }
-
-                indicator.push('-');
-                Some(indicator)
-            }
-        }
-    }
-
-    fn format_modifier(modifier: KeyButMask) -> &'static str {
-        match modifier {
-            KeyButMask::MOD1 => "Alt",
-            KeyButMask::MOD4 => "Super",
-            KeyButMask::SHIFT => "Shift",
-            KeyButMask::CONTROL => "Ctrl",
-            _ => "Mod",
-        }
-    }
-
-    fn update_bar(&mut self) -> WmResult<()> {
-        let layout_symbol = self.get_layout_symbol();
-        let keychord_indicator = self.get_keychord_indicator();
-
-        for (monitor_index, monitor) in self.monitors.iter().enumerate() {
-            if let Some(bar) = self.bars.get_mut(monitor_index) {
-                let mut occupied_tags: TagMask = 0;
-                let mut urgent_tags: TagMask = 0;
-                for client in self.clients.values() {
-                    if client.monitor_index == monitor_index {
-                        occupied_tags |= client.tags;
-                        if client.is_urgent {
-                            urgent_tags |= client.tags;
-                        }
-                    }
-                }
-
-                let draw_blocks = monitor_index == self.selected_monitor;
-                bar.invalidate();
-                bar.draw(
-                    &self.connection,
-                    &self.font,
-                    self.display,
-                    monitor.tagset[monitor.selected_tags_index],
-                    occupied_tags,
-                    urgent_tags,
-                    draw_blocks,
-                    &layout_symbol,
-                    keychord_indicator.as_deref(),
-                )?;
-            }
-        }
-        Ok(())
-    }
-
-    fn update_tab_bars(&mut self) -> WmResult<()> {
-        for (monitor_index, monitor) in self.monitors.iter().enumerate() {
-            if let Some(tab_bar) = self.tab_bars.get_mut(monitor_index) {
-                let visible_windows: Vec<(Window, String)> = self
-                    .windows
-                    .iter()
-                    .filter_map(|&window| {
-                        if let Some(client) = self.clients.get(&window) {
-                            if client.monitor_index != monitor_index
-                                || self.floating_windows.contains(&window)
-                                || self.fullscreen_windows.contains(&window)
-                            {
-                                return None;
-                            }
-                            if (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0 {
-                                return Some((window, client.name.clone()));
-                            }
-                        }
-                        None
-                    })
-                    .collect();
-
-                let focused_window = monitor.selected_client;
-
-                tab_bar.draw(
-                    &self.connection,
-                    &self.font,
-                    &visible_windows,
-                    focused_window,
-                )?;
-            }
-        }
-        Ok(())
-    }
-
-    fn handle_key_action(&mut self, action: KeyAction, arg: &Arg) -> WmResult<()> {
-        match action {
-            KeyAction::Spawn => handlers::handle_spawn_action(action, arg, self.selected_monitor)?,
-            KeyAction::SpawnTerminal => {
-                crate::signal::spawn_detached(&self.config.terminal);
-            }
-            KeyAction::KillClient => {
-                if let Some(focused) = self
-                    .monitors
-                    .get(self.selected_monitor)
-                    .and_then(|m| m.selected_client)
-                {
-                    self.kill_client(focused)?;
-                }
-            }
-            KeyAction::ToggleFullScreen => {
-                self.fullscreen()?;
-                self.restack()?;
-            }
-            KeyAction::ChangeLayout => {
-                if let Arg::Str(layout_name) = arg {
-                    match layout_from_str(layout_name) {
-                        Ok(layout) => {
-                            self.layout = layout;
-                            if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-                                if let Some(ref mut pertag) = monitor.pertag {
-                                    pertag.layouts[pertag.current_tag] = layout_name.to_string();
-                                }
-                            }
-                            if layout_name != "normie" && layout_name != "floating" {
-                                self.floating_windows.clear();
-                            }
-                            self.apply_layout()?;
-                            self.update_bar()?;
-                            self.restack()?;
-                        }
-                        Err(e) => eprintln!("Failed to change layout: {}", e),
-                    }
-                }
-            }
-            KeyAction::CycleLayout => {
-                let current_name = self.layout.name();
-                let next_name = next_layout(current_name);
-                match layout_from_str(next_name) {
-                    Ok(layout) => {
-                        self.layout = layout;
-                        if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-                            if let Some(ref mut pertag) = monitor.pertag {
-                                pertag.layouts[pertag.current_tag] = next_name.to_string();
-                            }
-                        }
-                        if next_name != "normie" && next_name != "floating" {
-                            self.floating_windows.clear();
-                        }
-                        self.apply_layout()?;
-                        self.update_bar()?;
-                        self.restack()?;
-                    }
-                    Err(e) => eprintln!("Failed to cycle layout: {}", e),
-                }
-            }
-            KeyAction::ToggleFloating => {
-                self.toggle_floating()?;
-                self.restack()?;
-            }
-
-            KeyAction::FocusStack => {
-                if let Arg::Int(direction) = arg {
-                    self.focusstack(*direction)?;
-                    self.restack()?;
-                }
-            }
-            KeyAction::MoveStack => {
-                if let Arg::Int(direction) = arg {
-                    self.move_stack(*direction)?;
-                    self.restack()?;
-                }
-            }
-            KeyAction::Quit | KeyAction::Restart => {}
-            KeyAction::ViewTag => {
-                if let Arg::Int(tag_index) = arg {
-                    self.view_tag(*tag_index as usize)?;
-                }
-            }
-            KeyAction::ViewNextTag => {
-                let monitor = self.get_selected_monitor();
-                let current_tag_index = unmask_tag(monitor.get_selected_tag()) as i32;
-                let len = self.config.tags.len() as i32;
-                self.view_tag((current_tag_index + 1).rem_euclid(len) as usize)?;
-            }
-            KeyAction::ViewPreviousTag => {
-                let monitor = self.get_selected_monitor();
-                let current_tag_index = unmask_tag(monitor.get_selected_tag()) as i32;
-                let len = self.config.tags.len() as i32;
-                self.view_tag((current_tag_index - 1).rem_euclid(len) as usize)?;
-            }
-            KeyAction::ViewNextNonEmptyTag => {
-                let monitor = self.get_selected_monitor();
-                let current = unmask_tag(monitor.get_selected_tag()) as i32;
-                let len = self.config.tags.len() as i32;
-                let mon_num = monitor.monitor_number;
-
-                for offset in 1..len {
-                    let next = (current + offset).rem_euclid(len) as usize;
-                    if self.has_windows_on_tag(mon_num, next) {
-                        self.view_tag(next)?;
-                        break;
-                    }
-                }
-            }
-            KeyAction::ViewPreviousNonEmptyTag => {
-                let monitor = self.get_selected_monitor();
-                let current = unmask_tag(monitor.get_selected_tag()) as i32;
-                let len = self.config.tags.len() as i32;
-                let mon_num = monitor.monitor_number;
-
-                for offset in 1..len {
-                    let prev = (current - offset).rem_euclid(len) as usize;
-                    if self.has_windows_on_tag(mon_num, prev) {
-                        self.view_tag(prev)?;
-                        break;
-                    }
-                }
-            }
-            KeyAction::ToggleView => {
-                if let Arg::Int(tag_index) = arg {
-                    self.toggleview(*tag_index as usize)?;
-                }
-            }
-            KeyAction::MoveToTag => {
-                if let Arg::Int(tag_index) = arg {
-                    self.move_to_tag(*tag_index as usize)?;
-                }
-            }
-            KeyAction::ToggleTag => {
-                if let Arg::Int(tag_index) = arg {
-                    self.toggletag(*tag_index as usize)?;
-                }
-            }
-            KeyAction::ToggleGaps => {
-                self.gaps_enabled = !self.gaps_enabled;
-                self.apply_layout()?;
-                self.restack()?;
-            }
-            KeyAction::FocusMonitor => {
-                if let Arg::Int(direction) = arg {
-                    self.focus_monitor(*direction)?;
-                }
-            }
-            KeyAction::TagMonitor => {
-                if let Arg::Int(direction) = arg {
-                    self.send_window_to_adjacent_monitor(*direction)?;
-                }
-            }
-            KeyAction::ShowKeybindOverlay => {
-                let monitor = &self.monitors[self.selected_monitor];
-                self.keybind_overlay.toggle(
-                    &self.connection,
-                    &self.font,
-                    &self.config.keybindings,
-                    monitor.screen_x as i16,
-                    monitor.screen_y as i16,
-                    monitor.screen_width as u16,
-                    monitor.screen_height as u16,
-                )?;
-            }
-            KeyAction::SetMasterFactor => {
-                if let Arg::Int(delta) = arg {
-                    self.set_master_factor(*delta as f32 / 100.0)?;
-                }
-            }
-            KeyAction::IncNumMaster => {
-                if let Arg::Int(delta) = arg {
-                    self.inc_num_master(*delta)?;
-                }
-            }
-            KeyAction::ScrollLeft => {
-                self.scroll_layout(-1)?;
-            }
-            KeyAction::ScrollRight => {
-                self.scroll_layout(1)?;
-            }
-            KeyAction::None => {}
-        }
-        Ok(())
-    }
-
-    fn is_window_visible(&self, window: Window) -> bool {
-        if let Some(client) = self.clients.get(&window) {
-            let monitor = self.monitors.get(client.monitor_index);
-            let selected_tags = monitor
-                .map(|m| m.tagset[m.selected_tags_index])
-                .unwrap_or(0);
-            (client.tags & selected_tags) != 0
-        } else {
-            false
-        }
-    }
-
-    fn visible_windows(&self) -> Vec<Window> {
-        let mut result = Vec::new();
-        for monitor in &self.monitors {
-            let mut current = monitor.clients_head;
-            while let Some(window) = current {
-                if let Some(client) = self.clients.get(&window) {
-                    let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index];
-                    if visible_tags != 0 {
-                        result.push(window);
-                    }
-                    current = client.next;
-                } else {
-                    break;
-                }
-            }
-        }
-        result
-    }
-
-    fn visible_windows_on_monitor(&self, monitor_index: usize) -> Vec<Window> {
-        let mut result = Vec::new();
-        if let Some(monitor) = self.monitors.get(monitor_index) {
-            let mut current = monitor.clients_head;
-            while let Some(window) = current {
-                if let Some(client) = self.clients.get(&window) {
-                    let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index];
-                    if visible_tags != 0 {
-                        result.push(window);
-                    }
-                    current = client.next;
-                } else {
-                    break;
-                }
-            }
-        }
-        result
-    }
-
-    fn get_monitor_at_point(&self, x: i32, y: i32) -> Option<usize> {
-        self.monitors
-            .iter()
-            .position(|mon| mon.contains_point(x, y))
-    }
-
-    fn get_monitor_for_rect(&self, x: i32, y: i32, w: i32, h: i32) -> usize {
-        let mut best_monitor = self.selected_monitor;
-        let mut max_area = 0;
-
-        for (idx, monitor) in self.monitors.iter().enumerate() {
-            let intersect_width = 0.max(
-                (x + w).min(monitor.window_area_x + monitor.window_area_width)
-                    - x.max(monitor.window_area_x),
-            );
-            let intersect_height = 0.max(
-                (y + h).min(monitor.window_area_y + monitor.window_area_height)
-                    - y.max(monitor.window_area_y),
-            );
-            let area = intersect_width * intersect_height;
-
-            if area > max_area {
-                max_area = area;
-                best_monitor = idx;
-            }
-        }
-
-        best_monitor
-    }
-
-    fn move_window_to_monitor(
-        &mut self,
-        window: Window,
-        target_monitor_index: usize,
-    ) -> WmResult<()> {
-        let current_monitor_index = self.clients.get(&window).map(|c| c.monitor_index);
-
-        if let Some(current_idx) = current_monitor_index
-            && current_idx == target_monitor_index
-        {
-            return Ok(());
-        }
-
-        self.unfocus(window, false)?;
-        self.detach(window);
-        self.detach_stack(window);
-
-        if let Some(client) = self.clients.get_mut(&window) {
-            client.monitor_index = target_monitor_index;
-            if let Some(target_monitor) = self.monitors.get(target_monitor_index) {
-                client.tags = target_monitor.tagset[target_monitor.selected_tags_index];
-            }
-        }
-
-        self.attach_aside(window, target_monitor_index);
-        self.attach_stack(window, target_monitor_index);
-
-        self.focus(None)?;
-        self.apply_layout()?;
-
-        Ok(())
-    }
-
-    fn get_adjacent_monitor(&self, direction: i32) -> Option<usize> {
-        if self.monitors.len() <= 1 {
-            return None;
-        }
-
-        if direction > 0 {
-            if self.selected_monitor + 1 < self.monitors.len() {
-                Some(self.selected_monitor + 1)
-            } else {
-                Some(0)
-            }
-        } else if self.selected_monitor == 0 {
-            Some(self.monitors.len() - 1)
-        } else {
-            Some(self.selected_monitor - 1)
-        }
-    }
-
-    fn is_visible(&self, window: Window) -> bool {
-        let Some(client) = self.clients.get(&window) else {
-            return false;
-        };
-
-        let Some(monitor) = self.monitors.get(client.monitor_index) else {
-            return false;
-        };
-
-        (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0
-    }
-
-    fn showhide(&mut self, window: Option<Window>) -> WmResult<()> {
-        let Some(window) = window else {
-            return Ok(());
-        };
-
-        let Some(client) = self.clients.get(&window).cloned() else {
-            return Ok(());
-        };
-
-        let monitor = match self.monitors.get(client.monitor_index) {
-            Some(m) => m,
-            None => return Ok(()),
-        };
-
-        let is_visible = (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0;
-
-        if is_visible {
-            self.connection.configure_window(
-                window,
-                &ConfigureWindowAux::new()
-                    .x(client.x_position as i32)
-                    .y(client.y_position as i32),
-            )?;
-
-            let is_floating = client.is_floating;
-            let is_fullscreen = client.is_fullscreen;
-            let has_no_layout = self.layout.name() == LayoutType::Normie.as_str();
-
-            if (has_no_layout || is_floating) && !is_fullscreen {
-                let (x, y, w, h, changed) = self.apply_size_hints(
-                    window,
-                    client.x_position as i32,
-                    client.y_position as i32,
-                    client.width as i32,
-                    client.height as i32,
-                );
-                if changed {
-                    if let Some(c) = self.clients.get_mut(&window) {
-                        c.old_x_position = c.x_position;
-                        c.old_y_position = c.y_position;
-                        c.old_width = c.width;
-                        c.old_height = c.height;
-                        c.x_position = x as i16;
-                        c.y_position = y as i16;
-                        c.width = w as u16;
-                        c.height = h as u16;
-                    }
-                    self.connection.configure_window(
-                        window,
-                        &ConfigureWindowAux::new()
-                            .x(x)
-                            .y(y)
-                            .width(w as u32)
-                            .height(h as u32)
-                            .border_width(self.config.border_width),
-                    )?;
-                    self.send_configure_notify(window)?;
-                    self.connection.flush()?;
-                }
-            }
-
-            self.showhide(client.stack_next)?;
-        } else {
-            self.showhide(client.stack_next)?;
-
-            let width = client.width_with_border() as i32;
-            self.connection.configure_window(
-                window,
-                &ConfigureWindowAux::new()
-                    .x(width * -2)
-                    .y(client.y_position as i32),
-            )?;
-        }
-
-        Ok(())
-    }
-
-    pub fn view_tag(&mut self, tag_index: usize) -> WmResult<()> {
-        if tag_index >= self.config.tags.len() {
-            return Ok(());
-        }
-
-        let new_tagset = tag_mask(tag_index);
-        let mut layout_name: Option<String> = None;
-        let mut toggle_bar = false;
-
-        if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-            if new_tagset == monitor.tagset[monitor.selected_tags_index] {
-                if !self.config.tag_back_and_forth {
-                    return Ok(());
-                }
-                monitor.tagset.swap(0, 1);
-                if let Some(ref mut pertag) = monitor.pertag {
-                    let tmp = pertag.previous_tag;
-                    pertag.previous_tag = pertag.current_tag;
-                    pertag.current_tag = tmp;
-                }
-            } else {
-                monitor.selected_tags_index ^= 1;
-                monitor.tagset[monitor.selected_tags_index] = new_tagset;
-                if let Some(ref mut pertag) = monitor.pertag {
-                    pertag.previous_tag = pertag.current_tag;
-                    pertag.current_tag = tag_index + 1;
-                }
-            }
-
-            if let Some(ref pertag) = monitor.pertag {
-                monitor.num_master = pertag.num_masters[pertag.current_tag];
-                monitor.master_factor = pertag.master_factors[pertag.current_tag];
-                layout_name = Some(pertag.layouts[pertag.current_tag].clone());
-                if monitor.show_bar != pertag.show_bars[pertag.current_tag] {
-                    toggle_bar = true;
-                }
-            }
-        }
-
-        if let Some(name) = layout_name {
-            if let Ok(layout) = layout_from_str(&name) {
-                self.layout = layout;
-            }
-        }
-
-        if toggle_bar {
-            self.toggle_bar()?;
-        }
-
-        self.save_selected_tags()?;
-        self.focus(None)?;
-        self.apply_layout()?;
-        self.update_bar()?;
-
-        Ok(())
-    }
-
-    pub fn toggleview(&mut self, tag_index: usize) -> WmResult<()> {
-        if tag_index >= self.config.tags.len() {
-            return Ok(());
-        }
-
-        let num_tags = self.config.tags.len();
-        let all_tags_mask = (1u32 << num_tags) - 1;
-        let mut layout_name: Option<String> = None;
-        let mut toggle_bar = false;
-
-        if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-            let mask = tag_mask(tag_index);
-            let new_tagset = monitor.tagset[monitor.selected_tags_index] ^ mask;
-
-            if new_tagset == 0 {
-                return Ok(());
-            }
-
-            monitor.tagset[monitor.selected_tags_index] = new_tagset;
-
-            if let Some(ref mut pertag) = monitor.pertag {
-                if new_tagset == all_tags_mask {
-                    pertag.previous_tag = pertag.current_tag;
-                    pertag.current_tag = 0;
-                }
-
-                if pertag.current_tag > 0 && (new_tagset & (1 << (pertag.current_tag - 1))) == 0 {
-                    pertag.previous_tag = pertag.current_tag;
-                    pertag.current_tag = (new_tagset.trailing_zeros() as usize) + 1;
-                }
-
-                monitor.num_master = pertag.num_masters[pertag.current_tag];
-                monitor.master_factor = pertag.master_factors[pertag.current_tag];
-                layout_name = Some(pertag.layouts[pertag.current_tag].clone());
-                if monitor.show_bar != pertag.show_bars[pertag.current_tag] {
-                    toggle_bar = true;
-                }
-            }
-        }
-
-        if let Some(name) = layout_name {
-            if let Ok(layout) = layout_from_str(&name) {
-                self.layout = layout;
-            }
-        }
-
-        if toggle_bar {
-            self.toggle_bar()?;
-        }
-
-        self.save_selected_tags()?;
-        self.focus(None)?;
-        self.apply_layout()?;
-        self.update_bar()?;
-
-        Ok(())
-    }
-
-    fn save_selected_tags(&self) -> WmResult<()> {
-        let net_current_desktop = self.atoms.net_current_desktop;
-
-        let selected_tags = self
-            .monitors
-            .get(self.selected_monitor)
-            .map(|m| m.tagset[m.selected_tags_index])
-            .unwrap_or(tag_mask(0));
-        let desktop = selected_tags.trailing_zeros();
-
-        let bytes = (desktop as u32).to_ne_bytes();
-        self.connection.change_property(
-            PropMode::REPLACE,
-            self.root,
-            net_current_desktop,
-            AtomEnum::CARDINAL,
-            32,
-            1,
-            &bytes,
-        )?;
-
-        self.connection.flush()?;
-        Ok(())
-    }
-
-    pub fn move_to_tag(&mut self, tag_index: usize) -> WmResult<()> {
-        if tag_index >= self.config.tags.len() {
-            return Ok(());
-        }
-
-        let focused = match self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client)
-        {
-            Some(win) => win,
-            None => return Ok(()),
-        };
-
-        let mask = tag_mask(tag_index);
-
-        if let Some(client) = self.clients.get_mut(&focused) {
-            client.tags = mask;
-        }
-
-        if let Err(error) = self.save_client_tag(focused, mask) {
-            eprintln!("Failed to save client tag: {:?}", error);
-        }
-
-        self.focus(None)?;
-        self.apply_layout()?;
-        self.update_bar()?;
-
-        Ok(())
-    }
-
-    pub fn toggletag(&mut self, tag_index: usize) -> WmResult<()> {
-        if tag_index >= self.config.tags.len() {
-            return Ok(());
-        }
-
-        let focused = match self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client)
-        {
-            Some(win) => win,
-            None => return Ok(()),
-        };
-
-        let mask = tag_mask(tag_index);
-        let current_tags = self.clients.get(&focused).map(|c| c.tags).unwrap_or(0);
-        let new_tags = current_tags ^ mask;
-
-        if new_tags == 0 {
-            return Ok(());
-        }
-
-        if let Some(client) = self.clients.get_mut(&focused) {
-            client.tags = new_tags;
-        }
-
-        if let Err(error) = self.save_client_tag(focused, new_tags) {
-            eprintln!("Failed to save client tag: {:?}", error);
-        }
-
-        self.focus(None)?;
-        self.apply_layout()?;
-        self.update_bar()?;
-
-        Ok(())
-    }
-
-    pub fn cycle_focus(&mut self, direction: i32) -> WmResult<()> {
-        let visible = self.visible_windows();
-
-        if visible.is_empty() {
-            return Ok(());
-        }
-
-        let current = self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client);
-
-        let next_window = if let Some(current) = current {
-            if let Some(current_index) = visible.iter().position(|&w| w == current) {
-                let next_index = if direction > 0 {
-                    (current_index + 1) % visible.len()
-                } else {
-                    (current_index + visible.len() - 1) % visible.len()
-                };
-                visible[next_index]
-            } else {
-                visible[0]
-            }
-        } else {
-            visible[0]
-        };
-
-        let is_tabbed = self.layout.name() == "tabbed";
-        if is_tabbed {
-            self.connection.configure_window(
-                next_window,
-                &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
-            )?;
-        }
-
-        self.focus(Some(next_window))?;
-
-        if is_tabbed {
-            self.update_tab_bars()?;
-        }
-
-        Ok(())
-    }
-
-    fn grab_keys(&mut self) -> WmResult<()> {
-        self.keyboard_mapping = Some(keyboard::grab_keys(
-            &self.connection,
-            self.root,
-            &self.config.keybindings,
-            self.current_key,
-        )?);
-        Ok(())
-    }
-
-    fn kill_client(&self, window: Window) -> WmResult<()> {
-        if self.send_event(window, self.atoms.wm_delete_window)? {
-            self.connection.flush()?;
-        } else {
-            eprintln!(
-                "Window {} doesn't support WM_DELETE_WINDOW, killing forcefully",
-                window
-            );
-            self.connection.kill_client(window)?;
-            self.connection.flush()?;
-        }
-        Ok(())
-    }
-
-    fn send_event(&self, window: Window, protocol: Atom) -> WmResult<bool> {
-        let protocols_reply = self
-            .connection
-            .get_property(
-                false,
-                window,
-                self.atoms.wm_protocols,
-                AtomEnum::ATOM,
-                0,
-                100,
-            )?
-            .reply();
-
-        let protocols_reply = match protocols_reply {
-            Ok(reply) => reply,
-            Err(_) => return Ok(false),
-        };
-
-        let protocols: Vec<Atom> = protocols_reply
-            .value
-            .chunks_exact(4)
-            .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
-            .collect();
-
-        if !protocols.contains(&protocol) {
-            return Ok(false);
-        }
-
-        let event = x11rb::protocol::xproto::ClientMessageEvent {
-            response_type: x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT,
-            format: 32,
-            sequence: 0,
-            window,
-            type_: self.atoms.wm_protocols,
-            data: x11rb::protocol::xproto::ClientMessageData::from([
-                protocol,
-                x11rb::CURRENT_TIME,
-                0,
-                0,
-                0,
-            ]),
-        };
-
-        self.connection
-            .send_event(false, window, EventMask::NO_EVENT, event)?;
-        self.connection.flush()?;
-        Ok(true)
-    }
-
-    fn set_urgent(&mut self, window: Window, urgent: bool) -> WmResult<()> {
-        if let Some(client) = self.clients.get_mut(&window) {
-            client.is_urgent = urgent;
-        }
-
-        let hints_reply = self
-            .connection
-            .get_property(false, window, AtomEnum::WM_HINTS, AtomEnum::WM_HINTS, 0, 9)?
-            .reply();
-
-        if let Ok(hints) = hints_reply
-            && hints.value.len() >= 4
-        {
-            let mut flags = u32::from_ne_bytes([
-                hints.value[0],
-                hints.value[1],
-                hints.value[2],
-                hints.value[3],
-            ]);
-
-            if urgent {
-                flags |= 256;
-            } else {
-                flags &= !256;
-            }
-
-            let mut new_hints = hints.value.clone();
-            new_hints[0..4].copy_from_slice(&flags.to_ne_bytes());
-
-            self.connection.change_property(
-                PropMode::REPLACE,
-                window,
-                AtomEnum::WM_HINTS,
-                AtomEnum::WM_HINTS,
-                32,
-                new_hints.len() as u32 / 4,
-                &new_hints,
-            )?;
-        }
-
-        Ok(())
-    }
-
-    fn get_window_atom_property(&self, window: Window, property: Atom) -> WmResult<Option<Atom>> {
-        let reply = self
-            .connection
-            .get_property(false, window, property, AtomEnum::ATOM, 0, 1)?
-            .reply();
-
-        match reply {
-            Ok(prop) if !prop.value.is_empty() && prop.value.len() >= 4 => {
-                let atom = u32::from_ne_bytes([
-                    prop.value[0],
-                    prop.value[1],
-                    prop.value[2],
-                    prop.value[3],
-                ]);
-                Ok(Some(atom))
-            }
-            _ => Ok(None),
-        }
-    }
-
-    fn get_window_atom_list_property(&self, window: Window, property: Atom) -> WmResult<Vec<Atom>> {
-        let reply = self
-            .connection
-            .get_property(false, window, property, AtomEnum::ATOM, 0, 32)?
-            .reply();
-
-        match reply {
-            Ok(prop) if !prop.value.is_empty() => {
-                let atoms: Vec<Atom> = prop
-                    .value
-                    .chunks_exact(4)
-                    .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
-                    .collect();
-                Ok(atoms)
-            }
-            _ => Ok(Vec::new()),
-        }
-    }
-
-    fn fullscreen(&mut self) -> WmResult<()> {
-        let Some(focused_window) = self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client)
-        else {
-            return Ok(());
-        };
-
-        let is_fullscreen = self.fullscreen_windows.contains(&focused_window);
-        self.set_window_fullscreen(focused_window, !is_fullscreen)?;
-        Ok(())
-    }
-
-    fn set_window_fullscreen(&mut self, window: Window, fullscreen: bool) -> WmResult<()> {
-        let monitor_idx = self
-            .clients
-            .get(&window)
-            .map(|c| c.monitor_index)
-            .unwrap_or(self.selected_monitor);
-        let monitor = &self.monitors[monitor_idx];
-
-        if fullscreen && !self.fullscreen_windows.contains(&window) {
-            let bytes = self.atoms.net_wm_state_fullscreen.to_ne_bytes().to_vec();
-            self.connection.change_property(
-                PropMode::REPLACE,
-                window,
-                self.atoms.net_wm_state,
-                AtomEnum::ATOM,
-                32,
-                1,
-                &bytes,
-            )?;
-
-            if let Some(client) = self.clients.get_mut(&window) {
-                client.is_fullscreen = true;
-                client.old_state = client.is_floating;
-                client.old_border_width = client.border_width;
-                client.old_x_position = client.x_position;
-                client.old_y_position = client.y_position;
-                client.old_width = client.width;
-                client.old_height = client.height;
-                client.border_width = 0;
-                client.is_floating = true;
-            }
-
-            self.fullscreen_windows.insert(window);
-            self.floating_windows.insert(window);
-
-            self.connection.configure_window(
-                window,
-                &x11rb::protocol::xproto::ConfigureWindowAux::new()
-                    .border_width(0)
-                    .x(monitor.screen_x)
-                    .y(monitor.screen_y)
-                    .width(monitor.screen_width as u32)
-                    .height(monitor.screen_height as u32)
-                    .stack_mode(x11rb::protocol::xproto::StackMode::ABOVE),
-            )?;
-
-            self.connection.flush()?;
-        } else if !fullscreen && self.fullscreen_windows.contains(&window) {
-            self.connection.change_property(
-                PropMode::REPLACE,
-                window,
-                self.atoms.net_wm_state,
-                AtomEnum::ATOM,
-                32,
-                0,
-                &[],
-            )?;
-
-            self.fullscreen_windows.remove(&window);
-
-            let (
-                was_floating,
-                restored_x,
-                restored_y,
-                restored_width,
-                restored_height,
-                restored_border,
-            ) = self
-                .clients
-                .get(&window)
-                .map(|client| {
-                    (
-                        client.old_state,
-                        client.old_x_position,
-                        client.old_y_position,
-                        client.old_width,
-                        client.old_height,
-                        client.old_border_width,
-                    )
-                })
-                .unwrap_or((false, 0, 0, 100, 100, 0));
-
-            if !was_floating {
-                self.floating_windows.remove(&window);
-            }
-
-            if let Some(client) = self.clients.get_mut(&window) {
-                client.is_fullscreen = false;
-                client.is_floating = client.old_state;
-                client.border_width = client.old_border_width;
-                client.x_position = client.old_x_position;
-                client.y_position = client.old_y_position;
-                client.width = client.old_width;
-                client.height = client.old_height;
-            }
-
-            self.connection.configure_window(
-                window,
-                &ConfigureWindowAux::new()
-                    .x(restored_x as i32)
-                    .y(restored_y as i32)
-                    .width(restored_width as u32)
-                    .height(restored_height as u32)
-                    .border_width(restored_border as u32),
-            )?;
-
-            self.apply_layout()?;
-        }
-
-        Ok(())
-    }
-
-    fn get_transient_parent(&self, window: Window) -> Option<Window> {
-        self.connection
-            .get_property(
-                false,
-                window,
-                AtomEnum::WM_TRANSIENT_FOR,
-                AtomEnum::WINDOW,
-                0,
-                1,
-            )
-            .ok()
-            .and_then(|cookie| cookie.reply().ok())
-            .filter(|reply| !reply.value.is_empty())
-            .and_then(|reply| {
-                if reply.value.len() >= 4 {
-                    let parent_window = u32::from_ne_bytes([
-                        reply.value[0],
-                        reply.value[1],
-                        reply.value[2],
-                        reply.value[3],
-                    ]);
-                    Some(parent_window)
-                } else {
-                    None
-                }
-            })
-    }
-
-    fn get_window_class_instance(&self, window: Window) -> (String, String) {
-        let reply = self
-            .connection
-            .get_property(false, window, AtomEnum::WM_CLASS, AtomEnum::STRING, 0, 1024)
-            .ok()
-            .and_then(|cookie| cookie.reply().ok());
-
-        if let Some(reply) = reply
-            && !reply.value.is_empty()
-            && let Ok(text) = std::str::from_utf8(&reply.value)
-        {
-            let parts: Vec<&str> = text.split('\0').collect();
-            let instance = parts.first().unwrap_or(&"").to_string();
-            let class = parts.get(1).unwrap_or(&"").to_string();
-            return (instance, class);
-        }
-
-        (String::new(), String::new())
-    }
-
-    fn apply_rules(&mut self, window: Window) -> WmResult<()> {
-        let (instance, class) = self.get_window_class_instance(window);
-        let title = self
-            .clients
-            .get(&window)
-            .map(|c| c.name.clone())
-            .unwrap_or_default();
-
-        let mut rule_tags: Option<u32> = None;
-        let mut rule_floating: Option<bool> = None;
-        let mut rule_monitor: Option<usize> = None;
-        let mut rule_focus = false;
-
-        for rule in &self.config.window_rules {
-            if rule.matches(&class, &instance, &title) {
-                if rule.tags.is_some() {
-                    rule_tags = rule.tags;
-                }
-                if rule.is_floating.is_some() {
-                    rule_floating = rule.is_floating;
-                }
-                if rule.monitor.is_some() {
-                    rule_monitor = rule.monitor;
-                }
-                rule_focus = rule.focus.unwrap_or(false);
-            }
-        }
-
-        if let Some(client) = self.clients.get_mut(&window) {
-            if let Some(is_floating) = rule_floating {
-                client.is_floating = is_floating;
-                if is_floating {
-                    self.floating_windows.insert(window);
-                } else {
-                    self.floating_windows.remove(&window);
-                }
-            }
-
-            if let Some(monitor_index) = rule_monitor
-                && monitor_index < self.monitors.len()
-            {
-                client.monitor_index = monitor_index;
-            }
-
-            if let Some(tags) = rule_tags {
-                client.tags = tags;
-
-                if rule_focus {
-                    let tag_index = unmask_tag(tags);
-                    let monitor_tagset = self
-                        .monitors
-                        .get(client.monitor_index)
-                        .map(|monitor| monitor.get_selected_tag())
-                        .unwrap_or(tag_mask(0));
-                    let is_tag_focused = monitor_tagset & tags == tags;
-
-                    if !is_tag_focused {
-                        self.view_tag(tag_index)?;
-                    }
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    fn manage_window(&mut self, window: Window) -> WmResult<()> {
-        let geometry = self.connection.get_geometry(window)?.reply()?;
-        let border_width = self.config.border_width;
-
-        let transient_parent = self.get_transient_parent(window);
-        let is_transient = transient_parent.is_some();
-
-        let (monitor_index, tags) = if let Some(parent) = transient_parent {
-            if let Some(parent_client) = self.clients.get(&parent) {
-                (parent_client.monitor_index, parent_client.tags)
-            } else {
-                let tags = self
-                    .monitors
-                    .get(self.selected_monitor)
-                    .map(|monitor| monitor.tagset[monitor.selected_tags_index])
-                    .unwrap_or(tag_mask(0));
-                (self.selected_monitor, tags)
-            }
-        } else {
-            let tags = self
-                .monitors
-                .get(self.selected_monitor)
-                .map(|monitor| monitor.tagset[monitor.selected_tags_index])
-                .unwrap_or(tag_mask(0));
-            (self.selected_monitor, tags)
-        };
-
-        let mut client = Client::new(window, monitor_index, tags);
-        client.x_position = geometry.x;
-        client.y_position = geometry.y;
-        client.width = geometry.width;
-        client.height = geometry.height;
-        client.old_x_position = geometry.x;
-        client.old_y_position = geometry.y;
-        client.old_width = geometry.width;
-        client.old_height = geometry.height;
-        client.old_border_width = geometry.border_width;
-        client.border_width = border_width as u16;
-
-        self.clients.insert(window, client);
-        self.update_window_title(window)?;
-
-        if !is_transient {
-            self.apply_rules(window)?;
-        }
-
-        let client_monitor = self
-            .clients
-            .get(&window)
-            .map(|c| c.monitor_index)
-            .unwrap_or(monitor_index);
-        let monitor = &self.monitors[client_monitor];
-
-        let mut x = self
-            .clients
-            .get(&window)
-            .map(|c| c.x_position as i32)
-            .unwrap_or(0);
-        let mut y = self
-            .clients
-            .get(&window)
-            .map(|c| c.y_position as i32)
-            .unwrap_or(0);
-        let w = self
-            .clients
-            .get(&window)
-            .map(|c| c.width as i32)
-            .unwrap_or(1);
-        let h = self
-            .clients
-            .get(&window)
-            .map(|c| c.height as i32)
-            .unwrap_or(1);
-        let bw = border_width as i32;
-
-        if x + w + 2 * bw > monitor.window_area_x + monitor.window_area_width {
-            x = monitor.window_area_x + monitor.window_area_width - w - 2 * bw;
-        }
-        if y + h + 2 * bw > monitor.window_area_y + monitor.window_area_height {
-            y = monitor.window_area_y + monitor.window_area_height - h - 2 * bw;
-        }
-        x = x.max(monitor.window_area_x);
-        y = y.max(monitor.window_area_y);
-
-        if let Some(c) = self.clients.get_mut(&window) {
-            c.x_position = x as i16;
-            c.y_position = y as i16;
-        }
-
-        self.connection.configure_window(
-            window,
-            &ConfigureWindowAux::new().border_width(border_width),
-        )?;
-        self.connection.change_window_attributes(
-            window,
-            &ChangeWindowAttributesAux::new().border_pixel(self.config.border_unfocused),
-        )?;
-        self.send_configure_notify(window)?;
-        self.update_window_type(window)?;
-        self.update_size_hints(window)?;
-        self.update_window_hints(window)?;
-
-        self.connection.change_window_attributes(
-            window,
-            &ChangeWindowAttributesAux::new().event_mask(
-                EventMask::ENTER_WINDOW
-                    | EventMask::FOCUS_CHANGE
-                    | EventMask::PROPERTY_CHANGE
-                    | EventMask::STRUCTURE_NOTIFY,
-            ),
-        )?;
-
-        let is_fixed = self
-            .clients
-            .get(&window)
-            .map(|c| c.is_fixed)
-            .unwrap_or(false);
-        if let Some(c) = self.clients.get_mut(&window)
-            && !c.is_floating
-        {
-            c.is_floating = is_transient || is_fixed;
-            c.old_state = c.is_floating;
-        }
-
-        if self
-            .clients
-            .get(&window)
-            .map(|c| c.is_floating)
-            .unwrap_or(false)
-        {
-            self.floating_windows.insert(window);
-            self.connection.configure_window(
-                window,
-                &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
-            )?;
-        }
-
-        if self.layout.name() == "scrolling" {
-            if let Some(selected) = self
-                .monitors
-                .get(client_monitor)
-                .and_then(|m| m.selected_client)
-            {
-                self.attach_after(window, selected, client_monitor);
-            } else {
-                self.attach_aside(window, client_monitor);
-            }
-        } else {
-            self.attach_aside(window, client_monitor);
-        }
-        self.attach_stack(window, client_monitor);
-        self.windows.push(window);
-
-        let off_screen_x = x + 2 * self.screen.width_in_pixels as i32;
-        self.connection.configure_window(
-            window,
-            &ConfigureWindowAux::new()
-                .x(off_screen_x)
-                .y(y)
-                .width(w as u32)
-                .height(h as u32),
-        )?;
-
-        self.set_wm_state(window, 1)?;
-        self.update_client_list()?;
-
-        let final_tags = self.clients.get(&window).map(|c| c.tags).unwrap_or(tags);
-        let _ = self.save_client_tag(window, final_tags);
-
-        if client_monitor == self.selected_monitor
-            && let Some(old_sel) = self
-                .monitors
-                .get(self.selected_monitor)
-                .and_then(|m| m.selected_client)
-        {
-            self.unfocus(old_sel, false)?;
-        }
-
-        if let Some(m) = self.monitors.get_mut(client_monitor) {
-            m.selected_client = Some(window);
-        }
-
-        if self.layout.name() == "scrolling" {
-            self.scroll_to_window(window, true)?;
-        }
-
-        self.apply_layout()?;
-        self.connection.map_window(window)?;
-        self.focus(None)?;
-        self.update_bar()?;
-
-        if self.layout.name() == "tabbed" {
-            self.update_tab_bars()?;
-        }
-
-        Ok(())
-    }
-
-    pub fn set_focus(&mut self, window: Window) -> WmResult<()> {
-        let never_focus = self
-            .clients
-            .get(&window)
-            .map(|c| c.never_focus)
-            .unwrap_or(false);
-
-        if !never_focus {
-            self.connection.set_input_focus(
-                InputFocus::POINTER_ROOT,
-                window,
-                x11rb::CURRENT_TIME,
-            )?;
-
-            self.connection.change_property(
-                PropMode::REPLACE,
-                self.root,
-                self.atoms.net_active_window,
-                AtomEnum::WINDOW,
-                32,
-                1,
-                &window.to_ne_bytes(),
-            )?;
-        }
-
-        let _ = self.send_event(window, self.atoms.wm_take_focus);
-        self.connection.flush()?;
-
-        Ok(())
-    }
-
-    fn grabbuttons(&self, window: Window, focused: bool) -> WmResult<()> {
-        self.connection
-            .ungrab_button(ButtonIndex::ANY, window, ModMask::ANY)?;
-
-        if !focused {
-            self.connection.grab_button(
-                false,
-                window,
-                EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE,
-                GrabMode::SYNC,
-                GrabMode::SYNC,
-                x11rb::NONE,
-                x11rb::NONE,
-                ButtonIndex::ANY,
-                ModMask::ANY,
-            )?;
-        }
-
-        let ignore_modifiers = [
-            0u16,
-            u16::from(ModMask::LOCK),
-            u16::from(ModMask::M2),
-            u16::from(ModMask::LOCK | ModMask::M2),
-        ];
-
-        for &ignore_mask in &ignore_modifiers {
-            let grab_mask = u16::from(self.config.modkey) | ignore_mask;
-
-            self.connection.grab_button(
-                false,
-                window,
-                EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE,
-                GrabMode::ASYNC,
-                GrabMode::SYNC,
-                x11rb::NONE,
-                x11rb::NONE,
-                ButtonIndex::M1,
-                grab_mask.into(),
-            )?;
-
-            self.connection.grab_button(
-                false,
-                window,
-                EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE,
-                GrabMode::ASYNC,
-                GrabMode::SYNC,
-                x11rb::NONE,
-                x11rb::NONE,
-                ButtonIndex::M3,
-                grab_mask.into(),
-            )?;
-        }
-
-        Ok(())
-    }
-
-    fn unfocus(&self, window: Window, reset_input_focus: bool) -> WmResult<()> {
-        if !self.windows.contains(&window) {
-            return Ok(());
-        }
-
-        self.grabbuttons(window, false)?;
-
-        self.connection.change_window_attributes(
-            window,
-            &ChangeWindowAttributesAux::new().border_pixel(self.config.border_unfocused),
-        )?;
-
-        if reset_input_focus {
-            self.connection.set_input_focus(
-                InputFocus::POINTER_ROOT,
-                self.root,
-                x11rb::CURRENT_TIME,
-            )?;
-            self.connection
-                .delete_property(self.root, self.atoms.net_active_window)?;
-        }
-
-        Ok(())
-    }
-
-    fn focus(&mut self, window: Option<Window>) -> WmResult<()> {
-        let old_selected = self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client);
-
-        let mut focus_client = window;
-        if focus_client.is_none() || focus_client.is_some_and(|w| !self.is_visible(w)) {
-            let mut current = self
-                .monitors
-                .get(self.selected_monitor)
-                .and_then(|m| m.stack_head);
-
-            focus_client = None;
-            while let Some(w) = current {
-                if self.is_visible(w) {
-                    focus_client = Some(w);
-                    break;
-                }
-                current = self.clients.get(&w).and_then(|c| c.stack_next);
-            }
-        }
-
-        if old_selected != focus_client {
-            if let Some(old_win) = old_selected {
-                self.unfocus(old_win, false)?;
-            }
-        }
-
-        if let Some(win) = focus_client {
-            let monitor_idx = self
-                .clients
-                .get(&win)
-                .map(|c| c.monitor_index)
-                .unwrap_or(self.selected_monitor);
-
-            if monitor_idx != self.selected_monitor {
-                self.selected_monitor = monitor_idx;
-            }
-
-            if self.clients.get(&win).is_some_and(|c| c.is_urgent) {
-                self.set_urgent(win, false)?;
-            }
-
-            self.detach_stack(win);
-            self.attach_stack(win, monitor_idx);
-
-            self.grabbuttons(win, true)?;
-
-            self.connection.change_window_attributes(
-                win,
-                &ChangeWindowAttributesAux::new().border_pixel(self.config.border_focused),
-            )?;
-
-            let never_focus = self
-                .clients
-                .get(&win)
-                .map(|client| client.never_focus)
-                .unwrap_or(false);
-
-            if !never_focus {
-                self.connection.set_input_focus(
-                    InputFocus::POINTER_ROOT,
-                    win,
-                    x11rb::CURRENT_TIME,
-                )?;
-
-                self.connection.change_property(
-                    PropMode::REPLACE,
-                    self.root,
-                    self.atoms.net_active_window,
-                    AtomEnum::WINDOW,
-                    32,
-                    1,
-                    &win.to_ne_bytes(),
-                )?;
-            }
-
-            let _ = self.send_event(win, self.atoms.wm_take_focus);
-
-            if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-                monitor.selected_client = Some(win);
-            }
-
-            self.previous_focused = Some(win);
-        } else {
-            self.connection.set_input_focus(
-                InputFocus::POINTER_ROOT,
-                self.root,
-                x11rb::CURRENT_TIME,
-            )?;
-
-            self.connection
-                .delete_property(self.root, self.atoms.net_active_window)?;
-
-            if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-                monitor.selected_client = None;
-            }
-        }
-
-        self.connection.flush()?;
-
-        Ok(())
-    }
-
-    fn restack(&mut self) -> WmResult<()> {
-        let monitor = match self.monitors.get(self.selected_monitor) {
-            Some(m) => m,
-            None => return Ok(()),
-        };
-
-        let mut windows_to_restack: Vec<Window> = Vec::new();
-
-        if let Some(selected) = monitor.selected_client
-            && self.floating_windows.contains(&selected)
-        {
-            windows_to_restack.push(selected);
-        }
-
-        let mut current = monitor.stack_head;
-        while let Some(win) = current {
-            if self.windows.contains(&win)
-                && self.floating_windows.contains(&win)
-                && Some(win) != monitor.selected_client
-            {
-                windows_to_restack.push(win);
-            }
-            current = self.clients.get(&win).and_then(|c| c.stack_next);
-        }
-
-        current = monitor.stack_head;
-        while let Some(win) = current {
-            if self.windows.contains(&win) && !self.floating_windows.contains(&win) {
-                windows_to_restack.push(win);
-            }
-            current = self.clients.get(&win).and_then(|c| c.stack_next);
-        }
-
-        for (i, &win) in windows_to_restack.iter().enumerate() {
-            if i == 0 {
-                self.connection.configure_window(
-                    win,
-                    &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
-                )?;
-            } else {
-                self.connection.configure_window(
-                    win,
-                    &ConfigureWindowAux::new()
-                        .sibling(windows_to_restack[i - 1])
-                        .stack_mode(StackMode::BELOW),
-                )?;
-            }
-        }
-
-        Ok(())
-    }
-
-    fn focusstack(&mut self, direction: i32) -> WmResult<()> {
-        let monitor = match self.monitors.get(self.selected_monitor) {
-            Some(monitor) => monitor,
-            None => return Ok(()),
-        };
-
-        let selected_window = match monitor.selected_client {
-            Some(window) => window,
-            None => return Ok(()),
-        };
-
-        let selected_is_fullscreen = self
-            .clients
-            .get(&selected_window)
-            .map(|client| client.is_fullscreen)
-            .unwrap_or(false);
-
-        if selected_is_fullscreen {
-            return Ok(());
-        }
-
-        let selected_tags = monitor.tagset[monitor.selected_tags_index];
-
-        let mut stack_windows: Vec<Window> = Vec::new();
-        let mut current_window = monitor.clients_head;
-        while let Some(window) = current_window {
-            if let Some(client) = self.clients.get(&window) {
-                if client.tags & selected_tags != 0 && !client.is_floating {
-                    stack_windows.push(window);
-                }
-                current_window = client.next;
-            } else {
-                break;
-            }
-        }
-
-        if stack_windows.is_empty() {
-            return Ok(());
-        }
-
-        let current_index = stack_windows
-            .iter()
-            .position(|&window| window == selected_window);
-
-        let next_window = if let Some(index) = current_index {
-            if direction > 0 {
-                if index + 1 < stack_windows.len() {
-                    stack_windows[index + 1]
-                } else {
-                    stack_windows[0]
-                }
-            } else if index > 0 {
-                stack_windows[index - 1]
-            } else {
-                stack_windows[stack_windows.len() - 1]
-            }
-        } else {
-            return Ok(());
-        };
-
-        self.focus(Some(next_window))?;
-
-        if self.layout.name() == "scrolling" {
-            self.scroll_to_window(next_window, true)?;
-        }
-
-        self.restack()?;
-        self.update_tab_bars()?;
-
-        Ok(())
-    }
-
-    pub fn move_stack(&mut self, direction: i32) -> WmResult<()> {
-        let monitor_index = self.selected_monitor;
-        let monitor = match self.monitors.get(monitor_index) {
-            Some(m) => m.clone(),
-            None => return Ok(()),
-        };
-
-        let selected = match monitor.selected_client {
-            Some(win) => win,
-            None => return Ok(()),
-        };
-
-        let selected_client = match self.clients.get(&selected) {
-            Some(c) => c,
-            None => return Ok(()),
-        };
-
-        let target = if direction > 0 {
-            let next = self.next_tiled(selected_client.next, &monitor);
-            if next.is_some() {
-                next
-            } else {
-                self.next_tiled(monitor.clients_head, &monitor)
-            }
-        } else {
-            let mut previous = None;
-            let mut current = monitor.clients_head;
-            while let Some(window) = current {
-                if window == selected {
-                    break;
-                }
-                if let Some(client) = self.clients.get(&window) {
-                    let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index];
-                    if visible_tags != 0 && !client.is_floating {
-                        previous = Some(window);
-                    }
-                    current = client.next;
-                } else {
-                    break;
-                }
-            }
-            if previous.is_none() {
-                let mut last = None;
-                let mut current = monitor.clients_head;
-                while let Some(window) = current {
-                    if let Some(client) = self.clients.get(&window) {
-                        let visible_tags =
-                            client.tags & monitor.tagset[monitor.selected_tags_index];
-                        if visible_tags != 0 && !client.is_floating {
-                            last = Some(window);
-                        }
-                        current = client.next;
-                    } else {
-                        break;
-                    }
-                }
-                last
-            } else {
-                previous
-            }
-        };
-
-        let target = match target {
-            Some(t) if t != selected => t,
-            _ => return Ok(()),
-        };
-
-        let mut prev_selected = None;
-        let mut prev_target = None;
-        let mut current = monitor.clients_head;
-
-        while let Some(window) = current {
-            if let Some(client) = self.clients.get(&window) {
-                if client.next == Some(selected) {
-                    prev_selected = Some(window);
-                }
-                if client.next == Some(target) {
-                    prev_target = Some(window);
-                }
-                current = client.next;
-            } else {
-                break;
-            }
-        }
-
-        let selected_next = self.clients.get(&selected).and_then(|c| c.next);
-        let target_next = self.clients.get(&target).and_then(|c| c.next);
-
-        let temp = if selected_next == Some(target) {
-            Some(selected)
-        } else {
-            selected_next
-        };
-
-        if let Some(client) = self.clients.get_mut(&selected) {
-            client.next = if target_next == Some(selected) {
-                Some(target)
-            } else {
-                target_next
-            };
-        }
-
-        if let Some(client) = self.clients.get_mut(&target) {
-            client.next = temp;
-        }
-
-        if let Some(prev) = prev_selected
-            && prev != target
-            && let Some(client) = self.clients.get_mut(&prev)
-        {
-            client.next = Some(target);
-        }
-
-        if let Some(prev) = prev_target
-            && prev != selected
-            && let Some(client) = self.clients.get_mut(&prev)
-        {
-            client.next = Some(selected);
-        }
-
-        if let Some(monitor) = self.monitors.get_mut(monitor_index) {
-            if monitor.clients_head == Some(selected) {
-                monitor.clients_head = Some(target);
-            } else if monitor.clients_head == Some(target) {
-                monitor.clients_head = Some(selected);
-            }
-        }
-
-        self.apply_layout()?;
-        Ok(())
-    }
-
-    pub fn focus_monitor(&mut self, direction: i32) -> WmResult<()> {
-        if self.monitors.len() <= 1 {
-            return Ok(());
-        }
-
-        let target_monitor = match self.get_adjacent_monitor(direction) {
-            Some(idx) if idx != self.selected_monitor => idx,
-            _ => return Ok(()),
-        };
-
-        let old_selected = self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client);
-
-        if let Some(win) = old_selected {
-            self.unfocus(win, true)?;
-        }
-
-        self.selected_monitor = target_monitor;
-        self.focus(None)?;
-
-        Ok(())
-    }
-
-    pub fn send_window_to_adjacent_monitor(&mut self, direction: i32) -> WmResult<()> {
-        if self.monitors.len() <= 1 {
-            return Ok(());
-        }
-
-        let selected_window = self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client);
-
-        let window = match selected_window {
-            Some(win) => win,
-            None => return Ok(()),
-        };
-
-        let target_monitor = match self.get_adjacent_monitor(direction) {
-            Some(idx) => idx,
-            None => return Ok(()),
-        };
-
-        self.move_window_to_monitor(window, target_monitor)?;
-
-        Ok(())
-    }
-
-    fn drag_window(&mut self, window: Window) -> WmResult<()> {
-        let is_fullscreen = self
-            .clients
-            .get(&window)
-            .map(|c| c.is_fullscreen)
-            .unwrap_or(false);
-
-        if is_fullscreen {
-            return Ok(());
-        }
-
-        let client_info = self.clients.get(&window).map(|c| {
-            (
-                c.x_position,
-                c.y_position,
-                c.width,
-                c.height,
-                c.is_floating,
-                c.monitor_index,
-            )
-        });
-
-        let Some((orig_x, orig_y, width, height, was_floating, monitor_idx)) = client_info else {
-            return Ok(());
-        };
-
-        let monitor = self.monitors.get(monitor_idx).cloned();
-        let Some(monitor) = monitor else {
-            return Ok(());
-        };
-
-        let snap = 32;
-        let is_normie = self.layout.name() == "normie";
-
-        if !was_floating && !is_normie {
-            self.toggle_floating()?;
-        }
-
-        self.connection
-            .grab_pointer(
-                false,
-                self.root,
-                EventMask::POINTER_MOTION | EventMask::BUTTON_RELEASE | EventMask::BUTTON_PRESS,
-                GrabMode::ASYNC,
-                GrabMode::ASYNC,
-                x11rb::NONE,
-                x11rb::NONE,
-                x11rb::CURRENT_TIME,
-            )?
-            .reply()?;
-
-        let pointer = self.connection.query_pointer(self.root)?.reply()?;
-        let (start_x, start_y) = (pointer.root_x as i32, pointer.root_y as i32);
-
-        let mut last_time = 0u32;
-
-        loop {
-            let event = self.connection.wait_for_event()?;
-            match event {
-                Event::ConfigureRequest(_) | Event::MapRequest(_) | Event::Expose(_) => {}
-                Event::MotionNotify(e) => {
-                    if e.time.wrapping_sub(last_time) <= 16 {
-                        continue;
-                    }
-                    last_time = e.time;
-
-                    let mut new_x = orig_x as i32 + (e.root_x as i32 - start_x);
-                    let mut new_y = orig_y as i32 + (e.root_y as i32 - start_y);
-
-                    if (monitor.window_area_x - new_x).abs() < snap {
-                        new_x = monitor.window_area_x;
-                    } else if ((monitor.window_area_x + monitor.window_area_width)
-                        - (new_x + width as i32))
-                        .abs()
-                        < snap
-                    {
-                        new_x = monitor.window_area_x + monitor.window_area_width - width as i32;
-                    }
-
-                    if (monitor.window_area_y - new_y).abs() < snap {
-                        new_y = monitor.window_area_y;
-                    } else if ((monitor.window_area_y + monitor.window_area_height)
-                        - (new_y + height as i32))
-                        .abs()
-                        < snap
-                    {
-                        new_y = monitor.window_area_y + monitor.window_area_height - height as i32;
-                    }
-
-                    let should_resize = is_normie
-                        || self
-                            .clients
-                            .get(&window)
-                            .map(|c| c.is_floating)
-                            .unwrap_or(false);
-
-                    if should_resize {
-                        if let Some(client) = self.clients.get_mut(&window) {
-                            client.x_position = new_x as i16;
-                            client.y_position = new_y as i16;
-                        }
-
-                        self.connection.configure_window(
-                            window,
-                            &ConfigureWindowAux::new().x(new_x).y(new_y),
-                        )?;
-                        self.connection.flush()?;
-                    }
-                }
-                Event::ButtonRelease(_) => break,
-                _ => {}
-            }
-        }
-
-        self.connection
-            .ungrab_pointer(x11rb::CURRENT_TIME)?
-            .check()?;
-
-        let final_client = self
-            .clients
-            .get(&window)
-            .map(|c| (c.x_position, c.y_position, c.width, c.height));
-
-        if let Some((x, y, w, h)) = final_client {
-            let new_monitor = self.get_monitor_for_rect(x as i32, y as i32, w as i32, h as i32);
-            if new_monitor != monitor_idx {
-                self.move_window_to_monitor(window, new_monitor)?;
-                self.selected_monitor = new_monitor;
-                self.focus(None)?;
-            }
-        }
-
-        if self.config.auto_tile && !was_floating && !is_normie {
-            let drop_monitor_idx = self
-                .clients
-                .get(&window)
-                .map(|c| c.monitor_index)
-                .unwrap_or(monitor_idx);
-
-            if let Some((x, y, w, h)) = final_client {
-                let center = (x as i32 + w as i32 / 2, y as i32 + h as i32 / 2);
-                if let Some(target) = self.tiled_window_at(window, drop_monitor_idx, center) {
-                    self.detach(window);
-                    self.insert_before(window, target, drop_monitor_idx);
-                }
-            }
-
-            self.floating_windows.remove(&window);
-            if let Some(client) = self.clients.get_mut(&window) {
-                client.is_floating = false;
-            }
-            self.apply_layout()?;
-        }
-
-        Ok(())
-    }
-
-    fn tiled_window_at(
-        &self,
-        exclude: Window,
-        monitor_idx: usize,
-        (px, py): (i32, i32),
-    ) -> Option<Window> {
-        let monitor = self.monitors.get(monitor_idx)?;
-        let tags = monitor.tagset[monitor.selected_tags_index];
-        let mut current = monitor.clients_head;
-
-        while let Some(win) = current {
-            let c = self.clients.get(&win)?;
-            current = c.next;
-
-            if win == exclude || c.is_floating || (c.tags & tags) == 0 {
-                continue;
-            }
-
-            let (x, y) = (c.x_position as i32, c.y_position as i32);
-            let (w, h) = (
-                c.width as i32 + c.border_width as i32 * 2,
-                c.height as i32 + c.border_width as i32 * 2,
-            );
-
-            if px >= x && px < x + w && py >= y && py < y + h {
-                return Some(win);
-            }
-        }
-        None
-    }
-
-    fn insert_before(&mut self, window: Window, target: Window, monitor_idx: usize) {
-        let Some(monitor) = self.monitors.get_mut(monitor_idx) else {
-            return;
-        };
-
-        if monitor.clients_head == Some(target) {
-            if let Some(c) = self.clients.get_mut(&window) {
-                c.next = Some(target);
-            }
-            monitor.clients_head = Some(window);
-            return;
-        }
-
-        let mut current = monitor.clients_head;
-        while let Some(w) = current {
-            let Some(c) = self.clients.get(&w) else { break };
-            if c.next != Some(target) {
-                current = c.next;
-                continue;
-            }
-            if let Some(prev) = self.clients.get_mut(&w) {
-                prev.next = Some(window);
-            }
-            if let Some(inserted) = self.clients.get_mut(&window) {
-                inserted.next = Some(target);
-            }
-            break;
-        }
-    }
-
-    fn resize_window_with_mouse(&mut self, window: Window) -> WmResult<()> {
-        let is_fullscreen = self
-            .clients
-            .get(&window)
-            .map(|c| c.is_fullscreen)
-            .unwrap_or(false);
-
-        if is_fullscreen {
-            return Ok(());
-        }
-
-        let client_info = self.clients.get(&window).map(|c| {
-            (
-                c.x_position,
-                c.y_position,
-                c.width,
-                c.height,
-                c.border_width,
-                c.is_floating,
-                c.monitor_index,
-            )
-        });
-
-        let Some((
-            orig_x,
-            orig_y,
-            orig_width,
-            orig_height,
-            border_width,
-            was_floating,
-            monitor_idx,
-        )) = client_info
-        else {
-            return Ok(());
-        };
-
-        let monitor = match self.monitors.get(monitor_idx) {
-            Some(m) => m,
-            None => return Ok(()),
-        };
-
-        let is_normie = self.layout.name() == "normie";
-
-        if self.config.auto_tile && !was_floating && !is_normie {
-            let mut tiled_count = 0;
-            let mut current = monitor.clients_head;
-            while let Some(w) = current {
-                if let Some(c) = self.clients.get(&w) {
-                    let visible = (c.tags & monitor.tagset[monitor.selected_tags_index]) != 0;
-                    if visible && !c.is_floating {
-                        tiled_count += 1;
-                    }
-                    current = c.next;
-                } else {
-                    break;
-                }
-            }
-            if tiled_count <= 1 {
-                return Ok(());
-            }
-        }
-
-        if !was_floating && !is_normie {
-            self.toggle_floating()?;
-        }
-
-        self.connection.warp_pointer(
-            x11rb::NONE,
-            window,
-            0,
-            0,
-            0,
-            0,
-            (orig_width + border_width - 1) as i16,
-            (orig_height + border_width - 1) as i16,
-        )?;
-
-        self.connection
-            .grab_pointer(
-                false,
-                self.root,
-                EventMask::POINTER_MOTION | EventMask::BUTTON_RELEASE | EventMask::BUTTON_PRESS,
-                GrabMode::ASYNC,
-                GrabMode::ASYNC,
-                x11rb::NONE,
-                x11rb::NONE,
-                x11rb::CURRENT_TIME,
-            )?
-            .reply()?;
-
-        let mut last_time = 0u32;
-
-        loop {
-            let event = self.connection.wait_for_event()?;
-            match event {
-                Event::ConfigureRequest(_) | Event::MapRequest(_) | Event::Expose(_) => {}
-                Event::MotionNotify(e) => {
-                    if e.time.wrapping_sub(last_time) <= 16 {
-                        continue;
-                    }
-                    last_time = e.time;
-
-                    let new_width = ((e.root_x as i32 - orig_x as i32 - 2 * border_width as i32
-                        + 1)
-                    .max(1)) as u32;
-                    let new_height = ((e.root_y as i32 - orig_y as i32 - 2 * border_width as i32
-                        + 1)
-                    .max(1)) as u32;
-
-                    let should_resize = is_normie
-                        || self
-                            .clients
-                            .get(&window)
-                            .map(|c| c.is_floating)
-                            .unwrap_or(false);
-
-                    if should_resize && let Some(client) = self.clients.get(&window).cloned() {
-                        let (_, _, hint_width, hint_height, _) = self.apply_size_hints(
-                            window,
-                            client.x_position as i32,
-                            client.y_position as i32,
-                            new_width as i32,
-                            new_height as i32,
-                        );
-
-                        if let Some(client_mut) = self.clients.get_mut(&window) {
-                            client_mut.width = hint_width as u16;
-                            client_mut.height = hint_height as u16;
-                        }
-
-                        self.connection.configure_window(
-                            window,
-                            &ConfigureWindowAux::new()
-                                .width(hint_width as u32)
-                                .height(hint_height as u32),
-                        )?;
-                        self.connection.flush()?;
-                    }
-                }
-                Event::ButtonRelease(_) => break,
-                _ => {}
-            }
-        }
-
-        let final_client = self.clients.get(&window).map(|c| (c.width, c.border_width));
-
-        if let Some((w, bw)) = final_client {
-            self.connection.warp_pointer(
-                x11rb::NONE,
-                window,
-                0,
-                0,
-                0,
-                0,
-                (w + bw - 1) as i16,
-                (w + bw - 1) as i16,
-            )?;
-        }
-
-        self.connection
-            .ungrab_pointer(x11rb::CURRENT_TIME)?
-            .check()?;
-
-        let final_client_pos = self
-            .clients
-            .get(&window)
-            .map(|c| (c.x_position, c.y_position, c.width, c.height));
-
-        if let Some((x, y, w, h)) = final_client_pos {
-            let new_monitor = self.get_monitor_for_rect(x as i32, y as i32, w as i32, h as i32);
-            if new_monitor != monitor_idx {
-                self.move_window_to_monitor(window, new_monitor)?;
-                self.selected_monitor = new_monitor;
-                self.focus(None)?;
-            }
-        }
-
-        if self.config.auto_tile && !was_floating && !is_normie {
-            self.floating_windows.remove(&window);
-            if let Some(client) = self.clients.get_mut(&window) {
-                client.is_floating = false;
-            }
-            self.apply_layout()?;
-        }
-
-        Ok(())
-    }
-
-    fn handle_event(&mut self, event: Event) -> WmResult<Control> {
-        match event {
-            Event::KeyPress(ref key_event) if key_event.event == self.overlay.window() => {
-                if self.overlay.is_visible()
-                    && let Err(error) = self.overlay.hide(&self.connection)
-                {
-                    eprintln!("Failed to hide overlay: {:?}", error);
-                }
-                return Ok(Control::Continue);
-            }
-            Event::ButtonPress(ref button_event) if button_event.event == self.overlay.window() => {
-                if self.overlay.is_visible()
-                    && let Err(error) = self.overlay.hide(&self.connection)
-                {
-                    eprintln!("Failed to hide overlay: {:?}", error);
-                }
-                return Ok(Control::Continue);
-            }
-            Event::Expose(ref expose_event) if expose_event.window == self.overlay.window() => {
-                if self.overlay.is_visible()
-                    && let Err(error) = self.overlay.draw(&self.connection, &self.font)
-                {
-                    eprintln!("Failed to draw overlay: {:?}", error);
-                }
-                return Ok(Control::Continue);
-            }
-            Event::KeyPress(ref e) if e.event == self.keybind_overlay.window() => {
-                if self.keybind_overlay.is_visible()
-                    && !self.keybind_overlay.should_suppress_input()
-                {
-                    use crate::keyboard::keysyms;
-                    if let Some(mapping) = &self.keyboard_mapping {
-                        let keysym = mapping.keycode_to_keysym(e.detail);
-                        let is_escape = keysym == keysyms::XK_ESCAPE;
-                        let is_q = keysym == keysyms::XK_Q || keysym == 0x0051;
-                        if (is_escape || is_q)
-                            && let Err(error) = self.keybind_overlay.hide(&self.connection)
-                        {
-                            eprintln!("Failed to hide keybind overlay: {:?}", error);
-                        }
-                    }
-                }
-                return Ok(Control::Continue);
-            }
-            Event::ButtonPress(ref e) if e.event == self.keybind_overlay.window() => {
-                self.connection
-                    .allow_events(Allow::REPLAY_POINTER, e.time)?;
-                return Ok(Control::Continue);
-            }
-            Event::Expose(ref expose_event)
-                if expose_event.window == self.keybind_overlay.window() =>
-            {
-                if self.keybind_overlay.is_visible()
-                    && let Err(error) = self.keybind_overlay.draw(&self.connection, &self.font)
-                {
-                    eprintln!("Failed to draw keybind overlay: {:?}", error);
-                }
-                return Ok(Control::Continue);
-            }
-            Event::MapRequest(event) => {
-                let attrs = match self.connection.get_window_attributes(event.window)?.reply() {
-                    Ok(attrs) => attrs,
-                    Err(_) => return Ok(Control::Continue),
-                };
-
-                if attrs.override_redirect {
-                    return Ok(Control::Continue);
-                }
-
-                if !self.windows.contains(&event.window) {
-                    self.manage_window(event.window)?;
-                }
-            }
-            Event::UnmapNotify(event) => {
-                if self.windows.contains(&event.window) && self.is_window_visible(event.window) {
-                    self.remove_window(event.window, false)?;
-                }
-            }
-            Event::DestroyNotify(event) => {
-                if self.windows.contains(&event.window) {
-                    self.remove_window(event.window, true)?;
-                }
-            }
-            Event::PropertyNotify(event) => {
-                if event.state == Property::DELETE {
-                    return Ok(Control::Continue);
-                }
-
-                if !self.clients.contains_key(&event.window) {
-                    return Ok(Control::Continue);
-                }
-
-                if event.atom == AtomEnum::WM_TRANSIENT_FOR.into() {
-                    let is_floating = self
-                        .clients
-                        .get(&event.window)
-                        .map(|c| c.is_floating)
-                        .unwrap_or(false);
-                    if !is_floating
-                        && let Some(parent) = self.get_transient_parent(event.window)
-                        && self.clients.contains_key(&parent)
-                    {
-                        if let Some(c) = self.clients.get_mut(&event.window) {
-                            c.is_floating = true;
-                        }
-                        self.floating_windows.insert(event.window);
-                        self.apply_layout()?;
-                    }
-                } else if event.atom == AtomEnum::WM_NORMAL_HINTS.into() {
-                    if let Some(c) = self.clients.get_mut(&event.window) {
-                        c.hints_valid = false;
-                    }
-                } else if event.atom == AtomEnum::WM_HINTS.into() {
-                    self.update_window_hints(event.window)?;
-                    self.update_bar()?;
-                }
-
-                if event.atom == self.atoms.wm_name || event.atom == self.atoms.net_wm_name {
-                    let _ = self.update_window_title(event.window);
-                    if self.layout.name() == "tabbed" {
-                        self.update_tab_bars()?;
-                    }
-                }
-
-                if event.atom == self.atoms.net_wm_window_type {
-                    self.update_window_type(event.window)?;
-                }
-            }
-            Event::EnterNotify(event) => {
-                if event.mode != x11rb::protocol::xproto::NotifyMode::NORMAL
-                    || event.detail == x11rb::protocol::xproto::NotifyDetail::INFERIOR
-                {
-                    return Ok(Control::Continue);
-                }
-                if self.windows.contains(&event.event) {
-                    if let Some(client) = self.clients.get(&event.event)
-                        && client.monitor_index != self.selected_monitor
-                    {
-                        if let Some(old_selected) = self
-                            .monitors
-                            .get(self.selected_monitor)
-                            .and_then(|monitor| monitor.selected_client)
-                        {
-                            self.unfocus(old_selected, false)?;
-                        }
-                        self.selected_monitor = client.monitor_index;
-                        self.update_bar()?;
-                    }
-                    self.focus(Some(event.event))?;
-                    self.update_tab_bars()?;
-                }
-            }
-            Event::MotionNotify(event) => {
-                if event.event != self.root {
-                    return Ok(Control::Continue);
-                }
-
-                if let Some(monitor_index) =
-                    self.get_monitor_at_point(event.root_x as i32, event.root_y as i32)
-                    && monitor_index != self.selected_monitor
-                {
-                    if let Some(old_selected) = self
-                        .monitors
-                        .get(self.selected_monitor)
-                        .and_then(|monitor| monitor.selected_client)
-                    {
-                        self.unfocus(old_selected, true)?;
-                    }
-
-                    self.selected_monitor = monitor_index;
-                    self.focus(None)?;
-                    self.update_bar()?;
-                    self.update_tab_bars()?;
-                }
-            }
-            Event::KeyPress(event) => {
-                let Some(mapping) = &self.keyboard_mapping else {
-                    return Ok(Control::Continue);
-                };
-
-                let result = keyboard::handle_key_press(
-                    event,
-                    &self.config.keybindings,
-                    &self.keychord_state,
-                    mapping,
-                );
-
-                match result {
-                    keyboard::handlers::KeychordResult::Completed(action, arg) => {
-                        self.keychord_state = keyboard::handlers::KeychordState::Idle;
-                        self.current_key = 0;
-                        self.grab_keys()?;
-                        self.update_bar()?;
-
-                        match action {
-                            KeyAction::Quit => return Ok(Control::Quit),
-                            KeyAction::Restart => match self.try_reload_config() {
-                                Ok(()) => {
-                                    self.gaps_enabled = self.config.gaps_enabled;
-                                    self.error_message = None;
-                                    if let Err(error) = self.overlay.hide(&self.connection) {
-                                        eprintln!(
-                                            "Failed to hide overlay after config reload: {:?}",
-                                            error
-                                        );
-                                    }
-                                    self.apply_layout()?;
-                                    self.update_bar()?;
-                                }
-                                Err(err) => {
-                                    eprintln!("Config reload error: {}", err);
-                                    self.error_message = Some(err.to_string());
-                                    let monitor = &self.monitors[self.selected_monitor];
-                                    let monitor_x = monitor.screen_x as i16;
-                                    let monitor_y = monitor.screen_y as i16;
-                                    let screen_width = monitor.screen_width as u16;
-                                    let screen_height = monitor.screen_height as u16;
-                                    match self.overlay.show_error(
-                                        &self.connection,
-                                        &self.font,
-                                        err,
-                                        monitor_x,
-                                        monitor_y,
-                                        screen_width,
-                                        screen_height,
-                                    ) {
-                                        Ok(()) => eprintln!("Error modal displayed"),
-                                        Err(e) => eprintln!("Failed to show error modal: {:?}", e),
-                                    }
-                                }
-                            },
-                            _ => self.handle_key_action(action, &arg)?,
-                        }
-                    }
-                    keyboard::handlers::KeychordResult::InProgress(candidates) => {
-                        self.current_key += 1;
-                        self.keychord_state = keyboard::handlers::KeychordState::InProgress {
-                            candidates: candidates.clone(),
-                            keys_pressed: self.current_key,
-                        };
-                        self.grab_keys()?;
-                        self.update_bar()?;
-                    }
-                    keyboard::handlers::KeychordResult::Cancelled
-                    | keyboard::handlers::KeychordResult::None => {
-                        self.keychord_state = keyboard::handlers::KeychordState::Idle;
-                        self.current_key = 0;
-                        self.grab_keys()?;
-                        self.update_bar()?;
-                    }
-                }
-            }
-            Event::ButtonPress(event) => {
-                if self.keybind_overlay.is_visible()
-                    && event.event != self.keybind_overlay.window()
-                    && let Err(error) = self.keybind_overlay.hide(&self.connection)
-                {
-                    eprintln!("Failed to hide keybind overlay: {:?}", error);
-                }
-
-                let is_bar_click = self
-                    .bars
-                    .iter()
-                    .enumerate()
-                    .find(|(_, bar)| bar.window() == event.event);
-
-                if let Some((monitor_index, bar)) = is_bar_click {
-                    if let Some(tag_index) = bar.handle_click(event.event_x) {
-                        if monitor_index != self.selected_monitor {
-                            self.selected_monitor = monitor_index;
-                        }
-                        self.view_tag(tag_index)?;
-                    }
-                } else {
-                    let is_tab_bar_click = self
-                        .tab_bars
-                        .iter()
-                        .enumerate()
-                        .find(|(_, tab_bar)| tab_bar.window() == event.event);
-
-                    if let Some((monitor_index, tab_bar)) = is_tab_bar_click {
-                        if monitor_index != self.selected_monitor {
-                            self.selected_monitor = monitor_index;
-                        }
-
-                        let visible_windows: Vec<(Window, String)> = self
-                            .windows
-                            .iter()
-                            .filter_map(|&window| {
-                                if let Some(client) = self.clients.get(&window) {
-                                    if client.monitor_index != monitor_index
-                                        || self.floating_windows.contains(&window)
-                                        || self.fullscreen_windows.contains(&window)
-                                    {
-                                        return None;
-                                    }
-                                    let monitor_tags = self
-                                        .monitors
-                                        .get(monitor_index)
-                                        .map(|m| m.tagset[m.selected_tags_index])
-                                        .unwrap_or(0);
-                                    if (client.tags & monitor_tags) != 0 {
-                                        return Some((window, client.name.clone()));
-                                    }
-                                }
-                                None
-                            })
-                            .collect();
-
-                        if let Some(clicked_window) =
-                            tab_bar.get_clicked_window(&visible_windows, event.event_x)
-                        {
-                            self.connection.configure_window(
-                                clicked_window,
-                                &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
-                            )?;
-                            self.focus(Some(clicked_window))?;
-                            self.update_tab_bars()?;
-                        }
-                    } else if event.child != x11rb::NONE {
-                        self.focus(Some(event.child))?;
-                        self.restack()?;
-                        self.update_tab_bars()?;
-
-                        let state_clean = u16::from(event.state)
-                            & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2));
-                        let modkey_held = state_clean & u16::from(self.config.modkey) != 0;
-
-                        if modkey_held && event.detail == ButtonIndex::M1.into() {
-                            if self.clients.contains_key(&event.child) {
-                                self.drag_window(event.child)?;
-                            }
-                            self.connection
-                                .allow_events(Allow::REPLAY_POINTER, event.time)?;
-                        } else if modkey_held && event.detail == ButtonIndex::M3.into() {
-                            if self.clients.contains_key(&event.child) {
-                                self.resize_window_with_mouse(event.child)?;
-                            }
-                            self.connection
-                                .allow_events(Allow::REPLAY_POINTER, event.time)?;
-                        } else {
-                            self.connection
-                                .allow_events(Allow::REPLAY_POINTER, event.time)?;
-                        }
-                    } else if self.windows.contains(&event.event) {
-                        self.focus(Some(event.event))?;
-                        self.restack()?;
-                        self.update_tab_bars()?;
-
-                        let state_clean = u16::from(event.state)
-                            & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2));
-                        let modkey_held = state_clean & u16::from(self.config.modkey) != 0;
-
-                        if modkey_held && event.detail == ButtonIndex::M1.into() {
-                            self.drag_window(event.event)?;
-                            self.connection
-                                .allow_events(Allow::REPLAY_POINTER, event.time)?;
-                        } else if modkey_held && event.detail == ButtonIndex::M3.into() {
-                            self.resize_window_with_mouse(event.event)?;
-                            self.connection
-                                .allow_events(Allow::REPLAY_POINTER, event.time)?;
-                        } else {
-                            self.connection
-                                .allow_events(Allow::REPLAY_POINTER, event.time)?;
-                        }
-                    } else {
-                        self.connection
-                            .allow_events(Allow::REPLAY_POINTER, event.time)?;
-                    }
-                }
-            }
-            Event::Expose(event) => {
-                for bar in &mut self.bars {
-                    if event.window == bar.window() {
-                        bar.invalidate();
-                        self.update_bar()?;
-                        break;
-                    }
-                }
-                for _tab_bar in &self.tab_bars {
-                    if event.window == _tab_bar.window() {
-                        self.update_tab_bars()?;
-                        break;
-                    }
-                }
-            }
-            Event::ConfigureRequest(event) => {
-                if let Some(client) = self.clients.get(&event.window) {
-                    let monitor = &self.monitors[client.monitor_index];
-                    let is_floating = client.is_floating;
-                    let is_fullscreen = client.is_fullscreen;
-                    let has_layout = self.layout.name() != "normie";
-
-                    if event.value_mask.contains(ConfigWindow::BORDER_WIDTH) {
-                        if let Some(c) = self.clients.get_mut(&event.window) {
-                            c.border_width = event.border_width;
-                        }
-                    } else if is_fullscreen {
-                        self.send_configure_notify(event.window)?;
-                    } else if is_floating || !has_layout {
-                        let mut x = client.x_position as i32;
-                        let mut y = client.y_position as i32;
-                        let mut w = client.width as i32;
-                        let mut h = client.height as i32;
-
-                        if event.value_mask.contains(ConfigWindow::X) {
-                            if let Some(c) = self.clients.get_mut(&event.window) {
-                                c.old_x_position = c.x_position;
-                            }
-                            x = monitor.screen_x + event.x as i32;
-                        }
-                        if event.value_mask.contains(ConfigWindow::Y) {
-                            if let Some(c) = self.clients.get_mut(&event.window) {
-                                c.old_y_position = c.y_position;
-                            }
-                            y = monitor.screen_y + event.y as i32;
-                        }
-                        if event.value_mask.contains(ConfigWindow::WIDTH) {
-                            if let Some(c) = self.clients.get_mut(&event.window) {
-                                c.old_width = c.width;
-                            }
-                            w = event.width as i32;
-                        }
-                        if event.value_mask.contains(ConfigWindow::HEIGHT) {
-                            if let Some(c) = self.clients.get_mut(&event.window) {
-                                c.old_height = c.height;
-                            }
-                            h = event.height as i32;
-                        }
-
-                        let bw = self.config.border_width as i32;
-                        let width_with_border = w + 2 * bw;
-                        let height_with_border = h + 2 * bw;
-
-                        if (x + w) > monitor.screen_x + monitor.screen_width && is_floating {
-                            x = monitor.screen_x
-                                + (monitor.screen_width / 2 - width_with_border / 2);
-                        }
-                        if (y + h) > monitor.screen_y + monitor.screen_height && is_floating {
-                            y = monitor.screen_y
-                                + (monitor.screen_height / 2 - height_with_border / 2);
-                        }
-
-                        if let Some(c) = self.clients.get_mut(&event.window) {
-                            c.x_position = x as i16;
-                            c.y_position = y as i16;
-                            c.width = w as u16;
-                            c.height = h as u16;
-                        }
-
-                        let only_position_change = event.value_mask.contains(ConfigWindow::X)
-                            || event.value_mask.contains(ConfigWindow::Y);
-                        let no_size_change = !event.value_mask.contains(ConfigWindow::WIDTH)
-                            && !event.value_mask.contains(ConfigWindow::HEIGHT);
-                        if only_position_change && no_size_change {
-                            self.send_configure_notify(event.window)?;
-                        }
-
-                        if self.is_visible(event.window) {
-                            self.connection.configure_window(
-                                event.window,
-                                &ConfigureWindowAux::new()
-                                    .x(x)
-                                    .y(y)
-                                    .width(w as u32)
-                                    .height(h as u32),
-                            )?;
-                        }
-                    } else {
-                        self.send_configure_notify(event.window)?;
-                    }
-                } else {
-                    let mut aux = ConfigureWindowAux::new();
-                    if event.value_mask.contains(ConfigWindow::X) {
-                        aux = aux.x(event.x as i32);
-                    }
-                    if event.value_mask.contains(ConfigWindow::Y) {
-                        aux = aux.y(event.y as i32);
-                    }
-                    if event.value_mask.contains(ConfigWindow::WIDTH) {
-                        aux = aux.width(event.width as u32);
-                    }
-                    if event.value_mask.contains(ConfigWindow::HEIGHT) {
-                        aux = aux.height(event.height as u32);
-                    }
-                    if event.value_mask.contains(ConfigWindow::BORDER_WIDTH) {
-                        aux = aux.border_width(event.border_width as u32);
-                    }
-                    if event.value_mask.contains(ConfigWindow::SIBLING) {
-                        aux = aux.sibling(event.sibling);
-                    }
-                    if event.value_mask.contains(ConfigWindow::STACK_MODE) {
-                        aux = aux.stack_mode(event.stack_mode);
-                    }
-                    self.connection.configure_window(event.window, &aux)?;
-                }
-                self.connection.flush()?;
-            }
-            Event::ClientMessage(event) => {
-                if !self.clients.contains_key(&event.window) {
-                    return Ok(Control::Continue);
-                }
-
-                if event.type_ == self.atoms.net_wm_state {
-                    let data = event.data.as_data32();
-                    let atom1 = data.get(1).copied().unwrap_or(0);
-                    let atom2 = data.get(2).copied().unwrap_or(0);
-
-                    if atom1 == self.atoms.net_wm_state_fullscreen
-                        || atom2 == self.atoms.net_wm_state_fullscreen
-                    {
-                        let action = data[0];
-                        let fullscreen = match action {
-                            1 => true,
-                            0 => false,
-                            2 => !self.fullscreen_windows.contains(&event.window),
-                            _ => return Ok(Control::Continue),
-                        };
-                        self.set_window_fullscreen(event.window, fullscreen)?;
-                        self.restack()?;
-                    }
-                } else if event.type_ == self.atoms.net_active_window {
-                    let selected_window = self
-                        .monitors
-                        .get(self.selected_monitor)
-                        .and_then(|m| m.selected_client);
-
-                    let is_urgent = self
-                        .clients
-                        .get(&event.window)
-                        .map(|c| c.is_urgent)
-                        .unwrap_or(false);
-
-                    if Some(event.window) != selected_window && !is_urgent {
-                        self.set_urgent(event.window, true)?;
-                    }
-                }
-            }
-            Event::FocusIn(event) => {
-                if !self.windows.contains(&event.event) {
-                    return Ok(Control::Continue);
-                }
-
-                let event_window_visible = self.is_visible(event.event);
-
-                if !event_window_visible {
-                    self.focus(None)?;
-                } else {
-                    let selected_window = self
-                        .monitors
-                        .get(self.selected_monitor)
-                        .and_then(|monitor| monitor.selected_client);
-
-                    if let Some(focused_window) = selected_window
-                        && event.event != focused_window
-                    {
-                        self.set_focus(focused_window)?;
-                    }
-                }
-            }
-            Event::MappingNotify(event) => {
-                if event.request == x11rb::protocol::xproto::Mapping::KEYBOARD {
-                    self.grab_keys()?;
-                }
-            }
-            Event::ConfigureNotify(event) => {
-                if event.window == self.root {
-                    let old_width = self.screen.width_in_pixels;
-                    let old_height = self.screen.height_in_pixels;
-
-                    if event.width != old_width || event.height != old_height {
-                        self.screen = self.connection.setup().roots[self.screen_number].clone();
-
-                        for monitor_index in 0..self.monitors.len() {
-                            let monitor = &self.monitors[monitor_index];
-                            let monitor_x = monitor.screen_x;
-                            let monitor_y = monitor.screen_y;
-                            let monitor_width = monitor.screen_width as u32;
-                            let monitor_height = monitor.screen_height as u32;
-
-                            let fullscreen_on_monitor: Vec<Window> = self
-                                .fullscreen_windows
-                                .iter()
-                                .filter(|&&window| {
-                                    self.clients
-                                        .get(&window)
-                                        .map(|client| client.monitor_index == monitor_index)
-                                        .unwrap_or(false)
-                                })
-                                .copied()
-                                .collect();
-
-                            for window in fullscreen_on_monitor {
-                                self.connection.configure_window(
-                                    window,
-                                    &ConfigureWindowAux::new()
-                                        .x(monitor_x)
-                                        .y(monitor_y)
-                                        .width(monitor_width)
-                                        .height(monitor_height),
-                                )?;
-                            }
-                        }
-
-                        self.apply_layout()?;
-                    }
-                }
-            }
-            _ => {}
-        }
-        Ok(Control::Continue)
-    }
-
-    fn apply_layout(&mut self) -> WmResult<()> {
-        for monitor_index in 0..self.monitors.len() {
-            let stack_head = self.monitors.get(monitor_index).and_then(|m| m.stack_head);
-            self.showhide(stack_head)?;
-        }
-
-        let is_normie = self.layout.name() == LayoutType::Normie.as_str();
-
-        if !is_normie {
-            let monitor_count = self.monitors.len();
-            for monitor_index in 0..monitor_count {
-                let monitor = &self.monitors[monitor_index];
-                let border_width = self.config.border_width;
-
-                let gaps = if self.gaps_enabled {
-                    GapConfig {
-                        inner_horizontal: self.config.gap_inner_horizontal,
-                        inner_vertical: self.config.gap_inner_vertical,
-                        outer_horizontal: self.config.gap_outer_horizontal,
-                        outer_vertical: self.config.gap_outer_vertical,
-                    }
-                } else {
-                    GapConfig {
-                        inner_horizontal: 0,
-                        inner_vertical: 0,
-                        outer_horizontal: 0,
-                        outer_vertical: 0,
-                    }
-                };
-
-                let monitor_x = monitor.screen_x;
-                let monitor_y = monitor.screen_y;
-                let monitor_width = monitor.screen_width;
-                let monitor_height = monitor.screen_height;
-                let scroll_offset = monitor.scroll_offset;
-
-                let mut visible: Vec<Window> = Vec::new();
-                let mut current = self.next_tiled(monitor.clients_head, monitor);
-                while let Some(window) = current {
-                    visible.push(window);
-                    if let Some(client) = self.clients.get(&window) {
-                        current = self.next_tiled(client.next, monitor);
-                    } else {
-                        break;
-                    }
-                }
-
-                let bar_height = if self.show_bar {
-                    self.bars
-                        .get(monitor_index)
-                        .map(|bar| bar.height() as u32)
-                        .unwrap_or(0)
-                } else {
-                    0
-                };
-                let usable_height = monitor_height.saturating_sub(bar_height as i32);
-                let master_factor = monitor.master_factor;
-                let num_master = monitor.num_master;
-                let smartgaps_enabled = self.config.smartgaps_enabled;
-
-                let geometries = self.layout.arrange(
-                    &visible,
-                    monitor_width as u32,
-                    usable_height as u32,
-                    &gaps,
-                    master_factor,
-                    num_master,
-                    smartgaps_enabled,
-                );
-
-                for (window, geometry) in visible.iter().zip(geometries.iter()) {
-                    let mut adjusted_width = geometry.width.saturating_sub(2 * border_width);
-                    let mut adjusted_height = geometry.height.saturating_sub(2 * border_width);
-
-                    if let Some(client) = self.clients.get(window).cloned()
-                        && !client.is_floating
-                    {
-                        let (_, _, hint_width, hint_height, _) = self.apply_size_hints(
-                            *window,
-                            geometry.x_coordinate,
-                            geometry.y_coordinate,
-                            adjusted_width as i32,
-                            adjusted_height as i32,
-                        );
-                        adjusted_width = hint_width as u32;
-                        adjusted_height = hint_height as u32;
-                    }
-
-                    let is_scrolling = self.layout.name() == "scrolling";
-                    let adjusted_x = if is_scrolling {
-                        geometry.x_coordinate + monitor_x - scroll_offset
-                    } else {
-                        geometry.x_coordinate + monitor_x
-                    };
-                    let adjusted_y = geometry.y_coordinate + monitor_y + bar_height as i32;
-
-                    if let Some(client) = self.clients.get_mut(window) {
-                        client.x_position = adjusted_x as i16;
-                        client.y_position = adjusted_y as i16;
-                        client.width = adjusted_width as u16;
-                        client.height = adjusted_height as u16;
-                    }
-
-                    self.connection.configure_window(
-                        *window,
-                        &ConfigureWindowAux::new()
-                            .x(adjusted_x)
-                            .y(adjusted_y)
-                            .width(adjusted_width)
-                            .height(adjusted_height)
-                            .border_width(border_width),
-                    )?;
-
-                    if let Some(c) = self.clients.get_mut(window) {
-                        c.x_position = adjusted_x as i16;
-                        c.y_position = adjusted_y as i16;
-                        c.width = adjusted_width as u16;
-                        c.height = adjusted_height as u16;
-                        c.border_width = border_width as u16;
-                    }
-                }
-            }
-        }
-
-        for monitor_index in 0..self.monitors.len() {
-            let stack_head = self.monitors[monitor_index].stack_head;
-            self.showhide(stack_head)?;
-        }
-
-        for monitor_index in 0..self.monitors.len() {
-            let monitor = &self.monitors[monitor_index];
-            let tags = monitor.tagset[monitor.selected_tags_index];
-
-            let has_visible_fullscreen = self.fullscreen_windows.iter().any(|&w| {
-                self.clients.get(&w).map_or(false, |c| {
-                    c.monitor_index == monitor_index && (c.tags & tags) != 0
-                })
-            });
-
-            if has_visible_fullscreen {
-                if let Some(bar) = self.bars.get(monitor_index) {
-                    self.connection.unmap_window(bar.window())?;
-                }
-
-                for &window in &self.fullscreen_windows {
-                    if let Some(client) = self.clients.get(&window) {
-                        if client.monitor_index == monitor_index && (client.tags & tags) != 0 {
-                            self.connection.configure_window(
-                                window,
-                                &ConfigureWindowAux::new()
-                                    .border_width(0)
-                                    .x(monitor.screen_x)
-                                    .y(monitor.screen_y)
-                                    .width(monitor.screen_width as u32)
-                                    .height(monitor.screen_height as u32)
-                                    .stack_mode(StackMode::ABOVE),
-                            )?;
-                        }
-                    }
-                }
-            } else if self.show_bar {
-                if let Some(bar) = self.bars.get(monitor_index) {
-                    self.connection.map_window(bar.window())?;
-                }
-            }
-        }
-
-        self.connection.flush()?;
-
-        let is_tabbed = self.layout.name() == LayoutType::Tabbed.as_str();
-
-        if is_tabbed {
-            let outer_horizontal = if self.gaps_enabled {
-                self.config.gap_outer_horizontal
-            } else {
-                0
-            };
-            let outer_vertical = if self.gaps_enabled {
-                self.config.gap_outer_vertical
-            } else {
-                0
-            };
-
-            for monitor_index in 0..self.tab_bars.len() {
-                if let Some(monitor) = self.monitors.get(monitor_index) {
-                    let bar_height = if self.show_bar {
-                        self.bars
-                            .get(monitor_index)
-                            .map(|bar| bar.height() as f32)
-                            .unwrap_or(0.0)
-                    } else {
-                        0.0
-                    };
-
-                    let tab_bar_x = (monitor.screen_x + outer_horizontal as i32) as i16;
-                    let tab_bar_y =
-                        (monitor.screen_y as f32 + bar_height + outer_vertical as f32) as i16;
-                    let tab_bar_width = monitor
-                        .screen_width
-                        .saturating_sub(2 * outer_horizontal as i32)
-                        as u16;
-
-                    if let Err(e) = self.tab_bars[monitor_index].reposition(
-                        &self.connection,
-                        tab_bar_x,
-                        tab_bar_y,
-                        tab_bar_width,
-                    ) {
-                        eprintln!("Failed to reposition tab bar: {:?}", e);
-                    }
-                }
-            }
-        }
-
-        for monitor_index in 0..self.tab_bars.len() {
-            let has_visible_windows = self.windows.iter().any(|&window| {
-                if let Some(client) = self.clients.get(&window) {
-                    if client.monitor_index != monitor_index
-                        || self.floating_windows.contains(&window)
-                        || self.fullscreen_windows.contains(&window)
-                    {
-                        return false;
-                    }
-                    if let Some(monitor) = self.monitors.get(monitor_index) {
-                        return (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0;
-                    }
-                }
-                false
-            });
-
-            if is_tabbed && has_visible_windows {
-                if let Err(e) = self.tab_bars[monitor_index].show(&self.connection) {
-                    eprintln!("Failed to show tab bar: {:?}", e);
-                }
-            } else if let Err(e) = self.tab_bars[monitor_index].hide(&self.connection) {
-                eprintln!("Failed to hide tab bar: {:?}", e);
-            }
-        }
-
-        if is_tabbed {
-            self.update_tab_bars()?;
-        }
-
-        Ok(())
-    }
-
-    pub fn change_layout<L: Layout + 'static>(&mut self, new_layout: L) -> WmResult<()> {
-        self.layout = Box::new(new_layout);
-        self.apply_layout()?;
-        Ok(())
-    }
-
-    fn send_configure_notify(&self, window: Window) -> WmResult<()> {
-        let client = self.clients.get(&window);
-        let (x, y, w, h, bw) = if let Some(c) = client {
-            (
-                c.x_position,
-                c.y_position,
-                c.width,
-                c.height,
-                c.border_width,
-            )
-        } else {
-            let geom = self.connection.get_geometry(window)?.reply()?;
-            (geom.x, geom.y, geom.width, geom.height, geom.border_width)
-        };
-
-        let event = ConfigureNotifyEvent {
-            response_type: CONFIGURE_NOTIFY_EVENT,
-            sequence: 0,
-            event: window,
-            window,
-            above_sibling: x11rb::NONE,
-            x,
-            y,
-            width: w,
-            height: h,
-            border_width: bw,
-            override_redirect: false,
-        };
-
-        self.connection
-            .send_event(false, window, EventMask::STRUCTURE_NOTIFY, event)?;
-
-        Ok(())
-    }
-
-    fn update_size_hints(&mut self, window: Window) -> WmResult<()> {
-        let size_hints = self
-            .connection
-            .get_property(
-                false,
-                window,
-                x11rb::protocol::xproto::AtomEnum::WM_NORMAL_HINTS,
-                x11rb::protocol::xproto::AtomEnum::WM_SIZE_HINTS,
-                0,
-                18,
-            )?
-            .reply()?;
-
-        if size_hints.value.is_empty() {
-            if let Some(client) = self.clients.get_mut(&window) {
-                client.hints_valid = false;
-            }
-            return Ok(());
-        }
-
-        if size_hints.value.len() < 18 * 4 {
-            if let Some(client) = self.clients.get_mut(&window) {
-                client.hints_valid = false;
-            }
-            return Ok(());
-        }
-
-        use crate::size_hints::{flags::*, offset::*};
-
-        let read_u32 = |offset: usize| -> u32 {
-            let bytes = &size_hints.value[offset * 4..(offset + 1) * 4];
-            u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
-        };
-
-        let flags = read_u32(FLAGS);
-
-        if let Some(client) = self.clients.get_mut(&window) {
-            if flags & P_BASE_SIZE != 0 {
-                client.base_width = read_u32(BASE_WIDTH) as i32;
-                client.base_height = read_u32(BASE_HEIGHT) as i32;
-            } else if flags & P_MIN_SIZE != 0 {
-                client.base_width = read_u32(MIN_WIDTH) as i32;
-                client.base_height = read_u32(MIN_HEIGHT) as i32;
-            } else {
-                client.base_width = 0;
-                client.base_height = 0;
-            }
-
-            if flags & P_RESIZE_INC != 0 {
-                client.increment_width = read_u32(WIDTH_INC) as i32;
-                client.increment_height = read_u32(HEIGHT_INC) as i32;
-            } else {
-                client.increment_width = 0;
-                client.increment_height = 0;
-            }
-
-            if flags & P_MAX_SIZE != 0 {
-                client.max_width = read_u32(MAX_WIDTH) as i32;
-                client.max_height = read_u32(MAX_HEIGHT) as i32;
-            } else {
-                client.max_width = 0;
-                client.max_height = 0;
-            }
-
-            if flags & P_MIN_SIZE != 0 {
-                client.min_width = read_u32(MIN_WIDTH) as i32;
-                client.min_height = read_u32(MIN_HEIGHT) as i32;
-            } else if flags & P_BASE_SIZE != 0 {
-                client.min_width = read_u32(BASE_WIDTH) as i32;
-                client.min_height = read_u32(BASE_HEIGHT) as i32;
-            } else {
-                client.min_width = 0;
-                client.min_height = 0;
-            }
-
-            if flags & P_ASPECT != 0 {
-                client.min_aspect =
-                    (read_u32(MIN_ASPECT_Y) as f32) / (read_u32(MIN_ASPECT_X) as f32).max(1.0);
-                client.max_aspect =
-                    (read_u32(MAX_ASPECT_X) as f32) / (read_u32(MAX_ASPECT_Y) as f32).max(1.0);
-            } else {
-                client.min_aspect = 0.0;
-                client.max_aspect = 0.0;
-            }
-
-            client.is_fixed = client.max_width > 0
-                && client.max_height > 0
-                && client.max_width == client.min_width
-                && client.max_height == client.min_height;
-
-            client.hints_valid = true;
-        }
-        Ok(())
-    }
-
-    fn update_window_title(&mut self, window: Window) -> WmResult<()> {
-        let net_name = self
-            .connection
-            .get_property(
-                false,
-                window,
-                self.atoms.net_wm_name,
-                self.atoms.utf8_string,
-                0,
-                256,
-            )
-            .ok()
-            .and_then(|cookie| cookie.reply().ok());
-
-        if let Some(name) = net_name
-            && !name.value.is_empty()
-            && let Ok(title) = String::from_utf8(name.value.clone())
-            && let Some(client) = self.clients.get_mut(&window)
-        {
-            client.name = title;
-            return Ok(());
-        }
-
-        let wm_name = self
-            .connection
-            .get_property(
-                false,
-                window,
-                self.atoms.wm_name,
-                x11rb::protocol::xproto::AtomEnum::STRING,
-                0,
-                256,
-            )?
-            .reply()?;
-
-        if !wm_name.value.is_empty()
-            && let Ok(title) = String::from_utf8(wm_name.value.clone())
-            && let Some(client) = self.clients.get_mut(&window)
-        {
-            client.name = title;
-        }
-
-        Ok(())
-    }
-
-    fn update_window_hints(&mut self, window: Window) -> WmResult<()> {
-        let hints_reply = self
-            .connection
-            .get_property(false, window, AtomEnum::WM_HINTS, AtomEnum::WM_HINTS, 0, 9)?
-            .reply();
-
-        if let Ok(hints) = hints_reply
-            && hints.value.len() >= 4
-        {
-            let flags = u32::from_ne_bytes([
-                hints.value[0],
-                hints.value[1],
-                hints.value[2],
-                hints.value[3],
-            ]);
-
-            let selected_window = self
-                .monitors
-                .get(self.selected_monitor)
-                .and_then(|m| m.selected_client);
-
-            if Some(window) == selected_window && (flags & 256) != 0 {
-                let new_flags = flags & !256;
-                let mut new_hints = hints.value.clone();
-                new_hints[0..4].copy_from_slice(&new_flags.to_ne_bytes());
-
-                self.connection.change_property(
-                    x11rb::protocol::xproto::PropMode::REPLACE,
-                    window,
-                    AtomEnum::WM_HINTS,
-                    AtomEnum::WM_HINTS,
-                    32,
-                    9,
-                    &new_hints,
-                )?;
-            } else if let Some(client) = self.clients.get_mut(&window) {
-                client.is_urgent = (flags & 256) != 0;
-            }
-
-            if hints.value.len() >= 8 && (flags & 1) != 0 {
-                let input = i32::from_ne_bytes([
-                    hints.value[4],
-                    hints.value[5],
-                    hints.value[6],
-                    hints.value[7],
-                ]);
-
-                if let Some(client) = self.clients.get_mut(&window) {
-                    client.never_focus = input == 0;
-                }
-            } else if let Some(client) = self.clients.get_mut(&window) {
-                client.never_focus = false;
-            }
-        }
-
-        Ok(())
-    }
-
-    fn update_window_type(&mut self, window: Window) -> WmResult<()> {
-        if let Ok(state_atoms) = self.get_window_atom_list_property(window, self.atoms.net_wm_state)
-        {
-            if state_atoms.contains(&self.atoms.net_wm_state_fullscreen) {
-                self.set_window_fullscreen(window, true)?;
-            }
-        }
-
-        if let Ok(Some(type_atom)) =
-            self.get_window_atom_property(window, self.atoms.net_wm_window_type)
-            && type_atom == self.atoms.net_wm_window_type_dialog
-        {
-            if let Some(client) = self.clients.get_mut(&window) {
-                client.is_floating = true;
-            }
-            self.floating_windows.insert(window);
-        }
-
-        Ok(())
-    }
-
-    fn apply_size_hints(
-        &mut self,
-        window: Window,
-        mut x: i32,
-        mut y: i32,
-        mut w: i32,
-        mut h: i32,
-    ) -> (i32, i32, i32, i32, bool) {
-        let bh = 20;
-
-        let (
-            client_x,
-            client_y,
-            client_w,
-            client_h,
-            bw,
-            monitor_index,
-            is_floating,
-            mut hints_valid,
-        ) = {
-            let client = match self.clients.get(&window) {
-                Some(c) => c,
-                None => return (x, y, w, h, false),
-            };
-            (
-                client.x_position as i32,
-                client.y_position as i32,
-                client.width as i32,
-                client.height as i32,
-                client.border_width as i32,
-                client.monitor_index,
-                client.is_floating,
-                client.hints_valid,
-            )
-        };
-
-        let monitor = &self.monitors[monitor_index];
-        let client_width = client_w + 2 * bw;
-        let client_height = client_h + 2 * bw;
-
-        w = w.max(1);
-        h = h.max(1);
-
-        if x >= monitor.window_area_x + monitor.window_area_width {
-            x = monitor.window_area_x + monitor.window_area_width - client_width;
-        }
-        if y >= monitor.window_area_y + monitor.window_area_height {
-            y = monitor.window_area_y + monitor.window_area_height - client_height;
-        }
-        if x + w + 2 * bw <= monitor.window_area_x {
-            x = monitor.window_area_x;
-        }
-        if y + h + 2 * bw <= monitor.window_area_y {
-            y = monitor.window_area_y;
-        }
-
-        if h < bh {
-            h = bh;
-        }
-        if w < bh {
-            w = bh;
-        }
-
-        if is_floating || self.layout.name() == "normie" {
-            if !hints_valid {
-                let _ = self.update_size_hints(window);
-                hints_valid = self
-                    .clients
-                    .get(&window)
-                    .map(|c| c.hints_valid)
-                    .unwrap_or(false);
-            }
-
-            if hints_valid {
-                let (
-                    base_width,
-                    base_height,
-                    min_width,
-                    min_height,
-                    max_width,
-                    max_height,
-                    inc_width,
-                    inc_height,
-                    min_aspect,
-                    max_aspect,
-                ) = {
-                    let client = self.clients.get(&window).unwrap();
-                    (
-                        client.base_width,
-                        client.base_height,
-                        client.min_width,
-                        client.min_height,
-                        client.max_width,
-                        client.max_height,
-                        client.increment_width,
-                        client.increment_height,
-                        client.min_aspect,
-                        client.max_aspect,
-                    )
-                };
-
-                let base_is_min = base_width == min_width && base_height == min_height;
-
-                if !base_is_min {
-                    w -= base_width;
-                    h -= base_height;
-                }
-
-                if min_aspect > 0.0 && max_aspect > 0.0 {
-                    if max_aspect < (w as f32 / h as f32) {
-                        w = (h as f32 * max_aspect + 0.5) as i32;
-                    } else if min_aspect < (h as f32 / w as f32) {
-                        h = (w as f32 * min_aspect + 0.5) as i32;
-                    }
-                }
-
-                if base_is_min {
-                    w -= base_width;
-                    h -= base_height;
-                }
-
-                if inc_width > 0 {
-                    w -= w % inc_width;
-                }
-                if inc_height > 0 {
-                    h -= h % inc_height;
-                }
-
-                w = (w + base_width).max(min_width);
-                h = (h + base_height).max(min_height);
-
-                if max_width > 0 {
-                    w = w.min(max_width);
-                }
-                if max_height > 0 {
-                    h = h.min(max_height);
-                }
-            }
-        }
-
-        let changed = x != client_x || y != client_y || w != client_w || h != client_h;
-        (x, y, w, h, changed)
-    }
-
-    fn next_tiled(&self, start: Option<Window>, monitor: &Monitor) -> Option<Window> {
-        let mut current = start;
-        while let Some(window) = current {
-            if let Some(client) = self.clients.get(&window) {
-                let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index];
-                if visible_tags != 0 && !client.is_floating {
-                    return Some(window);
-                }
-                current = client.next;
-            } else {
-                break;
-            }
-        }
-        None
-    }
-
-    fn next_tagged(&self, start: Option<Window>, tags: u32) -> Option<Window> {
-        let mut current = start;
-        while let Some(window) = current {
-            if let Some(client) = self.clients.get(&window) {
-                let visible_on_tags = (client.tags & tags) != 0;
-                if !client.is_floating && visible_on_tags {
-                    return Some(window);
-                }
-                current = client.next;
-            } else {
-                break;
-            }
-        }
-        None
-    }
-
-    fn attach(&mut self, window: Window, monitor_index: usize) {
-        if let Some(monitor) = self.monitors.get_mut(monitor_index)
-            && let Some(client) = self.clients.get_mut(&window)
-        {
-            client.next = monitor.clients_head;
-            monitor.clients_head = Some(window);
-        }
-    }
-
-    fn attach_after(&mut self, window: Window, after_window: Window, monitor_index: usize) {
-        if let Some(after_client) = self.clients.get(&after_window) {
-            let old_next = after_client.next;
-            if let Some(new_client) = self.clients.get_mut(&window) {
-                new_client.next = old_next;
-            }
-            if let Some(after_client_mut) = self.clients.get_mut(&after_window) {
-                after_client_mut.next = Some(window);
-            }
-        } else {
-            self.attach(window, monitor_index);
-        }
-    }
-
-    fn attach_aside(&mut self, window: Window, monitor_index: usize) {
-        let monitor = match self.monitors.get(monitor_index) {
-            Some(m) => m,
-            None => return,
-        };
-
-        let new_window_tags = self.clients.get(&window).map(|c| c.tags).unwrap_or(0);
-        let first_tagged = self.next_tagged(monitor.clients_head, new_window_tags);
-
-        if first_tagged.is_none() {
-            self.attach(window, monitor_index);
-            return;
-        }
-
-        if let Some(insert_after_window) = first_tagged
-            && let Some(after_client) = self.clients.get(&insert_after_window)
-        {
-            let old_next = after_client.next;
-            if let Some(new_client) = self.clients.get_mut(&window) {
-                new_client.next = old_next;
-            }
-            if let Some(after_client_mut) = self.clients.get_mut(&insert_after_window) {
-                after_client_mut.next = Some(window);
-            }
-        }
-    }
-
-    fn detach(&mut self, window: Window) {
-        let monitor_index = self.clients.get(&window).map(|c| c.monitor_index);
-        if let Some(monitor_index) = monitor_index
-            && let Some(monitor) = self.monitors.get_mut(monitor_index)
-        {
-            if monitor.clients_head == Some(window) {
-                if let Some(client) = self.clients.get(&window) {
-                    monitor.clients_head = client.next;
-                }
-            } else {
-                let mut current = monitor.clients_head;
-                while let Some(current_window) = current {
-                    if let Some(current_client) = self.clients.get(&current_window) {
-                        if current_client.next == Some(window) {
-                            let new_next = self.clients.get(&window).and_then(|c| c.next);
-                            if let Some(current_client_mut) = self.clients.get_mut(&current_window)
-                            {
-                                current_client_mut.next = new_next;
-                            }
-                            break;
-                        }
-                        current = current_client.next;
-                    } else {
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
-    fn attach_stack(&mut self, window: Window, monitor_index: usize) {
-        if let Some(monitor) = self.monitors.get_mut(monitor_index)
-            && let Some(client) = self.clients.get_mut(&window)
-        {
-            client.stack_next = monitor.stack_head;
-            monitor.stack_head = Some(window);
-        }
-    }
-
-    fn detach_stack(&mut self, window: Window) {
-        let monitor_index = self.clients.get(&window).map(|c| c.monitor_index);
-        if let Some(monitor_index) = monitor_index
-            && let Some(monitor) = self.monitors.get_mut(monitor_index)
-        {
-            if monitor.stack_head == Some(window) {
-                if let Some(client) = self.clients.get(&window) {
-                    monitor.stack_head = client.stack_next;
-                }
-                let should_update_selected = monitor.selected_client == Some(window);
-                let mut new_selected: Option<Window> = None;
-                if should_update_selected {
-                    let mut stack_current = monitor.stack_head;
-                    while let Some(stack_window) = stack_current {
-                        if let Some(stack_client) = self.clients.get(&stack_window) {
-                            if self.is_window_visible(stack_window) {
-                                new_selected = Some(stack_window);
-                                break;
-                            }
-                            stack_current = stack_client.stack_next;
-                        } else {
-                            break;
-                        }
-                    }
-                }
-                if should_update_selected
-                    && let Some(monitor) = self.monitors.get_mut(monitor_index)
-                {
-                    monitor.selected_client = new_selected;
-                }
-            } else {
-                let mut current = monitor.stack_head;
-                while let Some(current_window) = current {
-                    if let Some(current_client) = self.clients.get(&current_window) {
-                        if current_client.stack_next == Some(window) {
-                            let new_stack_next =
-                                self.clients.get(&window).and_then(|c| c.stack_next);
-                            if let Some(current_client_mut) = self.clients.get_mut(&current_window)
-                            {
-                                current_client_mut.stack_next = new_stack_next;
-                            }
-                            break;
-                        }
-                        current = current_client.stack_next;
-                    } else {
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
-    fn remove_window(&mut self, window: Window, destroyed: bool) -> WmResult<()> {
-        let initial_count = self.windows.len();
-
-        let focused = self
-            .monitors
-            .get(self.selected_monitor)
-            .and_then(|m| m.selected_client);
-
-        if !destroyed {
-            if let Some(client) = self.clients.get(&window) {
-                let old_border_width = client.old_border_width;
-                self.connection.configure_window(
-                    window,
-                    &ConfigureWindowAux::new().border_width(old_border_width as u32),
-                )?;
-            }
-            self.set_wm_state(window, 0)?;
-        }
-
-        if self.clients.contains_key(&window) {
-            self.detach(window);
-            self.detach_stack(window);
-            self.clients.remove(&window);
-        }
-
-        self.windows.retain(|&w| w != window);
-        self.floating_windows.remove(&window);
-        self.update_client_list()?;
-
-        if self.windows.len() < initial_count {
-            if focused == Some(window) {
-                let visible = self.visible_windows_on_monitor(self.selected_monitor);
-                if let Some(&new_win) = visible.last() {
-                    self.focus(Some(new_win))?;
-                    if self.layout.name() == "scrolling" {
-                        self.scroll_to_window(new_win, true)?;
-                    }
-                } else if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-                    monitor.selected_client = None;
-                }
-            }
-
-            self.apply_layout()?;
-            self.update_bar()?;
-        }
-        Ok(())
-    }
-
-    fn get_selected_monitor(&self) -> &Monitor {
-        &self.monitors[self.selected_monitor]
-    }
-
-    fn has_windows_on_tag(&self, monitor_number: usize, tag_index: usize) -> bool {
-        let Some(monitor) = self.monitors.get(monitor_number) else {
-            return false;
-        };
-
-        let mut current = monitor.clients_head;
-        while let Some(window) = current {
-            let Some(client) = self.clients.get(&window) else {
-                break;
-            };
-
-            if unmask_tag(client.tags) == tag_index {
-                return true;
-            }
-            current = client.next;
-        }
-
-        false
-    }
-
-    fn run_autostart_commands(&self) {
-        for command in &self.config.autostart {
-            crate::signal::spawn_detached(command);
-            eprintln!("[autostart] Spawned: {}", command);
-        }
-    }
-}
diff --git a/src/x11/display.zig b/src/x11/display.zig
new file mode 100644
index 0000000..10bebb8
--- /dev/null
+++ b/src/x11/display.zig
@@ -0,0 +1,100 @@
+const std = @import("std");
+const xlib = @import("xlib.zig");
+
+pub const DisplayError = error{
+    cannot_open_display,
+    another_wm_running,
+};
+
+var wm_detected: bool = false;
+
+pub const Display = struct {
+    handle: *xlib.Display,
+    screen: c_int,
+    root: xlib.Window,
+
+    pub fn open() DisplayError!Display {
+        const handle = xlib.XOpenDisplay(null) orelse return DisplayError.cannot_open_display;
+        const screen = xlib.XDefaultScreen(handle);
+        const root = xlib.XRootWindow(handle, screen);
+
+        return Display{
+            .handle = handle,
+            .screen = screen,
+            .root = root,
+        };
+    }
+
+    pub fn close(self: *Display) void {
+        _ = xlib.XCloseDisplay(self.handle);
+    }
+
+    pub fn become_window_manager(self: *Display) DisplayError!void {
+        wm_detected = false;
+        _ = xlib.XSetErrorHandler(on_wm_detected);
+        _ = xlib.XSelectInput(
+            self.handle,
+            self.root,
+            xlib.SubstructureRedirectMask | xlib.SubstructureNotifyMask | xlib.ButtonPressMask | xlib.PointerMotionMask | xlib.EnterWindowMask,
+        );
+        _ = xlib.XSync(self.handle, xlib.False);
+
+        if (wm_detected) {
+            return DisplayError.another_wm_running;
+        }
+
+        _ = xlib.XSetErrorHandler(on_x_error);
+    }
+
+    pub fn screen_width(self: *Display) c_int {
+        return xlib.XDisplayWidth(self.handle, self.screen);
+    }
+
+    pub fn screen_height(self: *Display) c_int {
+        return xlib.XDisplayHeight(self.handle, self.screen);
+    }
+
+    pub fn next_event(self: *Display) xlib.XEvent {
+        var event: xlib.XEvent = undefined;
+        _ = xlib.XNextEvent(self.handle, &event);
+        return event;
+    }
+
+    pub fn pending(self: *Display) c_int {
+        return xlib.XPending(self.handle);
+    }
+
+    pub fn sync(self: *Display, discard: bool) void {
+        _ = xlib.XSync(self.handle, if (discard) xlib.True else xlib.False);
+    }
+
+    pub fn grab_key(
+        self: *Display,
+        keycode: c_int,
+        modifiers: c_uint,
+    ) void {
+        _ = xlib.XGrabKey(
+            self.handle,
+            keycode,
+            modifiers,
+            self.root,
+            xlib.True,
+            xlib.GrabModeAsync,
+            xlib.GrabModeAsync,
+        );
+    }
+
+    pub fn keysym_to_keycode(self: *Display, keysym: xlib.KeySym) c_int {
+        return @intCast(xlib.XKeysymToKeycode(self.handle, keysym));
+    }
+};
+
+fn on_wm_detected(_: ?*xlib.Display, _: [*c]xlib.XErrorEvent) callconv(.c) c_int {
+    wm_detected = true;
+    return 0;
+}
+
+fn on_x_error(_: ?*xlib.Display, event: [*c]xlib.XErrorEvent) callconv(.c) c_int {
+    std.debug.print("x11 error: request={d} error={d}\n", .{ event.*.request_code, event.*.error_code });
+    return 0;
+}
diff --git a/src/x11/events.zig b/src/x11/events.zig
new file mode 100644
index 0000000..15a3e86
--- /dev/null
+++ b/src/x11/events.zig
@@ -0,0 +1,84 @@
+const std = @import("std");
+const xlib = @import("xlib.zig");
+
+pub const EventType = enum(c_int) {
+    key_press = xlib.KeyPress,
+    key_release = xlib.KeyRelease,
+    button_press = xlib.ButtonPress,
+    button_release = xlib.ButtonRelease,
+    motion_notify = xlib.MotionNotify,
+    enter_notify = xlib.EnterNotify,
+    leave_notify = xlib.LeaveNotify,
+    focus_in = xlib.FocusIn,
+    focus_out = xlib.FocusOut,
+    keymap_notify = xlib.KeymapNotify,
+    expose = xlib.Expose,
+    graphics_expose = xlib.GraphicsExpose,
+    no_expose = xlib.NoExpose,
+    visibility_notify = xlib.VisibilityNotify,
+    create_notify = xlib.CreateNotify,
+    destroy_notify = xlib.DestroyNotify,
+    unmap_notify = xlib.UnmapNotify,
+    map_notify = xlib.MapNotify,
+    map_request = xlib.MapRequest,
+    reparent_notify = xlib.ReparentNotify,
+    configure_notify = xlib.ConfigureNotify,
+    configure_request = xlib.ConfigureRequest,
+    gravity_notify = xlib.GravityNotify,
+    resize_request = xlib.ResizeRequest,
+    circulate_notify = xlib.CirculateNotify,
+    circulate_request = xlib.CirculateRequest,
+    property_notify = xlib.PropertyNotify,
+    selection_clear = xlib.SelectionClear,
+    selection_request = xlib.SelectionRequest,
+    selection_notify = xlib.SelectionNotify,
+    colormap_notify = xlib.ColormapNotify,
+    client_message = xlib.ClientMessage,
+    mapping_notify = xlib.MappingNotify,
+    generic_event = xlib.GenericEvent,
+    _,
+};
+
+pub fn get_event_type(event: *const xlib.XEvent) EventType {
+    return @enumFromInt(event.type);
+}
+
+pub fn event_name(event_type: EventType) []const u8 {
+    return switch (event_type) {
+        .key_press => "key_press",
+        .key_release => "key_release",
+        .button_press => "button_press",
+        .button_release => "button_release",
+        .motion_notify => "motion_notify",
+        .enter_notify => "enter_notify",
+        .leave_notify => "leave_notify",
+        .focus_in => "focus_in",
+        .focus_out => "focus_out",
+        .keymap_notify => "keymap_notify",
+        .expose => "expose",
+        .graphics_expose => "graphics_expose",
+        .no_expose => "no_expose",
+        .visibility_notify => "visibility_notify",
+        .create_notify => "create_notify",
+        .destroy_notify => "destroy_notify",
+        .unmap_notify => "unmap_notify",
+        .map_notify => "map_notify",
+        .map_request => "map_request",
+        .reparent_notify => "reparent_notify",
+        .configure_notify => "configure_notify",
+        .configure_request => "configure_request",
+        .gravity_notify => "gravity_notify",
+        .resize_request => "resize_request",
+        .circulate_notify => "circulate_notify",
+        .circulate_request => "circulate_request",
+        .property_notify => "property_notify",
+        .selection_clear => "selection_clear",
+        .selection_request => "selection_request",
+        .selection_notify => "selection_notify",
+        .colormap_notify => "colormap_notify",
+        .client_message => "client_message",
+        .mapping_notify => "mapping_notify",
+        .generic_event => "generic_event",
+        _ => "unknown",
+    };
+}
diff --git a/src/x11/xlib.zig b/src/x11/xlib.zig
new file mode 100644
index 0000000..50065d1
--- /dev/null
+++ b/src/x11/xlib.zig
@@ -0,0 +1,268 @@
+pub const c = @cImport({
+    @cInclude("X11/Xlib.h");
+    @cInclude("X11/Xutil.h");
+    @cInclude("X11/Xatom.h");
+    @cInclude("X11/cursorfont.h");
+    @cInclude("X11/keysym.h");
+    @cInclude("X11/extensions/Xinerama.h");
+    @cInclude("X11/Xft/Xft.h");
+});
+
+pub const Display = c.Display;
+pub const Window = c.Window;
+pub const XEvent = c.XEvent;
+pub const XWindowAttributes = c.XWindowAttributes;
+pub const XWindowChanges = c.XWindowChanges;
+pub const XMapRequestEvent = c.XMapRequestEvent;
+pub const XConfigureRequestEvent = c.XConfigureRequestEvent;
+pub const XKeyEvent = c.XKeyEvent;
+pub const XDestroyWindowEvent = c.XDestroyWindowEvent;
+pub const XUnmapEvent = c.XUnmapEvent;
+pub const XCrossingEvent = c.XCrossingEvent;
+pub const XFocusChangeEvent = c.XFocusChangeEvent;
+pub const XErrorEvent = c.XErrorEvent;
+pub const KeySym = c.KeySym;
+
+pub const XOpenDisplay = c.XOpenDisplay;
+pub const XCloseDisplay = c.XCloseDisplay;
+pub const XConnectionNumber = c.XConnectionNumber;
+pub const XDefaultScreen = c.XDefaultScreen;
+pub const XRootWindow = c.XRootWindow;
+pub const XDisplayWidth = c.XDisplayWidth;
+pub const XDisplayHeight = c.XDisplayHeight;
+pub const XNextEvent = c.XNextEvent;
+pub const XPending = c.XPending;
+pub const XSync = c.XSync;
+pub const XSelectInput = c.XSelectInput;
+pub const XSetErrorHandler = c.XSetErrorHandler;
+pub const XGrabKey = c.XGrabKey;
+pub const XKeysymToKeycode = c.XKeysymToKeycode;
+pub const XKeycodeToKeysym = c.XKeycodeToKeysym;
+pub const XQueryTree = c.XQueryTree;
+pub const XFree = c.XFree;
+pub const XGetWindowAttributes = c.XGetWindowAttributes;
+pub const XMapWindow = c.XMapWindow;
+pub const XConfigureWindow = c.XConfigureWindow;
+pub const XSetInputFocus = c.XSetInputFocus;
+pub const XRaiseWindow = c.XRaiseWindow;
+pub const XMoveResizeWindow = c.XMoveResizeWindow;
+pub const XMoveWindow = c.XMoveWindow;
+pub const XSetWindowBorder = c.XSetWindowBorder;
+pub const XSetWindowBorderWidth = c.XSetWindowBorderWidth;
+
+pub const SubstructureRedirectMask = c.SubstructureRedirectMask;
+pub const SubstructureNotifyMask = c.SubstructureNotifyMask;
+pub const EnterWindowMask = c.EnterWindowMask;
+pub const FocusChangeMask = c.FocusChangeMask;
+pub const PropertyChangeMask = c.PropertyChangeMask;
+pub const StructureNotifyMask = c.StructureNotifyMask;
+
+pub const Mod4Mask = c.Mod4Mask;
+pub const ShiftMask = c.ShiftMask;
+pub const LockMask = c.LockMask;
+pub const Mod2Mask = c.Mod2Mask;
+pub const ControlMask = c.ControlMask;
+
+pub const GrabModeAsync = c.GrabModeAsync;
+pub const RevertToPointerRoot = c.RevertToPointerRoot;
+pub const CurrentTime = c.CurrentTime;
+pub const NotifyNormal = c.NotifyNormal;
+pub const NotifyInferior = c.NotifyInferior;
+
+pub const True = c.True;
+pub const False = c.False;
+
+pub const XK_q = c.XK_q;
+pub const XK_f = c.XK_f;
+pub const XK_h = c.XK_h;
+pub const XK_i = c.XK_i;
+pub const XK_d = c.XK_d;
+pub const XK_j = c.XK_j;
+pub const XK_k = c.XK_k;
+pub const XK_l = c.XK_l;
+pub const XK_m = c.XK_m;
+pub const XK_comma = c.XK_comma;
+pub const XK_period = c.XK_period;
+pub const XK_space = c.XK_space;
+pub const XK_Return = c.XK_Return;
+pub const XK_p = c.XK_p;
+pub const XK_a = c.XK_a;
+pub const XK_s = c.XK_s;
+pub const XK_1 = c.XK_1;
+pub const XK_2 = c.XK_2;
+pub const XK_3 = c.XK_3;
+pub const XK_4 = c.XK_4;
+pub const XK_5 = c.XK_5;
+pub const XK_6 = c.XK_6;
+pub const XK_7 = c.XK_7;
+pub const XK_8 = c.XK_8;
+pub const XK_9 = c.XK_9;
+
+pub const Mod1Mask = c.Mod1Mask;
+pub const Mod3Mask = c.Mod3Mask;
+pub const Mod5Mask = c.Mod5Mask;
+
+pub const XKillClient = c.XKillClient;
+pub const XInternAtom = c.XInternAtom;
+pub const XChangeProperty = c.XChangeProperty;
+pub const XGetWindowProperty = c.XGetWindowProperty;
+pub const XSendEvent = c.XSendEvent;
+
+pub const Atom = c.Atom;
+pub const XA_ATOM = c.XA_ATOM;
+pub const XClientMessageEvent = c.XClientMessageEvent;
+
+pub const PropModeReplace = c.PropModeReplace;
+
+pub const XGrabPointer = c.XGrabPointer;
+pub const XUngrabPointer = c.XUngrabPointer;
+pub const XGrabButton = c.XGrabButton;
+pub const XQueryPointer = c.XQueryPointer;
+pub const XWarpPointer = c.XWarpPointer;
+pub const XGetModifierMapping = c.XGetModifierMapping;
+pub const XFreeModifiermap = c.XFreeModifiermap;
+pub const XModifierKeymap = c.XModifierKeymap;
+pub const XK_Num_Lock = c.XK_Num_Lock;
+
+pub const Button1 = c.Button1;
+pub const Button1Mask = c.Button1Mask;
+pub const Button3 = c.Button3;
+pub const Button3Mask = c.Button3Mask;
+pub const ButtonPressMask = c.ButtonPressMask;
+pub const ButtonReleaseMask = c.ButtonReleaseMask;
+pub const PointerMotionMask = c.PointerMotionMask;
+pub const GrabModeSync = c.GrabModeSync;
+pub const GrabSuccess = c.GrabSuccess;
+pub const None = c.None;
+
+pub const XButtonEvent = c.XButtonEvent;
+pub const XMotionEvent = c.XMotionEvent;
+pub const XExposeEvent = c.XExposeEvent;
+
+pub const XineramaIsActive = c.XineramaIsActive;
+pub const XineramaQueryScreens = c.XineramaQueryScreens;
+pub const XineramaScreenInfo = c.XineramaScreenInfo;
+
+pub const XftFont = c.XftFont;
+pub const XftColor = c.XftColor;
+pub const XftDraw = c.XftDraw;
+pub const XftFontOpenName = c.XftFontOpenName;
+pub const XftFontClose = c.XftFontClose;
+pub const XftDrawCreate = c.XftDrawCreate;
+pub const XftDrawDestroy = c.XftDrawDestroy;
+pub const XftDrawStringUtf8 = c.XftDrawStringUtf8;
+pub const XftColorAllocValue = c.XftColorAllocValue;
+pub const XftColorFree = c.XftColorFree;
+pub const XftTextExtentsUtf8 = c.XftTextExtentsUtf8;
+pub const XGlyphInfo = c.XGlyphInfo;
+pub const XRenderColor = c.XRenderColor;
+
+pub const XCreatePixmap = c.XCreatePixmap;
+pub const XFreePixmap = c.XFreePixmap;
+pub const XCopyArea = c.XCopyArea;
+pub const XCreateGC = c.XCreateGC;
+pub const XFreeGC = c.XFreeGC;
+pub const XSetForeground = c.XSetForeground;
+pub const XFillRectangle = c.XFillRectangle;
+pub const XDefaultVisual = c.XDefaultVisual;
+pub const XDefaultColormap = c.XDefaultColormap;
+pub const XDefaultDepth = c.XDefaultDepth;
+pub const Pixmap = c.Pixmap;
+pub const Drawable = c.Drawable;
+pub const GC = c.GC;
+pub const Visual = c.Visual;
+pub const Colormap = c.Colormap;
+
+pub const KeyPress = c.KeyPress;
+pub const KeyRelease = c.KeyRelease;
+pub const ButtonPress = c.ButtonPress;
+pub const ButtonRelease = c.ButtonRelease;
+pub const MotionNotify = c.MotionNotify;
+pub const EnterNotify = c.EnterNotify;
+pub const LeaveNotify = c.LeaveNotify;
+pub const FocusIn = c.FocusIn;
+pub const FocusOut = c.FocusOut;
+pub const KeymapNotify = c.KeymapNotify;
+pub const Expose = c.Expose;
+pub const GraphicsExpose = c.GraphicsExpose;
+pub const NoExpose = c.NoExpose;
+pub const VisibilityNotify = c.VisibilityNotify;
+pub const CreateNotify = c.CreateNotify;
+pub const DestroyNotify = c.DestroyNotify;
+pub const UnmapNotify = c.UnmapNotify;
+pub const MapNotify = c.MapNotify;
+pub const MapRequest = c.MapRequest;
+pub const ReparentNotify = c.ReparentNotify;
+pub const ConfigureNotify = c.ConfigureNotify;
+pub const ConfigureRequest = c.ConfigureRequest;
+pub const GravityNotify = c.GravityNotify;
+pub const ResizeRequest = c.ResizeRequest;
+pub const CirculateNotify = c.CirculateNotify;
+pub const CirculateRequest = c.CirculateRequest;
+pub const PropertyNotify = c.PropertyNotify;
+pub const SelectionClear = c.SelectionClear;
+pub const SelectionRequest = c.SelectionRequest;
+pub const SelectionNotify = c.SelectionNotify;
+pub const ColormapNotify = c.ColormapNotify;
+pub const ClientMessage = c.ClientMessage;
+pub const MappingNotify = c.MappingNotify;
+pub const GenericEvent = c.GenericEvent;
+
+pub const XClassHint = c.XClassHint;
+pub const XGetClassHint = c.XGetClassHint;
+pub const XWMHints = c.XWMHints;
+pub const XGetWMHints = c.XGetWMHints;
+pub const XSetWMHints = c.XSetWMHints;
+pub const XSizeHints = c.XSizeHints;
+pub const XGetWMNormalHints = c.XGetWMNormalHints;
+pub const XGetTransientForHint = c.XGetTransientForHint;
+pub const XTextProperty = c.XTextProperty;
+pub const XGetTextProperty = c.XGetTextProperty;
+pub const XmbTextPropertyToTextList = c.XmbTextPropertyToTextList;
+pub const XFreeStringList = c.XFreeStringList;
+pub const Success = c.Success;
+pub const XGetWMProtocols = c.XGetWMProtocols;
+pub const XAllocSizeHints = c.XAllocSizeHints;
+
+pub const XUrgencyHint = c.XUrgencyHint;
+pub const InputHint = c.InputHint;
+pub const PBaseSize = c.PBaseSize;
+pub const PMinSize = c.PMinSize;
+pub const PMaxSize = c.PMaxSize;
+pub const PResizeInc = c.PResizeInc;
+pub const PAspect = c.PAspect;
+pub const PSize = c.PSize;
+
+pub const XA_WM_NAME = c.XA_WM_NAME;
+pub const XA_WINDOW = c.XA_WINDOW;
+pub const XA_STRING = c.XA_STRING;
+pub const PropModeAppend = c.PropModeAppend;
+pub const NoEventMask = c.NoEventMask;
+
+pub const XPropertyEvent = c.XPropertyEvent;
+pub const PropertyDelete = c.PropertyDelete;
+pub const XA_WM_TRANSIENT_FOR = c.XA_WM_TRANSIENT_FOR;
+pub const XA_WM_NORMAL_HINTS = c.XA_WM_NORMAL_HINTS;
+pub const XA_WM_HINTS = c.XA_WM_HINTS;
+
+pub const XDeleteProperty = c.XDeleteProperty;
+pub const XCreateSimpleWindow = c.XCreateSimpleWindow;
+pub const XDestroyWindow = c.XDestroyWindow;
+pub const XGrabServer = c.XGrabServer;
+pub const XUngrabServer = c.XUngrabServer;
+pub const XUngrabButton = c.XUngrabButton;
+pub const XUngrabKey = c.XUngrabKey;
+pub const AnyKey = c.AnyKey;
+pub const AnyModifier = c.AnyModifier;
+
+pub const Cursor = c.Cursor;
+pub const XCreateFontCursor = c.XCreateFontCursor;
+pub const XFreeCursor = c.XFreeCursor;
+pub const XDefineCursor = c.XDefineCursor;
+pub const XC_left_ptr = c.XC_left_ptr;
+pub const XC_sizing = c.XC_sizing;
+pub const XC_fleur = c.XC_fleur;
+
+pub const XAllowEvents = c.XAllowEvents;
+pub const ReplayPointer = c.ReplayPointer;
+pub const AnyButton = c.AnyButton;
diff --git a/tests/config_tests.zig b/tests/config_tests.zig
new file mode 100644
index 0000000..230e971
--- /dev/null
+++ b/tests/config_tests.zig
@@ -0,0 +1,88 @@
+const std = @import("std");
+const testing = std.testing;
+
+const Config = struct {
+    terminal: []const u8 = "st",
+    font: []const u8 = "monospace:size=10",
+    border_width: i32 = 2,
+    border_focused: u32 = 0x6dade3,
+    border_unfocused: u32 = 0x444444,
+    gaps_enabled: bool = true,
+    gap_inner_h: i32 = 5,
+    gap_inner_v: i32 = 5,
+    gap_outer_h: i32 = 5,
+    gap_outer_v: i32 = 5,
+    modkey: u32 = (1 << 6),
+    auto_tile: bool = false,
+    tag_back_and_forth: bool = false,
+    hide_vacant_tags: bool = false,
+};
+
+test "config has correct default terminal" {
+    const cfg = Config{};
+    try testing.expectEqualStrings("st", cfg.terminal);
+}
+
+test "config has correct default font" {
+    const cfg = Config{};
+    try testing.expectEqualStrings("monospace:size=10", cfg.font);
+}
+
+test "config has correct default border width" {
+    const cfg = Config{};
+    try testing.expectEqual(@as(i32, 2), cfg.border_width);
+}
+
+test "config has correct default focused border color" {
+    const cfg = Config{};
+    try testing.expectEqual(@as(u32, 0x6dade3), cfg.border_focused);
+}
+
+test "config has correct default unfocused border color" {
+    const cfg = Config{};
+    try testing.expectEqual(@as(u32, 0x444444), cfg.border_unfocused);
+}
+
+test "config gaps are enabled by default" {
+    const cfg = Config{};
+    try testing.expect(cfg.gaps_enabled);
+}
+
+test "config has correct default gap values" {
+    const cfg = Config{};
+    try testing.expectEqual(@as(i32, 5), cfg.gap_inner_h);
+    try testing.expectEqual(@as(i32, 5), cfg.gap_inner_v);
+    try testing.expectEqual(@as(i32, 5), cfg.gap_outer_h);
+    try testing.expectEqual(@as(i32, 5), cfg.gap_outer_v);
+}
+
+test "config auto_tile is disabled by default" {
+    const cfg = Config{};
+    try testing.expect(!cfg.auto_tile);
+}
+
+test "config tag_back_and_forth is disabled by default" {
+    const cfg = Config{};
+    try testing.expect(!cfg.tag_back_and_forth);
+}
+
+test "config hide_vacant_tags is disabled by default" {
+    const cfg = Config{};
+    try testing.expect(!cfg.hide_vacant_tags);
+}
+
+test "config default modkey is Mod4 (super)" {
+    const cfg = Config{};
+    try testing.expectEqual(@as(u32, 1 << 6), cfg.modkey);
+}
+
+test "can override config defaults" {
+    const cfg = Config{
+        .terminal = "alacritty",
+        .border_width = 3,
+        .gaps_enabled = false,
+    };
+    try testing.expectEqualStrings("alacritty", cfg.terminal);
+    try testing.expectEqual(@as(i32, 3), cfg.border_width);
+    try testing.expect(!cfg.gaps_enabled);
+}
diff --git a/tests/keybind_tests.zig b/tests/keybind_tests.zig
new file mode 100644
index 0000000..ad8814c
--- /dev/null
+++ b/tests/keybind_tests.zig
@@ -0,0 +1,137 @@
+const std = @import("std");
+const testing = std.testing;
+
+const Mod4Mask: u32 = 1 << 6;
+const Mod1Mask: u32 = 1 << 3;
+const ShiftMask: u32 = 1 << 0;
+const ControlMask: u32 = 1 << 2;
+
+fn parse_modifier(name: []const u8) u32 {
+    if (std.mem.eql(u8, name, "Mod4") or std.mem.eql(u8, name, "mod4") or std.mem.eql(u8, name, "super")) {
+        return Mod4Mask;
+    } else if (std.mem.eql(u8, name, "Mod1") or std.mem.eql(u8, name, "mod1") or std.mem.eql(u8, name, "alt")) {
+        return Mod1Mask;
+    } else if (std.mem.eql(u8, name, "Shift") or std.mem.eql(u8, name, "shift")) {
+        return ShiftMask;
+    } else if (std.mem.eql(u8, name, "Control") or std.mem.eql(u8, name, "control") or std.mem.eql(u8, name, "ctrl")) {
+        return ControlMask;
+    }
+    return 0;
+}
+
+fn parse_modifiers(names: []const []const u8) u32 {
+    var mask: u32 = 0;
+    for (names) |name| {
+        mask |= parse_modifier(name);
+    }
+    return mask;
+}
+
+test "parse_modifier: Mod4 variants" {
+    try testing.expectEqual(Mod4Mask, parse_modifier("Mod4"));
+    try testing.expectEqual(Mod4Mask, parse_modifier("mod4"));
+    try testing.expectEqual(Mod4Mask, parse_modifier("super"));
+}
+
+test "parse_modifier: Mod1 (Alt) variants" {
+    try testing.expectEqual(Mod1Mask, parse_modifier("Mod1"));
+    try testing.expectEqual(Mod1Mask, parse_modifier("mod1"));
+    try testing.expectEqual(Mod1Mask, parse_modifier("alt"));
+}
+
+test "parse_modifier: Shift variants" {
+    try testing.expectEqual(ShiftMask, parse_modifier("Shift"));
+    try testing.expectEqual(ShiftMask, parse_modifier("shift"));
+}
+
+test "parse_modifier: Control variants" {
+    try testing.expectEqual(ControlMask, parse_modifier("Control"));
+    try testing.expectEqual(ControlMask, parse_modifier("control"));
+    try testing.expectEqual(ControlMask, parse_modifier("ctrl"));
+}
+
+test "parse_modifier: unknown returns 0" {
+    try testing.expectEqual(@as(u32, 0), parse_modifier("Unknown"));
+    try testing.expectEqual(@as(u32, 0), parse_modifier(""));
+}
+
+test "parse_modifiers: combines multiple modifiers" {
+    const mods = &[_][]const u8{ "Mod4", "Shift" };
+    try testing.expectEqual(Mod4Mask | ShiftMask, parse_modifiers(mods));
+}
+
+test "parse_modifiers: triple combination" {
+    const mods = &[_][]const u8{ "super", "shift", "ctrl" };
+    try testing.expectEqual(Mod4Mask | ShiftMask | ControlMask, parse_modifiers(mods));
+}
+
+test "parse_modifiers: empty list returns 0" {
+    const mods = &[_][]const u8{};
+    try testing.expectEqual(@as(u32, 0), parse_modifiers(mods));
+}
+
+fn key_name_to_keysym(name: []const u8) ?u64 {
+    const key_map = .{
+        .{ "Return", 0xff0d },
+        .{ "Enter", 0xff0d },
+        .{ "Tab", 0xff09 },
+        .{ "Escape", 0xff1b },
+        .{ "BackSpace", 0xff08 },
+        .{ "Delete", 0xffff },
+        .{ "space", 0x0020 },
+        .{ "Space", 0x0020 },
+    };
+
+    inline for (key_map) |entry| {
+        if (std.mem.eql(u8, name, entry[0])) {
+            return entry[1];
+        }
+    }
+
+    if (name.len == 1) {
+        const char = name[0];
+        if (char >= 'a' and char <= 'z') {
+            return char;
+        }
+        if (char >= 'A' and char <= 'Z') {
+            return char + 32;
+        }
+        if (char >= '0' and char <= '9') {
+            return char;
+        }
+    }
+
+    return null;
+}
+
+test "key_name_to_keysym: special keys" {
+    try testing.expectEqual(@as(?u64, 0xff0d), key_name_to_keysym("Return"));
+    try testing.expectEqual(@as(?u64, 0xff0d), key_name_to_keysym("Enter"));
+    try testing.expectEqual(@as(?u64, 0xff09), key_name_to_keysym("Tab"));
+    try testing.expectEqual(@as(?u64, 0xff1b), key_name_to_keysym("Escape"));
+}
+
+test "key_name_to_keysym: lowercase letters" {
+    try testing.expectEqual(@as(?u64, 'a'), key_name_to_keysym("a"));
+    try testing.expectEqual(@as(?u64, 'z'), key_name_to_keysym("z"));
+}
+
+test "key_name_to_keysym: uppercase converts to lowercase" {
+    try testing.expectEqual(@as(?u64, 'a'), key_name_to_keysym("A"));
+    try testing.expectEqual(@as(?u64, 'z'), key_name_to_keysym("Z"));
+}
+
+test "key_name_to_keysym: numbers" {
+    try testing.expectEqual(@as(?u64, '0'), key_name_to_keysym("0"));
+    try testing.expectEqual(@as(?u64, '9'), key_name_to_keysym("9"));
+}
+
+test "key_name_to_keysym: unknown returns null" {
+    try testing.expectEqual(@as(?u64, null), key_name_to_keysym("UnknownKey"));
+    try testing.expectEqual(@as(?u64, null), key_name_to_keysym(""));
+}
+
+test "key_name_to_keysym: space variants" {
+    try testing.expectEqual(@as(?u64, 0x0020), key_name_to_keysym("space"));
+    try testing.expectEqual(@as(?u64, 0x0020), key_name_to_keysym("Space"));
+}
diff --git a/tests/layout_tests.zig b/tests/layout_tests.zig
new file mode 100644
index 0000000..3481b62
--- /dev/null
+++ b/tests/layout_tests.zig
@@ -0,0 +1,138 @@
+const std = @import("std");
+const testing = std.testing;
+
+const Rect = struct {
+    x: i32,
+    y: i32,
+    w: i32,
+    h: i32,
+};
+
+fn calculate_master_stack_layout(
+    area: Rect,
+    num_clients: usize,
+    num_master: usize,
+    master_factor: f32,
+    gap: i32,
+) []Rect {
+    var rects: [16]Rect = undefined;
+    if (num_clients == 0) return rects[0..0];
+
+    const actual_master = @min(num_master, num_clients);
+    const stack_count = if (num_clients > actual_master) num_clients - actual_master else 0;
+
+    const master_width: i32 = if (stack_count > 0)
+        @intFromFloat(@as(f32, @floatFromInt(area.w - gap)) * master_factor)
+    else
+        area.w;
+
+    var i: usize = 0;
+    while (i < actual_master) : (i += 1) {
+        const h = @divTrunc(area.h - @as(i32, @intCast(actual_master - 1)) * gap, @as(i32, @intCast(actual_master)));
+        rects[i] = .{
+            .x = area.x,
+            .y = area.y + @as(i32, @intCast(i)) * (h + gap),
+            .w = master_width,
+            .h = h,
+        };
+    }
+
+    const stack_width = area.w - master_width - gap;
+    var j: usize = 0;
+    while (j < stack_count) : (j += 1) {
+        const h = @divTrunc(area.h - @as(i32, @intCast(stack_count - 1)) * gap, @as(i32, @intCast(stack_count)));
+        rects[actual_master + j] = .{
+            .x = area.x + master_width + gap,
+            .y = area.y + @as(i32, @intCast(j)) * (h + gap),
+            .w = stack_width,
+            .h = h,
+        };
+    }
+
+    return rects[0..num_clients];
+}
+
+test "tiling layout: single window fills area" {
+    const area = Rect{ .x = 0, .y = 0, .w = 800, .h = 600 };
+    const rects = calculate_master_stack_layout(area, 1, 1, 0.55, 5);
+
+    try testing.expectEqual(@as(usize, 1), rects.len);
+    try testing.expectEqual(@as(i32, 0), rects[0].x);
+    try testing.expectEqual(@as(i32, 0), rects[0].y);
+    try testing.expectEqual(@as(i32, 800), rects[0].w);
+    try testing.expectEqual(@as(i32, 600), rects[0].h);
+}
+
+test "tiling layout: two windows split horizontally" {
+    const area = Rect{ .x = 0, .y = 0, .w = 800, .h = 600 };
+    const rects = calculate_master_stack_layout(area, 2, 1, 0.5, 0);
+
+    try testing.expectEqual(@as(usize, 2), rects.len);
+    try testing.expectEqual(@as(i32, 400), rects[0].w);
+    try testing.expectEqual(@as(i32, 400), rects[1].w);
+}
+
+test "tiling layout: respects master factor" {
+    const area = Rect{ .x = 0, .y = 0, .w = 1000, .h = 600 };
+    const rects = calculate_master_stack_layout(area, 2, 1, 0.6, 0);
+
+    try testing.expectEqual(@as(usize, 2), rects.len);
+    try testing.expectEqual(@as(i32, 600), rects[0].w);
+    try testing.expectEqual(@as(i32, 400), rects[1].w);
+}
+
+test "tiling layout: no clients returns empty" {
+    const area = Rect{ .x = 0, .y = 0, .w = 800, .h = 600 };
+    const rects = calculate_master_stack_layout(area, 0, 1, 0.55, 5);
+
+    try testing.expectEqual(@as(usize, 0), rects.len);
+}
+
+test "tiling layout: multiple stack windows divide height" {
+    const area = Rect{ .x = 0, .y = 0, .w = 800, .h = 600 };
+    const rects = calculate_master_stack_layout(area, 3, 1, 0.5, 0);
+
+    try testing.expectEqual(@as(usize, 3), rects.len);
+    try testing.expectEqual(@as(i32, 600), rects[0].h);
+    try testing.expectEqual(@as(i32, 300), rects[1].h);
+    try testing.expectEqual(@as(i32, 300), rects[2].h);
+}
+
+fn calculate_monocle_layout(area: Rect, num_clients: usize) []Rect {
+    var rects: [16]Rect = undefined;
+    if (num_clients == 0) return rects[0..0];
+
+    var i: usize = 0;
+    while (i < num_clients) : (i += 1) {
+        rects[i] = area;
+    }
+    return rects[0..num_clients];
+}
+
+test "monocle layout: all windows get full area" {
+    const area = Rect{ .x = 0, .y = 0, .w = 800, .h = 600 };
+    const rects = calculate_monocle_layout(area, 3);
+
+    try testing.expectEqual(@as(usize, 3), rects.len);
+    for (rects) |rect| {
+        try testing.expectEqual(@as(i32, 0), rect.x);
+        try testing.expectEqual(@as(i32, 0), rect.y);
+        try testing.expectEqual(@as(i32, 800), rect.w);
+        try testing.expectEqual(@as(i32, 600), rect.h);
+    }
+}
+
+test "monocle layout: no clients returns empty" {
+    const area = Rect{ .x = 0, .y = 0, .w = 800, .h = 600 };
+    const rects = calculate_monocle_layout(area, 0);
+
+    try testing.expectEqual(@as(usize, 0), rects.len);
+}
+
+test "monocle layout: respects area offset" {
+    const area = Rect{ .x = 100, .y = 50, .w = 800, .h = 600 };
+    const rects = calculate_monocle_layout(area, 1);
+
+    try testing.expectEqual(@as(i32, 100), rects[0].x);
+    try testing.expectEqual(@as(i32, 50), rects[0].y);
+}
diff --git a/tests/main_tests.zig b/tests/main_tests.zig
new file mode 100644
index 0000000..c007a5c
--- /dev/null
+++ b/tests/main_tests.zig
@@ -0,0 +1,7 @@
+const std = @import("std");
+
+test {
+    _ = @import("config_tests.zig");
+    _ = @import("layout_tests.zig");
+    _ = @import("keybind_tests.zig");
+}