oxwm

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

Switched from RandR to Xinerama for monitor detection to match dwm's approach. Removed OXWM_MODKEY env var since --config flag handles different configs better. Moved test-config.ron into repo for version control. Fixed monitor-specific window operations to prevent actions from crossing monitor boundaries.

Commit
bc73085280a7397e2f3266ac85e8a938874443d9
Parent
7604855
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-10-21 08:27:19

Diff

diff --git a/Cargo.lock b/Cargo.lock
index c4099d2..86cca37 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13,9 +13,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.99"
+version = "1.0.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
 
 [[package]]
 name = "autocfg"
@@ -31,11 +31,11 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
 name = "bitflags"
-version = "2.9.4"
+version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
 dependencies = [
- "serde",
+ "serde_core",
 ]
 
 [[package]]
@@ -46,9 +46,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
 
 [[package]]
 name = "cc"
-version = "1.2.40"
+version = "1.2.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb"
+checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
 dependencies = [
  "find-msvc-tools",
  "shlex",
@@ -56,9 +56,9 @@ dependencies = [
 
 [[package]]
 name = "cfg-if"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
 
 [[package]]
 name = "chrono"
@@ -107,23 +107,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
 dependencies = [
  "libc",
- "windows-sys 0.61.0",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
 name = "find-msvc-tools"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3"
+checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
 
 [[package]]
 name = "gethostname"
-version = "1.0.2"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55"
+checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
 dependencies = [
  "rustix",
- "windows-targets 0.52.6",
+ "windows-link",
 ]
 
 [[package]]
@@ -173,9 +173,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.175"
+version = "0.2.177"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
 
 [[package]]
 name = "libredox"
@@ -290,7 +290,7 @@ dependencies = [
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.61.0",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
@@ -337,9 +337,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "syn"
-version = "2.0.106"
+version = "2.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -368,9 +368,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.19"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
+checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
 
 [[package]]
 name = "wasi"
@@ -439,9 +439,9 @@ dependencies = [
 
 [[package]]
 name = "windows-core"
-version = "0.62.1"
+version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
 dependencies = [
  "windows-implement",
  "windows-interface",
@@ -452,9 +452,9 @@ dependencies = [
 
 [[package]]
 name = "windows-implement"
-version = "0.60.1"
+version = "0.60.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -463,9 +463,9 @@ dependencies = [
 
 [[package]]
 name = "windows-interface"
-version = "0.59.2"
+version = "0.59.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -474,24 +474,24 @@ dependencies = [
 
 [[package]]
 name = "windows-link"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
 
 [[package]]
 name = "windows-result"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
 dependencies = [
  "windows-link",
 ]
 
 [[package]]
 name = "windows-strings"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
 dependencies = [
  "windows-link",
 ]
@@ -502,14 +502,14 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets 0.48.5",
+ "windows-targets",
 ]
 
 [[package]]
 name = "windows-sys"
-version = "0.61.0"
+version = "0.61.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
 dependencies = [
  "windows-link",
 ]
@@ -520,29 +520,13 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
-dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm",
- "windows_i686_msvc 0.52.6",
- "windows_x86_64_gnu 0.52.6",
- "windows_x86_64_gnullvm 0.52.6",
- "windows_x86_64_msvc 0.52.6",
+ "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]]
@@ -551,90 +535,42 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
 [[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_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
-
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
-[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
 [[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_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
-
 [[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_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
 [[package]]
 name = "x11"
 version = "2.21.0"
diff --git a/Cargo.toml b/Cargo.toml
index 5900aa8..c33d0c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ path = "src/bin/main.rs"
 
 [dependencies]
 x11 = { version = "2.21", features = ["xlib", "xft"] }
-x11rb = { version = "0.13", features = ["cursor", "randr"] }
+x11rb = { version = "0.13", features = ["cursor", "xinerama"] }
 anyhow = "1"
 chrono = "0.4"
 dirs = "5.0"
diff --git a/justfile b/justfile
index 5f92a0a..1fbc233 100644
--- a/justfile
+++ b/justfile
@@ -19,12 +19,17 @@ test-clean:
 	pkill Xephyr || true
 	rm -rf ~/.config/oxwm
 	Xephyr -screen 1280x800 :1 & sleep 1
-	DISPLAY=:1 cargo run --release
+	DISPLAY=:1 cargo run --release -- --config test-config.ron
 
 test:
 	pkill Xephyr || true
 	Xephyr -screen 1280x800 :1 & sleep 1
-	DISPLAY=:1 cargo run --release
+	DISPLAY=:1 cargo run --release -- --config test-config.ron
+
+test-multimon:
+	pkill Xephyr || true
+	Xephyr +xinerama -screen 640x480 -screen 640x480 :1 & sleep 1
+	DISPLAY=:1 cargo run --release -- --config test-config.ron
 
 init:
     cargo run --release -- --init
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 9809a58..d4c74f9 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -4,6 +4,8 @@ use std::path::PathBuf;
 fn main() -> Result<()> {
     let args: Vec<String> = std::env::args().collect();
 
+    let mut custom_config_path: Option<PathBuf> = None;
+
     match args.get(1).map(|s| s.as_str()) {
         Some("--version") => {
             println!("oxwm {}", env!("CARGO_PKG_VERSION"));
@@ -17,10 +19,18 @@ fn main() -> Result<()> {
             init_config()?;
             return Ok(());
         }
+        Some("--config") => {
+            if let Some(path) = args.get(2) {
+                custom_config_path = Some(PathBuf::from(path));
+            } else {
+                eprintln!("Error: --config requires a path argument");
+                std::process::exit(1);
+            }
+        }
         _ => {}
     }
 
-    let config = load_config()?;
+    let config = load_config(custom_config_path)?;
 
     let mut wm = oxwm::window_manager::WindowManager::new(config)?;
     let should_restart = wm.run()?;
@@ -36,14 +46,18 @@ fn main() -> Result<()> {
     Ok(())
 }
 
-fn load_config() -> Result<oxwm::Config> {
-    let config_path = get_config_path().join("config.ron");
-
-    if !config_path.exists() {
-        println!("No config found at {:?}", config_path);
-        println!("Creating default config...");
-        init_config()?;
-    }
+fn load_config(custom_path: Option<PathBuf>) -> Result<oxwm::Config> {
+    let config_path = if let Some(path) = custom_path {
+        path
+    } else {
+        let default_path = get_config_path().join("config.ron");
+        if !default_path.exists() {
+            println!("No config found at {:?}", default_path);
+            println!("Creating default config...");
+            init_config()?;
+        }
+        default_path
+    };
 
     let config_str = std::fs::read_to_string(&config_path)
         .map_err(|e| anyhow::anyhow!("Failed to read config file: {}", e))?;
@@ -79,9 +93,10 @@ fn print_help() {
     println!("USAGE:");
     println!("    oxwm [OPTIONS]\n");
     println!("OPTIONS:");
-    println!("    --init         Create default config in ~/.config/oxwm/config.ron");
-    println!("    --version      Print version information");
-    println!("    --help         Print this help message\n");
+    println!("    --init              Create default config in ~/.config/oxwm/config.ron");
+    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.ron");
     println!("    Edit the config file and use Mod+Shift+R to reload");
diff --git a/src/monitor.rs b/src/monitor.rs
index 59d624c..05602aa 100644
--- a/src/monitor.rs
+++ b/src/monitor.rs
@@ -1,5 +1,5 @@
 use crate::errors::WmError;
-use x11rb::protocol::randr::{self, ConnectionExt as _};
+use x11rb::protocol::xinerama::ConnectionExt as _;
 use x11rb::protocol::xproto::{Screen, Window};
 use x11rb::rust_connection::RustConnection;
 
@@ -38,91 +38,43 @@ impl Monitor {
 pub fn detect_monitors(
     connection: &RustConnection,
     screen: &Screen,
-    root: Window,
+    _root: Window,
 ) -> WmResult<Vec<Monitor>> {
-    let randr_version = match connection.randr_query_version(1, 2) {
-        Ok(cookie) => match cookie.reply() {
-            Ok(reply) => reply.major_version >= 1 && reply.minor_version >= 2,
-            Err(_) => false,
-        },
-        Err(_) => false,
-    };
-
-    if !randr_version {
-        eprintln!("RandR 1.2+ not available, using single monitor");
-        return Ok(vec![Monitor::new(
-            0,
-            0,
-            screen.width_in_pixels as u32,
-            screen.height_in_pixels as u32,
-        )]);
-    }
-
-    let resources = match connection.randr_get_screen_resources(root) {
-        Ok(cookie) => match cookie.reply() {
-            Ok(res) => res,
-            Err(_) => {
-                eprintln!("Failed to get screen resources, using single monitor");
-                return Ok(vec![Monitor::new(
-                    0,
-                    0,
-                    screen.width_in_pixels as u32,
-                    screen.height_in_pixels as u32,
-                )]);
-            }
-        },
-        Err(_) => {
-            eprintln!("Failed to query screen resources, using single monitor");
-            return Ok(vec![Monitor::new(
-                0,
-                0,
-                screen.width_in_pixels as u32,
-                screen.height_in_pixels as u32,
-            )]);
-        }
-    };
-
     let mut monitors = Vec::new();
 
-    for &output in &resources.outputs {
-        let output_info = match connection.randr_get_output_info(output, 0) {
-            Ok(cookie) => match cookie.reply() {
-                Ok(info) => info,
-                Err(_) => continue,
-            },
-            Err(_) => continue,
-        };
-
-        if output_info.connection != randr::Connection::CONNECTED {
-            continue;
-        }
-
-        if output_info.crtc == 0 {
-            continue;
-        }
-
-        let crtc_info = match connection.randr_get_crtc_info(output_info.crtc, 0) {
-            Ok(cookie) => match cookie.reply() {
-                Ok(info) => info,
-                Err(_) => continue,
-            },
-            Err(_) => continue,
-        };
-
-        if crtc_info.width == 0 || crtc_info.height == 0 {
-            continue;
+    if let Ok(cookie) = connection.xinerama_is_active() {
+        if let Ok(reply) = cookie.reply() {
+            if reply.state != 0 {
+                if let Ok(screens_cookie) = connection.xinerama_query_screens() {
+                    if let Ok(screens_reply) = screens_cookie.reply() {
+                        for screen_info in &screens_reply.screen_info {
+                            if screen_info.width == 0 || screen_info.height == 0 {
+                                continue;
+                            }
+
+                            let is_unique = !monitors.iter().any(|m: &Monitor| {
+                                m.x == screen_info.x_org as i32
+                                    && m.y == screen_info.y_org as i32
+                                    && m.width == screen_info.width as u32
+                                    && m.height == screen_info.height as u32
+                            });
+
+                            if is_unique {
+                                monitors.push(Monitor::new(
+                                    screen_info.x_org as i32,
+                                    screen_info.y_org as i32,
+                                    screen_info.width as u32,
+                                    screen_info.height as u32,
+                                ));
+                            }
+                        }
+                    }
+                }
+            }
         }
-
-        monitors.push(Monitor::new(
-            crtc_info.x as i32,
-            crtc_info.y as i32,
-            crtc_info.width as u32,
-            crtc_info.height as u32,
-        ));
     }
 
     if monitors.is_empty() {
-        eprintln!("No monitors detected via RandR, using full screen");
         monitors.push(Monitor::new(
             0,
             0,
diff --git a/src/window_manager.rs b/src/window_manager.rs
index e7ada2a..3e6a1ec 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -143,7 +143,7 @@ impl WindowManager {
         let font = crate::bar::font::Font::new(display, screen_number as i32, &config.font)?;
 
         let mut bars = Vec::new();
-        for monitor in &monitors {
+        for monitor in monitors.iter() {
             let bar = Bar::new(
                 &connection,
                 &screen,
@@ -619,7 +619,7 @@ impl WindowManager {
         self.apply_layout()?;
         self.update_bar()?;
 
-        let visible = self.visible_windows();
+        let visible = self.visible_windows_on_monitor(self.selected_monitor);
         if let Some(&win) = visible.first() {
             self.set_focus(win)?;
         }
@@ -910,6 +910,10 @@ impl WindowManager {
                 }
             }
             Event::MotionNotify(event) => {
+                if event.event != self.root {
+                    return Ok(None);
+                }
+
                 if let Some(monitor_index) = self.get_monitor_at_point(event.root_x as i32, event.root_y as i32) {
                     if monitor_index != self.selected_monitor {
                         self.selected_monitor = monitor_index;
@@ -1056,7 +1060,7 @@ impl WindowManager {
         if self.windows.len() < initial_count {
             let focused = self.monitors.get(self.selected_monitor).and_then(|m| m.focused_window);
             if focused == Some(window) {
-                let visible = self.visible_windows();
+                let visible = self.visible_windows_on_monitor(self.selected_monitor);
                 if let Some(&new_win) = visible.last() {
                     self.set_focus(new_win)?;
                 } else if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
diff --git a/test-config.ron b/test-config.ron
new file mode 100644
index 0000000..5425704
--- /dev/null
+++ b/test-config.ron
@@ -0,0 +1,136 @@
+#![enable(implicit_some)]
+// OXWM Configuration File
+// Edit this file and reload with Mod+Shift+R (no compilation needed!)
+
+(
+    border_width: 2,
+    border_focused: 0x6dade3,
+    border_unfocused: 0xbbbbbb,
+    font: "JetBrainsMono Nerd Font:style=Bold:size=12",
+    
+    gaps_enabled: true,
+    gap_inner_horizontal: 5,
+    gap_inner_vertical: 5,
+    gap_outer_horizontal: 5,
+    gap_outer_vertical: 5,
+    
+    terminal: "st",
+    modkey: Mod1,
+
+    tags: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
+
+    // Alternative icon tags (uncomment to use):
+    // tags: ["", "󰊯", "", "", "󰙯", "󱇤", "", "󱘶", "󰧮"],
+    
+    // Alternative text tags (uncomment to use):
+    // tags: ["DEV", "WWW", "SYS", "DOC", "VBOX", "CHAT", "MUS", "VID", "MISC"],
+
+    keybindings: [
+        (modifiers: [Mod1], key: Return, action: Spawn, arg: "st"),
+        (modifiers: [Mod1], key: D, action: Spawn, arg: ["sh", "-c", "dmenu_run -l 10"]),
+        (modifiers: [Mod1], key: S, action: Spawn, arg: ["sh", "-c", "maim -s | xclip -selection clipboard -t image/png"]),
+        (modifiers: [Mod1], key: Q, action: KillClient),
+        (modifiers: [Mod1, Shift], key: F, action: ToggleFullScreen),
+        (modifiers: [Mod1, Shift], key: Space, action: ToggleFloating),
+        (modifiers: [Mod1], key: A, action: ToggleGaps),
+        (modifiers: [Mod1, Shift], key: Q, action: Quit),
+        (modifiers: [Mod1, Shift], key: R, action: Restart),
+        (modifiers: [Mod1], key: J, action: FocusStack, arg: -1),
+        (modifiers: [Mod1], key: K, action: FocusStack, arg: 1),
+        (modifiers: [Mod1], key: Key1, action: ViewTag, arg: 0),
+        (modifiers: [Mod1], key: Key2, action: ViewTag, arg: 1),
+        (modifiers: [Mod1], key: Key3, action: ViewTag, arg: 2),
+        (modifiers: [Mod1], key: Key4, action: ViewTag, arg: 3),
+        (modifiers: [Mod1], key: Key5, action: ViewTag, arg: 4),
+        (modifiers: [Mod1], key: Key6, action: ViewTag, arg: 5),
+        (modifiers: [Mod1], key: Key7, action: ViewTag, arg: 6),
+        (modifiers: [Mod1], key: Key8, action: ViewTag, arg: 7),
+        (modifiers: [Mod1], key: Key9, action: ViewTag, arg: 8),
+        (modifiers: [Mod1, Shift], key: Key1, action: MoveToTag, arg: 0),
+        (modifiers: [Mod1, Shift], key: Key2, action: MoveToTag, arg: 1),
+        (modifiers: [Mod1, Shift], key: Key3, action: MoveToTag, arg: 2),
+        (modifiers: [Mod1, Shift], key: Key4, action: MoveToTag, arg: 3),
+        (modifiers: [Mod1, Shift], key: Key5, action: MoveToTag, arg: 4),
+        (modifiers: [Mod1, Shift], key: Key6, action: MoveToTag, arg: 5),
+        (modifiers: [Mod1, Shift], key: Key7, action: MoveToTag, arg: 6),
+        (modifiers: [Mod1, Shift], key: Key8, action: MoveToTag, arg: 7),
+        (modifiers: [Mod1, Shift], key: Key9, action: MoveToTag, arg: 8),
+    ],
+    
+    status_blocks: [
+        // Battery block (shows charging/discharging status)
+        (
+            format: "",
+            command: "Battery",
+            battery_formats: (
+                charging: "󰂄 Bat: {}%",
+                discharging: "󰁹 Bat:{}%",
+                full: "󰁹 Bat: {}%",
+            ),
+            interval_secs: 30,
+            color: 0x9ece6a,
+            underline: true,
+        ),
+        
+        // Separator
+        (
+            format: " │  ",
+            command: "Static",
+            interval_secs: 18446744073709551615,
+            color: 0xa9b1d6,
+            underline: false,
+        ),
+        
+        // RAM usage
+        (
+            format: "󰍛 {used}/{total} GB",
+            command: "Ram",
+            interval_secs: 5,
+            color: 0x7aa2f7,
+            underline: true,
+        ),
+        
+        // Separator
+        (
+            format: " │  ",
+            command: "Static",
+            interval_secs: 18446744073709551615,
+            color: 0xa9b1d6,
+            underline: false,
+        ),
+        
+        // Kernel version
+        (
+            format: " {}",
+            command: "Shell",
+            command_arg: "uname -r",
+            interval_secs: 18446744073709551615,
+            color: 0xf7768e,
+            underline: true,
+        ),
+        
+        // Separator
+        (
+            format: " │  ",
+            command: "Static",
+            interval_secs: 18446744073709551615,
+            color: 0xa9b1d6,
+            underline: false,
+        ),
+        
+        // Date and time
+        (
+            format: "󰸘 {}",
+            command: "DateTime",
+            command_arg: "%a, %b %d - %-I:%M %P",
+            interval_secs: 1,
+            color: 0x0db9d7,
+            underline: true,
+        ),
+    ],
+    
+    scheme_normal: (foreground: 0xbbbbbb, background: 0x1a1b26, underline: 0x444444),
+    scheme_occupied: (foreground: 0x0db9d7, background: 0x1a1b26, underline: 0x0db9d7),
+    scheme_selected: (foreground: 0x0db9d7, background: 0x1a1b26, underline: 0xad8ee6),
+)
+