oxwm

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

refactored to allow ron file, and not have to deal with compiling or initting. added ron parser

Commit
5099cb05b65f3023d68011322fe04a9e10c88bb4
Parent
4594114
Author
tonybanters <tony@tonybtw.com>
Date
2025-10-14 23:53:15

Diff

diff --git a/Cargo.lock b/Cargo.lock
index 70c684a..31b4bcb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -23,11 +23,20 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
 [[package]]
 name = "bitflags"
 version = "2.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "bumpalo"
@@ -213,11 +222,13 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "oxwm"
-version = "0.1.11"
+version = "0.1.12"
 dependencies = [
  "anyhow",
  "chrono",
  "dirs",
+ "ron",
+ "serde",
  "x11",
  "x11rb",
 ]
@@ -257,6 +268,18 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "ron"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
+dependencies = [
+ "base64",
+ "bitflags",
+ "serde",
+ "serde_derive",
+]
+
 [[package]]
 name = "rustix"
 version = "1.1.2"
@@ -276,6 +299,36 @@ version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
 
+[[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"
diff --git a/Cargo.toml b/Cargo.toml
index a53158a..581dd72 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "oxwm"
-version = "0.1.11"
+version = "0.1.12"
 edition = "2024"
 
 [lib]
@@ -17,4 +17,5 @@ x11rb = { version = "0.13", features = ["cursor"] }
 anyhow = "1"
 chrono = "0.4"
 dirs = "5.0"
-# xcursor-rs = "0.1"
+serde = { version = "1.0", features = ["derive"] }
+ron = "0.8"
diff --git a/justfile b/justfile
index 463cc02..5f92a0a 100644
--- a/justfile
+++ b/justfile
@@ -5,12 +5,12 @@ install: build
     cp target/release/oxwm ~/.local/bin/oxwm
     chmod +x ~/.local/bin/oxwm
     @echo "✓ oxwm installed to ~/.local/bin/oxwm"
-    @echo "  Run 'oxwm --init' to set up your config"
+    @echo "  Run 'oxwm --init' to create your config"
 
 uninstall:
     rm -f ~/.local/bin/oxwm
     @echo "✓ oxwm uninstalled"
-    @echo "  Your config at ~/.config/oxwm is preserved"
+    @echo "  Your config at ~/.config/oxwm/config.ron is preserved"
 
 clean:
     cargo clean
@@ -23,24 +23,14 @@ test-clean:
 
 test:
 	pkill Xephyr || true
-	rm -f ~/.config/oxwm/oxwm-user
 	Xephyr -screen 1280x800 :1 & sleep 1
 	DISPLAY=:1 cargo run --release
 
-test-user:
-	pkill Xephyr || true
-	cargo run --release -- --recompile
-	Xephyr -screen 1280x800 :1 & sleep 1
-	DISPLAY=:1 ~/.config/oxwm/oxwm-user
-
 init:
-    cargo run -- --init
-
-recompile:
-    cargo run -- --recompile
+    cargo run --release -- --init
 
 edit:
-    $EDITOR ~/.config/oxwm/config.rs
+    $EDITOR ~/.config/oxwm/config.ron
 
 check:
     cargo clippy -- -W clippy::all
diff --git a/src/bin/main.rs b/src/bin/main.rs
index b3b6974..9809a58 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -15,36 +15,13 @@ fn main() -> Result<()> {
         }
         Some("--init") => {
             init_config()?;
-            println!("✓ Config created at ~/.config/oxwm/config.rs");
-            println!("  Edit and reload with Mod+Shift+R");
-            return Ok(());
-        }
-        Some("--recompile") => {
-            recompile_config()?;
             return Ok(());
         }
         _ => {}
     }
 
-    let config_dir = get_config_path();
-    let user_bin = config_dir.join("target/release/oxwm-user");
-
-    if user_bin.exists() {
-        use std::os::unix::process::CommandExt;
-        let err = std::process::Command::new(&user_bin)
-            .args(&args[1..])
-            .exec();
-        eprintln!("Failed to exec user binary: {}", err);
-        std::process::exit(1);
-    }
+    let config = load_config()?;
 
-    let config = oxwm::Config::default();
-    run_wm_with_config(config, &args)?;
-
-    Ok(())
-}
-
-fn run_wm_with_config(config: oxwm::Config, args: &[String]) -> Result<()> {
     let mut wm = oxwm::window_manager::WindowManager::new(config)?;
     let should_restart = wm.run()?;
 
@@ -59,171 +36,57 @@ fn run_wm_with_config(config: oxwm::Config, args: &[String]) -> Result<()> {
     Ok(())
 }
 
-fn should_recompile(config: &PathBuf, binary: &PathBuf) -> Result<bool> {
-    let config_dir = get_config_path();
-    let binary_time = std::fs::metadata(binary)?.modified()?;
-
-    let watch_files = ["config.rs", "Cargo.toml"];
-
-    for filename in &watch_files {
-        let path = config_dir.join(filename);
-        if !path.exists() {
-            continue;
-        }
+fn load_config() -> Result<oxwm::Config> {
+    let config_path = get_config_path().join("config.ron");
 
-        let file_time = std::fs::metadata(&path)?.modified()?;
-        if file_time > binary_time {
-            return Ok(true);
-        }
+    if !config_path.exists() {
+        println!("No config found at {:?}", config_path);
+        println!("Creating default config...");
+        init_config()?;
     }
 
-    Ok(false)
+    let config_str = std::fs::read_to_string(&config_path)
+        .map_err(|e| anyhow::anyhow!("Failed to read config file: {}", e))?;
+
+    oxwm::config::parse_config(&config_str)
+        .map_err(|e| anyhow::anyhow!("Failed to parse config: {}", e))
 }
 
 fn init_config() -> Result<()> {
     let config_dir = get_config_path();
     std::fs::create_dir_all(&config_dir)?;
 
-    let config_template = include_str!("../../templates/config.rs");
-    std::fs::write(config_dir.join("config.rs"), config_template)?;
-
-    let main_template = include_str!("../../templates/main.rs");
-    std::fs::write(config_dir.join("main.rs"), main_template)?;
-
-    let cargo_toml = r#"[package]
-name = "oxwm-user"
-version = "0.1.0"
-edition = "2024"
-
-[dependencies]
-oxwm = { git = "https://github.com/tonybanters/oxwm" }
-anyhow = "1"
-
-[[bin]]
-name = "oxwm-user"
-path = "main.rs"
-"#;
-
-    std::fs::write(config_dir.join("Cargo.toml"), cargo_toml)?;
-    std::fs::write(config_dir.join(".gitignore"), "target/\nCargo.lock\n")?;
-
-    if is_nixos() {
-        let shell_nix = r#"{ pkgs ? import <nixpkgs> {} }:
-pkgs.mkShell {
-  buildInputs = with pkgs; [
-    rustc
-    cargo
-    pkg-config
-    xorg.libX11
-    xorg.libXft
-    xorg.libXrender
-    freetype
-    fontconfig
-  ];
-}
-"#;
-        std::fs::write(config_dir.join("shell.nix"), shell_nix)?;
-        println!("✓ Created shell.nix for NixOS");
-    }
-
-    Ok(())
-}
-
-fn recompile_config() -> Result<()> {
-    let config_dir = get_config_path();
-
-    if !config_dir.join("config.rs").exists() {
-        anyhow::bail!("No config found. Run: oxwm --init");
-    }
+    let config_template = include_str!("../../templates/config.ron");
+    let config_path = config_dir.join("config.ron");
 
-    println!("Compiling oxwm configuration...");
+    std::fs::write(&config_path, config_template)?;
 
-    let build_method = detect_build_method();
-
-    let output = match build_method {
-        BuildMethod::NixFlake => {
-            println!("Using nix flake build...");
-            std::process::Command::new("nix")
-                .args(&["build", ".#", "--no-link"])
-                .current_dir(&config_dir)
-                .output()?
-        }
-        BuildMethod::NixBuild => {
-            println!("Using nix-shell...");
-            std::process::Command::new("nix-shell")
-                .args(&["--run", "cargo build --release"])
-                .current_dir(&config_dir)
-                .output()?
-        }
-        BuildMethod::Cargo => std::process::Command::new("cargo")
-            .args(&["build", "--release"])
-            .current_dir(&config_dir)
-            .output()?,
-    };
-
-    if !output.status.success() {
-        let stderr = String::from_utf8_lossy(&output.stderr);
-        eprintln!("\n❌ Compilation failed:\n{}", stderr);
-        anyhow::bail!("Failed to compile configuration");
-    }
-
-    println!("✓ Compiled successfully.");
-    println!("  Restart oxwm to use new config");
+    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(())
 }
 
-#[derive(Debug)]
-enum BuildMethod {
-    NixFlake,
-    NixBuild,
-    Cargo,
-}
-
-fn detect_build_method() -> BuildMethod {
-    let config_dir = get_config_path();
-
-    if config_dir.join("flake.nix").exists() {
-        println!("Detected flake.nix, will use nix flake for recompilation");
-        return BuildMethod::NixFlake;
-    }
-
-    if config_dir.join("default.nix").exists() || config_dir.join("shell.nix").exists() {
-        println!("Detected nix files, will use nix-shell for recompilation");
-        return BuildMethod::NixBuild;
-    }
-
-    println!("Will use cargo for recompilation");
-    BuildMethod::Cargo
-}
-
-fn is_nixos() -> bool {
-    std::path::Path::new("/etc/NIXOS").exists()
-        || std::path::Path::new("/run/current-system/nixos-version").exists()
-        || std::env::var("NIX_PATH").is_ok()
-}
-
 fn get_config_path() -> PathBuf {
     dirs::config_dir()
         .expect("Could not find config directory")
         .join("oxwm")
 }
 
-fn get_user_binary_path() -> PathBuf {
-    get_config_path().join("oxwm-user")
-}
-
 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");
-    println!("    --recompile    Recompile user configuration");
+    println!("    --init         Create default config in ~/.config/oxwm/config.ron");
     println!("    --version      Print version information");
     println!("    --help         Print this help message\n");
     println!("CONFIG:");
-    println!("    First run: Creates config at ~/.config/oxwm/config.rs");
-    println!("    Edit your config and run 'oxwm --recompile'");
-    println!("    Use Mod+Shift+R to hot-reload after recompiling\n");
+    println!("    Location: ~/.config/oxwm/config.ron");
+    println!("    Edit the config file and use Mod+Shift+R to reload");
+    println!("    No compilation needed - instant hot-reload!\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");
 }
diff --git a/src/bin/main.rs.backup b/src/bin/main.rs.backup
new file mode 100644
index 0000000..b3b6974
--- /dev/null
+++ b/src/bin/main.rs.backup
@@ -0,0 +1,229 @@
+use anyhow::Result;
+use std::path::PathBuf;
+
+fn main() -> Result<()> {
+    let args: Vec<String> = std::env::args().collect();
+
+    match args.get(1).map(|s| s.as_str()) {
+        Some("--version") => {
+            println!("oxwm {}", env!("CARGO_PKG_VERSION"));
+            return Ok(());
+        }
+        Some("--help") => {
+            print_help();
+            return Ok(());
+        }
+        Some("--init") => {
+            init_config()?;
+            println!("✓ Config created at ~/.config/oxwm/config.rs");
+            println!("  Edit and reload with Mod+Shift+R");
+            return Ok(());
+        }
+        Some("--recompile") => {
+            recompile_config()?;
+            return Ok(());
+        }
+        _ => {}
+    }
+
+    let config_dir = get_config_path();
+    let user_bin = config_dir.join("target/release/oxwm-user");
+
+    if user_bin.exists() {
+        use std::os::unix::process::CommandExt;
+        let err = std::process::Command::new(&user_bin)
+            .args(&args[1..])
+            .exec();
+        eprintln!("Failed to exec user binary: {}", err);
+        std::process::exit(1);
+    }
+
+    let config = oxwm::Config::default();
+    run_wm_with_config(config, &args)?;
+
+    Ok(())
+}
+
+fn run_wm_with_config(config: oxwm::Config, args: &[String]) -> Result<()> {
+    let mut wm = oxwm::window_manager::WindowManager::new(config)?;
+    let should_restart = wm.run()?;
+
+    drop(wm);
+
+    if should_restart {
+        use std::os::unix::process::CommandExt;
+        let err = std::process::Command::new(&args[0]).args(&args[1..]).exec();
+        eprintln!("Failed to restart: {}", err);
+    }
+
+    Ok(())
+}
+
+fn should_recompile(config: &PathBuf, binary: &PathBuf) -> Result<bool> {
+    let config_dir = get_config_path();
+    let binary_time = std::fs::metadata(binary)?.modified()?;
+
+    let watch_files = ["config.rs", "Cargo.toml"];
+
+    for filename in &watch_files {
+        let path = config_dir.join(filename);
+        if !path.exists() {
+            continue;
+        }
+
+        let file_time = std::fs::metadata(&path)?.modified()?;
+        if file_time > binary_time {
+            return Ok(true);
+        }
+    }
+
+    Ok(false)
+}
+
+fn init_config() -> Result<()> {
+    let config_dir = get_config_path();
+    std::fs::create_dir_all(&config_dir)?;
+
+    let config_template = include_str!("../../templates/config.rs");
+    std::fs::write(config_dir.join("config.rs"), config_template)?;
+
+    let main_template = include_str!("../../templates/main.rs");
+    std::fs::write(config_dir.join("main.rs"), main_template)?;
+
+    let cargo_toml = r#"[package]
+name = "oxwm-user"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+oxwm = { git = "https://github.com/tonybanters/oxwm" }
+anyhow = "1"
+
+[[bin]]
+name = "oxwm-user"
+path = "main.rs"
+"#;
+
+    std::fs::write(config_dir.join("Cargo.toml"), cargo_toml)?;
+    std::fs::write(config_dir.join(".gitignore"), "target/\nCargo.lock\n")?;
+
+    if is_nixos() {
+        let shell_nix = r#"{ pkgs ? import <nixpkgs> {} }:
+pkgs.mkShell {
+  buildInputs = with pkgs; [
+    rustc
+    cargo
+    pkg-config
+    xorg.libX11
+    xorg.libXft
+    xorg.libXrender
+    freetype
+    fontconfig
+  ];
+}
+"#;
+        std::fs::write(config_dir.join("shell.nix"), shell_nix)?;
+        println!("✓ Created shell.nix for NixOS");
+    }
+
+    Ok(())
+}
+
+fn recompile_config() -> Result<()> {
+    let config_dir = get_config_path();
+
+    if !config_dir.join("config.rs").exists() {
+        anyhow::bail!("No config found. Run: oxwm --init");
+    }
+
+    println!("Compiling oxwm configuration...");
+
+    let build_method = detect_build_method();
+
+    let output = match build_method {
+        BuildMethod::NixFlake => {
+            println!("Using nix flake build...");
+            std::process::Command::new("nix")
+                .args(&["build", ".#", "--no-link"])
+                .current_dir(&config_dir)
+                .output()?
+        }
+        BuildMethod::NixBuild => {
+            println!("Using nix-shell...");
+            std::process::Command::new("nix-shell")
+                .args(&["--run", "cargo build --release"])
+                .current_dir(&config_dir)
+                .output()?
+        }
+        BuildMethod::Cargo => std::process::Command::new("cargo")
+            .args(&["build", "--release"])
+            .current_dir(&config_dir)
+            .output()?,
+    };
+
+    if !output.status.success() {
+        let stderr = String::from_utf8_lossy(&output.stderr);
+        eprintln!("\n❌ Compilation failed:\n{}", stderr);
+        anyhow::bail!("Failed to compile configuration");
+    }
+
+    println!("✓ Compiled successfully.");
+    println!("  Restart oxwm to use new config");
+
+    Ok(())
+}
+
+#[derive(Debug)]
+enum BuildMethod {
+    NixFlake,
+    NixBuild,
+    Cargo,
+}
+
+fn detect_build_method() -> BuildMethod {
+    let config_dir = get_config_path();
+
+    if config_dir.join("flake.nix").exists() {
+        println!("Detected flake.nix, will use nix flake for recompilation");
+        return BuildMethod::NixFlake;
+    }
+
+    if config_dir.join("default.nix").exists() || config_dir.join("shell.nix").exists() {
+        println!("Detected nix files, will use nix-shell for recompilation");
+        return BuildMethod::NixBuild;
+    }
+
+    println!("Will use cargo for recompilation");
+    BuildMethod::Cargo
+}
+
+fn is_nixos() -> bool {
+    std::path::Path::new("/etc/NIXOS").exists()
+        || std::path::Path::new("/run/current-system/nixos-version").exists()
+        || std::env::var("NIX_PATH").is_ok()
+}
+
+fn get_config_path() -> PathBuf {
+    dirs::config_dir()
+        .expect("Could not find config directory")
+        .join("oxwm")
+}
+
+fn get_user_binary_path() -> PathBuf {
+    get_config_path().join("oxwm-user")
+}
+
+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");
+    println!("    --recompile    Recompile user configuration");
+    println!("    --version      Print version information");
+    println!("    --help         Print this help message\n");
+    println!("CONFIG:");
+    println!("    First run: Creates config at ~/.config/oxwm/config.rs");
+    println!("    Edit your config and run 'oxwm --recompile'");
+    println!("    Use Mod+Shift+R to hot-reload after recompiling\n");
+}
diff --git a/src/config.rs.backup b/src/config.rs.backup
deleted file mode 100644
index c02129c..0000000
--- a/src/config.rs.backup
+++ /dev/null
@@ -1,190 +0,0 @@
-use crate::bar::{BlockCommand, BlockConfig};
-use crate::keyboard::handlers::Key;
-use crate::keyboard::{Arg, KeyAction, keycodes};
-use x11rb::protocol::xproto::KeyButMask;
-
-// ========================================
-// APPEARANCE
-// ========================================
-pub const BORDER_WIDTH: u32 = 2;
-pub const BORDER_FOCUSED: u32 = 0x6dade3;
-pub const BORDER_UNFOCUSED: u32 = 0xbbbbbb;
-pub const FONT: &str = "JetBrainsMono Nerd Font:style=Bold:size=12";
-
-// ========================================
-// GAPS (Vanity Gaps)
-// ========================================
-pub const GAPS_ENABLED: bool = false;
-pub const GAP_INNER_HORIZONTAL: u32 = 3;
-pub const GAP_INNER_VERTICAL: u32 = 3;
-pub const GAP_OUTER_HORIZONTAL: u32 = 3;
-pub const GAP_OUTER_VERTICAL: u32 = 3;
-//
-// ========================================
-// DEFAULTS
-// ========================================
-pub const TERMINAL: &str = "st";
-pub const XCLOCK: &str = "xclock";
-pub const MODKEY: KeyButMask = KeyButMask::MOD4;
-
-// ========================================
-// BAR COLORS
-// ========================================
-
-// Base colors
-const GRAY_DARK: u32 = 0x1a1b26;
-const GRAY_SEP: u32 = 0xa9b1d6;
-const GRAY_MID: u32 = 0x444444;
-const GRAY_LIGHT: u32 = 0xbbbbbb;
-const CYAN: u32 = 0x0db9d7;
-const MAGENTA: u32 = 0xad8ee6;
-const RED: u32 = 0xf7768e;
-const GREEN: u32 = 0x9ece6a;
-const BLUE: u32 = 0x7aa2f7;
-const YELLOW: u32 = 0xe0af68;
-
-pub struct ColorScheme {
-    pub foreground: u32,
-    pub background: u32,
-    pub underline: u32,
-}
-
-pub const SCHEME_NORMAL: ColorScheme = ColorScheme {
-    foreground: GRAY_LIGHT,
-    background: GRAY_DARK,
-    underline: GRAY_MID,
-};
-
-pub const SCHEME_OCCUPIED: ColorScheme = ColorScheme {
-    foreground: CYAN,
-    background: GRAY_DARK,
-    underline: CYAN,
-};
-
-pub const SCHEME_SELECTED: ColorScheme = ColorScheme {
-    foreground: CYAN,
-    background: GRAY_DARK,
-    underline: MAGENTA,
-};
-
-// ========================================
-// Commands
-// ========================================
-const SCREENSHOT_CMD: &[&str] = &[
-    "sh",
-    "-c",
-    "maim -s | xclip -selection clipboard -t image/png",
-];
-
-const DMENU_CMD: &[&str] = &["sh", "-c", "dmenu_run -l 10"];
-
-// ========================================
-// TAGS
-// ========================================
-pub const TAG_COUNT: usize = 9;
-pub const TAGS: [&str; TAG_COUNT] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
-//pub const TAGS: [&str; TAG_COUNT] = ["", "󰊯", "", "", "󰙯", "󱇤", "", "󱘶", "󰧮"];
-// pub const TAGS: [&str; TAG_COUNT] = [
-//     "DEV", "WWW", "SYS", "DOC", "VBOX", "CHAT", "MUS", "VID", "MISC",
-// ];
-
-// ========================================
-// KEYBINDINGS
-// ========================================
-#[rustfmt::skip]
-pub const KEYBINDINGS: &[Key] = &[
-    Key::new(&[MODKEY],        keycodes::RETURN, KeyAction::Spawn,      Arg::Str(TERMINAL)),
-    Key::new(&[MODKEY],        keycodes::F,      KeyAction::Spawn,      Arg::Str(XCLOCK)),
-
-    Key::new(&[MODKEY],        keycodes::S,      KeyAction::Spawn,      Arg::Array(SCREENSHOT_CMD)),
-    Key::new(&[MODKEY],        keycodes::D,      KeyAction::Spawn,      Arg::Array(DMENU_CMD)),
-    Key::new(&[MODKEY],        keycodes::Q,      KeyAction::KillClient, Arg::None),
-    Key::new(&[MODKEY, SHIFT], keycodes::F,      KeyAction::ToggleFullScreen, Arg::None),
-    Key::new(&[MODKEY],        keycodes::A,      KeyAction::ToggleGaps, Arg::None),
-    Key::new(&[MODKEY, SHIFT], keycodes::Q,      KeyAction::Quit,       Arg::None),
-    Key::new(&[MODKEY, SHIFT], keycodes::R,      KeyAction::Restart,    Arg::None),
-    Key::new(&[MODKEY],        keycodes::J,      KeyAction::FocusStack, Arg::Int(-1)),
-    Key::new(&[MODKEY],        keycodes::K,      KeyAction::FocusStack, Arg::Int(1)),
-    
-    Key::new(&[MODKEY], keycodes::KEY_1, KeyAction::ViewTag, Arg::Int(0)),
-    Key::new(&[MODKEY], keycodes::KEY_2, KeyAction::ViewTag, Arg::Int(1)),
-    Key::new(&[MODKEY], keycodes::KEY_3, KeyAction::ViewTag, Arg::Int(2)),
-    Key::new(&[MODKEY], keycodes::KEY_4, KeyAction::ViewTag, Arg::Int(3)),
-    Key::new(&[MODKEY], keycodes::KEY_5, KeyAction::ViewTag, Arg::Int(4)),
-    Key::new(&[MODKEY], keycodes::KEY_6, KeyAction::ViewTag, Arg::Int(5)),
-    Key::new(&[MODKEY], keycodes::KEY_7, KeyAction::ViewTag, Arg::Int(6)),
-    Key::new(&[MODKEY], keycodes::KEY_8, KeyAction::ViewTag, Arg::Int(7)),
-    Key::new(&[MODKEY], keycodes::KEY_9, KeyAction::ViewTag, Arg::Int(8)),
-    
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_1, KeyAction::MoveToTag, Arg::Int(0)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_2, KeyAction::MoveToTag, Arg::Int(1)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_3, KeyAction::MoveToTag, Arg::Int(2)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_4, KeyAction::MoveToTag, Arg::Int(3)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_5, KeyAction::MoveToTag, Arg::Int(4)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_6, KeyAction::MoveToTag, Arg::Int(5)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_7, KeyAction::MoveToTag, Arg::Int(6)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_8, KeyAction::MoveToTag, Arg::Int(7)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_9, KeyAction::MoveToTag, Arg::Int(8)),
-];
-
-// ========================================
-// STATUS BAR BLOCKS
-// ========================================
-pub const STATUS_BLOCKS: &[BlockConfig] = &[
-    BlockConfig {
-        format: "",
-        command: BlockCommand::Battery {
-            format_charging: "󰂄 Bat: {}%",
-            format_discharging: "󰁹 Bat:{}%",
-            format_full: "󰁹 Bat: {}%",
-        },
-        interval_secs: 30,
-        color: GREEN,
-        underline: true,
-    },
-    BlockConfig {
-        format: " │  ",
-        command: BlockCommand::Static(""),
-        interval_secs: u64::MAX,
-        color: GRAY_SEP,
-        underline: false,
-    },
-    BlockConfig {
-        format: "󰍛 {used}/{total} GB",
-        command: BlockCommand::Ram,
-        interval_secs: 5,
-        color: BLUE,
-        underline: true,
-    },
-    BlockConfig {
-        format: " │  ",
-        command: BlockCommand::Static(""),
-        interval_secs: u64::MAX,
-        color: GRAY_SEP,
-        underline: false,
-    },
-    BlockConfig {
-        format: " {}",
-        command: BlockCommand::Shell("uname -r"),
-        interval_secs: u64::MAX,
-        color: RED,
-        underline: true,
-    },
-    BlockConfig {
-        format: " │  ",
-        command: BlockCommand::Static(""),
-        interval_secs: u64::MAX,
-        color: GRAY_SEP,
-        underline: false,
-    },
-    BlockConfig {
-        format: "󰸘 {}",
-        command: BlockCommand::DateTime("%a, %b %d - %-I:%M %P"),
-        interval_secs: 1,
-        color: CYAN,
-        underline: true,
-    },
-];
-
-const SHIFT: KeyButMask = KeyButMask::SHIFT;
-pub const WM_BINARY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/target/release/oxwm");
diff --git a/src/config/mod.rs b/src/config/mod.rs
new file mode 100644
index 0000000..01c16b0
--- /dev/null
+++ b/src/config/mod.rs
@@ -0,0 +1,343 @@
+use anyhow::{Context, Result, bail};
+use serde::Deserialize;
+
+pub fn parse_config(input: &str) -> Result<crate::Config> {
+    let config_data: ConfigData = ron::from_str(input).map_err(|e| {
+        eprintln!("RON Parse Error Details: {}", e);
+        anyhow::anyhow!("Failed to parse RON config: {}", e)
+    })?;
+
+    config_data_to_config(config_data)
+}
+
+#[derive(Debug, Deserialize)]
+struct ConfigData {
+    border_width: u32,
+    border_focused: u32,
+    border_unfocused: u32,
+    font: String,
+
+    gaps_enabled: bool,
+    gap_inner_horizontal: u32,
+    gap_inner_vertical: u32,
+    gap_outer_horizontal: u32,
+    gap_outer_vertical: u32,
+
+    terminal: String,
+    modkey: String,
+
+    tags: Vec<String>,
+    keybindings: Vec<KeybindingData>,
+    status_blocks: Vec<StatusBlockData>,
+
+    scheme_normal: ColorSchemeData,
+    scheme_occupied: ColorSchemeData,
+    scheme_selected: ColorSchemeData,
+}
+
+#[derive(Debug, Deserialize)]
+struct KeybindingData {
+    modifiers: Vec<String>,
+    key: String,
+    action: String,
+    #[serde(default)]
+    arg: ArgData,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(untagged)]
+enum ArgData {
+    None,
+    String(String),
+    Int(i32),
+    Array(Vec<String>),
+}
+
+impl Default for ArgData {
+    fn default() -> Self {
+        ArgData::None
+    }
+}
+
+#[derive(Debug, Deserialize)]
+struct StatusBlockData {
+    format: String,
+    command: String,
+    #[serde(default)]
+    command_arg: Option<String>,
+    #[serde(default)]
+    battery_formats: Option<BatteryFormats>,
+    interval_secs: u64,
+    color: u32,
+    underline: bool,
+}
+
+#[derive(Debug, Deserialize)]
+struct BatteryFormats {
+    charging: String,
+    discharging: String,
+    full: String,
+}
+
+#[derive(Debug, Deserialize)]
+struct ColorSchemeData {
+    foreground: u32,
+    background: u32,
+    underline: u32,
+}
+
+fn config_data_to_config(data: ConfigData) -> Result<crate::Config> {
+    use crate::keyboard::handlers::Key;
+    use crate::keyboard::{Arg, KeyAction};
+    use x11rb::protocol::xproto::KeyButMask;
+
+    // Parse modkey
+    let modkey = parse_modkey(&data.modkey)?;
+
+    // Parse keybindings
+    let mut keybindings = Vec::new();
+    for kb_data in data.keybindings {
+        let modifiers = kb_data
+            .modifiers
+            .iter()
+            .map(|s| parse_modkey(s))
+            .collect::<Result<Vec<_>>>()?;
+
+        let modifiers_static: &'static [KeyButMask] = Box::leak(modifiers.into_boxed_slice());
+
+        let key = string_to_keycode(&kb_data.key)?;
+        let action = parse_key_action(&kb_data.action)?;
+        let arg = arg_data_to_arg(kb_data.arg)?;
+
+        keybindings.push(Key::new(modifiers_static, key, action, arg));
+    }
+
+    // Parse status blocks
+    let mut status_blocks = Vec::new();
+    for block_data in data.status_blocks {
+        use crate::bar::{BlockCommand, BlockConfig};
+
+        let format_static: &'static str = Box::leak(block_data.format.into_boxed_str());
+
+        let command = match block_data.command.as_str() {
+            "DateTime" => {
+                let fmt = block_data
+                    .command_arg
+                    .context("DateTime command requires command_arg")?;
+                let fmt_static: &'static str = Box::leak(fmt.into_boxed_str());
+                BlockCommand::DateTime(fmt_static)
+            }
+            "Shell" => {
+                let cmd = block_data
+                    .command_arg
+                    .context("Shell command requires command_arg")?;
+                let cmd_static: &'static str = Box::leak(cmd.into_boxed_str());
+                BlockCommand::Shell(cmd_static)
+            }
+            "Ram" => BlockCommand::Ram,
+            "Static" => {
+                let text = block_data.command_arg.unwrap_or_default();
+                let text_static: &'static str = Box::leak(text.into_boxed_str());
+                BlockCommand::Static(text_static)
+            }
+            "Battery" => {
+                let formats = block_data
+                    .battery_formats
+                    .context("Battery command requires battery_formats")?;
+                BlockCommand::Battery {
+                    format_charging: Box::leak(formats.charging.into_boxed_str()),
+                    format_discharging: Box::leak(formats.discharging.into_boxed_str()),
+                    format_full: Box::leak(formats.full.into_boxed_str()),
+                }
+            }
+            _ => bail!("Unknown block command: {}", block_data.command),
+        };
+
+        status_blocks.push(BlockConfig {
+            format: format_static,
+            command,
+            interval_secs: block_data.interval_secs,
+            color: block_data.color,
+            underline: block_data.underline,
+        });
+    }
+
+    Ok(crate::Config {
+        border_width: data.border_width,
+        border_focused: data.border_focused,
+        border_unfocused: data.border_unfocused,
+        font: data.font,
+        gaps_enabled: data.gaps_enabled,
+        gap_inner_horizontal: data.gap_inner_horizontal,
+        gap_inner_vertical: data.gap_inner_vertical,
+        gap_outer_horizontal: data.gap_outer_horizontal,
+        gap_outer_vertical: data.gap_outer_vertical,
+        terminal: data.terminal,
+        modkey,
+        tags: data.tags,
+        keybindings,
+        status_blocks,
+        scheme_normal: crate::ColorScheme {
+            foreground: data.scheme_normal.foreground,
+            background: data.scheme_normal.background,
+            underline: data.scheme_normal.underline,
+        },
+        scheme_occupied: crate::ColorScheme {
+            foreground: data.scheme_occupied.foreground,
+            background: data.scheme_occupied.background,
+            underline: data.scheme_occupied.underline,
+        },
+        scheme_selected: crate::ColorScheme {
+            foreground: data.scheme_selected.foreground,
+            background: data.scheme_selected.background,
+            underline: data.scheme_selected.underline,
+        },
+    })
+}
+
+fn parse_modkey(s: &str) -> Result<x11rb::protocol::xproto::KeyButMask> {
+    use x11rb::protocol::xproto::KeyButMask;
+
+    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),
+        _ => bail!("Invalid modkey: {}", s),
+    }
+}
+
+fn string_to_keycode(s: &str) -> Result<x11rb::protocol::xproto::Keycode> {
+    use crate::keyboard::keycodes;
+
+    match s.to_lowercase().as_str() {
+        "return" => Ok(keycodes::RETURN),
+        "q" => Ok(keycodes::Q),
+        "escape" => Ok(keycodes::ESCAPE),
+        "space" => Ok(keycodes::SPACE),
+        "tab" => Ok(keycodes::TAB),
+        "backspace" => Ok(keycodes::BACKSPACE),
+        "delete" => Ok(keycodes::DELETE),
+
+        // Function keys
+        "f1" => Ok(keycodes::F1),
+        "f2" => Ok(keycodes::F2),
+        "f3" => Ok(keycodes::F3),
+        "f4" => Ok(keycodes::F4),
+        "f5" => Ok(keycodes::F5),
+        "f6" => Ok(keycodes::F6),
+        "f7" => Ok(keycodes::F7),
+        "f8" => Ok(keycodes::F8),
+        "f9" => Ok(keycodes::F9),
+        "f10" => Ok(keycodes::F10),
+        "f11" => Ok(keycodes::F11),
+        "f12" => Ok(keycodes::F12),
+
+        // Letters
+        "a" => Ok(keycodes::A),
+        "b" => Ok(keycodes::B),
+        "c" => Ok(keycodes::C),
+        "d" => Ok(keycodes::D),
+        "e" => Ok(keycodes::E),
+        "f" => Ok(keycodes::F),
+        "g" => Ok(keycodes::G),
+        "h" => Ok(keycodes::H),
+        "i" => Ok(keycodes::I),
+        "j" => Ok(keycodes::J),
+        "k" => Ok(keycodes::K),
+        "l" => Ok(keycodes::L),
+        "m" => Ok(keycodes::M),
+        "n" => Ok(keycodes::N),
+        "o" => Ok(keycodes::O),
+        "p" => Ok(keycodes::P),
+        "r" => Ok(keycodes::R),
+        "s" => Ok(keycodes::S),
+        "t" => Ok(keycodes::T),
+        "u" => Ok(keycodes::U),
+        "v" => Ok(keycodes::V),
+        "w" => Ok(keycodes::W),
+        "x" => Ok(keycodes::X),
+        "y" => Ok(keycodes::Y),
+        "z" => Ok(keycodes::Z),
+
+        // Numbers
+        "0" => Ok(keycodes::KEY_0),
+        "1" => Ok(keycodes::KEY_1),
+        "2" => Ok(keycodes::KEY_2),
+        "3" => Ok(keycodes::KEY_3),
+        "4" => Ok(keycodes::KEY_4),
+        "5" => Ok(keycodes::KEY_5),
+        "6" => Ok(keycodes::KEY_6),
+        "7" => Ok(keycodes::KEY_7),
+        "8" => Ok(keycodes::KEY_8),
+        "9" => Ok(keycodes::KEY_9),
+
+        // Arrows
+        "left" => Ok(keycodes::LEFT),
+        "right" => Ok(keycodes::RIGHT),
+        "up" => Ok(keycodes::UP),
+        "down" => Ok(keycodes::DOWN),
+        "home" => Ok(keycodes::HOME),
+        "end" => Ok(keycodes::END),
+        "pageup" => Ok(keycodes::PAGE_UP),
+        "pagedown" => Ok(keycodes::PAGE_DOWN),
+        "insert" => Ok(keycodes::INSERT),
+
+        // Symbols
+        "minus" | "-" => Ok(keycodes::MINUS),
+        "equal" | "=" => Ok(keycodes::EQUAL),
+        "bracketleft" | "[" => Ok(keycodes::LEFT_BRACKET),
+        "bracketright" | "]" => Ok(keycodes::RIGHT_BRACKET),
+        "semicolon" | ";" => Ok(keycodes::SEMICOLON),
+        "apostrophe" | "'" => Ok(keycodes::APOSTROPHE),
+        "grave" | "`" => Ok(keycodes::GRAVE),
+        "backslash" | "\\" => Ok(keycodes::BACKSLASH),
+        "comma" | "," => Ok(keycodes::COMMA),
+        "period" | "." => Ok(keycodes::PERIOD),
+        "slash" | "/" => Ok(keycodes::SLASH),
+
+        _ => bail!("Unknown key: {}", s),
+    }
+}
+
+fn parse_key_action(s: &str) -> Result<crate::keyboard::KeyAction> {
+    use crate::keyboard::KeyAction;
+
+    match s {
+        "Spawn" => Ok(KeyAction::Spawn),
+        "KillClient" => Ok(KeyAction::KillClient),
+        "FocusStack" => Ok(KeyAction::FocusStack),
+        "Quit" => Ok(KeyAction::Quit),
+        "Restart" => Ok(KeyAction::Restart),
+        "ViewTag" => Ok(KeyAction::ViewTag),
+        "MoveToTag" => Ok(KeyAction::MoveToTag),
+        "ToggleGaps" => Ok(KeyAction::ToggleGaps),
+        "ToggleFullScreen" => Ok(KeyAction::ToggleFullScreen),
+        "ToggleFloating" => Ok(KeyAction::ToggleFloating),
+        _ => bail!("Unknown action: {}", s),
+    }
+}
+
+fn arg_data_to_arg(data: ArgData) -> Result<crate::keyboard::Arg> {
+    use crate::keyboard::Arg;
+
+    match data {
+        ArgData::None => Ok(Arg::None),
+        ArgData::String(s) => {
+            let static_str: &'static str = Box::leak(s.into_boxed_str());
+            Ok(Arg::Str(static_str))
+        }
+        ArgData::Int(n) => Ok(Arg::Int(n)),
+        ArgData::Array(arr) => {
+            let static_strs: Vec<&'static str> = arr
+                .into_iter()
+                .map(|s| Box::leak(s.into_boxed_str()) as &'static str)
+                .collect();
+            let static_slice: &'static [&'static str] = Box::leak(static_strs.into_boxed_slice());
+            Ok(Arg::Array(static_slice))
+        }
+    }
+}
diff --git a/src/default_config.rs b/src/default_config.rs
deleted file mode 100644
index 98efd35..0000000
--- a/src/default_config.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-use crate::bar::{BlockCommand, BlockConfig};
-use crate::keyboard::handlers::Key;
-use crate::keyboard::{Arg, KeyAction, keycodes};
-use x11rb::protocol::xproto::KeyButMask;
-
-// ========================================
-// APPEARANCE
-// ========================================
-pub const BORDER_WIDTH: u32 = 2;
-pub const BORDER_FOCUSED: u32 = 0x6dade3;
-pub const BORDER_UNFOCUSED: u32 = 0xbbbbbb;
-pub const FONT: &str = "JetBrainsMono Nerd Font:style=Bold:size=12";
-
-// ========================================
-// GAPS (Vanity Gaps)
-// ========================================
-pub const GAPS_ENABLED: bool = false;
-pub const GAP_INNER_HORIZONTAL: u32 = 3;
-pub const GAP_INNER_VERTICAL: u32 = 3;
-pub const GAP_OUTER_HORIZONTAL: u32 = 3;
-pub const GAP_OUTER_VERTICAL: u32 = 3;
-//
-// ========================================
-// DEFAULTS
-// ========================================
-pub const TERMINAL: &str = "st";
-pub const XCLOCK: &str = "xclock";
-pub const MODKEY: KeyButMask = KeyButMask::MOD4;
-
-// ========================================
-// BAR COLORS
-// ========================================
-
-// Base colors
-const GRAY_DARK: u32 = 0x1a1b26;
-const GRAY_SEP: u32 = 0xa9b1d6;
-const GRAY_MID: u32 = 0x444444;
-const GRAY_LIGHT: u32 = 0xbbbbbb;
-const CYAN: u32 = 0x0db9d7;
-const MAGENTA: u32 = 0xad8ee6;
-const RED: u32 = 0xf7768e;
-const GREEN: u32 = 0x9ece6a;
-const BLUE: u32 = 0x7aa2f7;
-const YELLOW: u32 = 0xe0af68;
-
-pub struct ColorScheme {
-    pub foreground: u32,
-    pub background: u32,
-    pub underline: u32,
-}
-
-pub const SCHEME_NORMAL: ColorScheme = ColorScheme {
-    foreground: GRAY_LIGHT,
-    background: GRAY_DARK,
-    underline: GRAY_MID,
-};
-
-pub const SCHEME_OCCUPIED: ColorScheme = ColorScheme {
-    foreground: CYAN,
-    background: GRAY_DARK,
-    underline: CYAN,
-};
-
-pub const SCHEME_SELECTED: ColorScheme = ColorScheme {
-    foreground: CYAN,
-    background: GRAY_DARK,
-    underline: MAGENTA,
-};
-
-// ========================================
-// Commands
-// ========================================
-const SCREENSHOT_CMD: &[&str] = &[
-    "sh",
-    "-c",
-    "maim -s | xclip -selection clipboard -t image/png",
-];
-
-const DMENU_CMD: &[&str] = &["sh", "-c", "dmenu_run -l 10"];
-
-// ========================================
-// TAGS
-// ========================================
-pub const TAG_COUNT: usize = 9;
-pub const TAGS: [&str; TAG_COUNT] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
-//pub const TAGS: [&str; TAG_COUNT] = ["", "󰊯", "", "", "󰙯", "󱇤", "", "󱘶", "󰧮"];
-// pub const TAGS: [&str; TAG_COUNT] = [
-//     "DEV", "WWW", "SYS", "DOC", "VBOX", "CHAT", "MUS", "VID", "MISC",
-// ];
-
-// ========================================
-// KEYBINDINGS
-// ========================================
-#[rustfmt::skip]
-pub const KEYBINDINGS: &[Key] = &[
-    Key::new(&[MODKEY],        keycodes::RETURN, KeyAction::Spawn,      Arg::Str(TERMINAL)),
-    Key::new(&[MODKEY],        keycodes::F,      KeyAction::Spawn,      Arg::Str(XCLOCK)),
-
-    Key::new(&[MODKEY],        keycodes::S,      KeyAction::Spawn,      Arg::Array(SCREENSHOT_CMD)),
-    Key::new(&[MODKEY],        keycodes::D,      KeyAction::Spawn,      Arg::Array(DMENU_CMD)),
-    Key::new(&[MODKEY],        keycodes::Q,      KeyAction::KillClient, Arg::None),
-    Key::new(&[MODKEY, SHIFT], keycodes::F,      KeyAction::ToggleFullScreen, Arg::None),
-    Key::new(&[MODKEY, SHIFT], keycodes::SPACE,  KeyAction::ToggleFloating, Arg::None),
-    Key::new(&[MODKEY],        keycodes::A,      KeyAction::ToggleGaps, Arg::None),
-    Key::new(&[MODKEY, SHIFT], keycodes::Q,      KeyAction::Quit,       Arg::None),
-    Key::new(&[MODKEY, SHIFT], keycodes::R,      KeyAction::Restart,    Arg::None),
-    Key::new(&[MODKEY],        keycodes::J,      KeyAction::FocusStack, Arg::Int(-1)),
-    Key::new(&[MODKEY],        keycodes::K,      KeyAction::FocusStack, Arg::Int(1)),
-    
-    Key::new(&[MODKEY], keycodes::KEY_1, KeyAction::ViewTag, Arg::Int(0)),
-    Key::new(&[MODKEY], keycodes::KEY_2, KeyAction::ViewTag, Arg::Int(1)),
-    Key::new(&[MODKEY], keycodes::KEY_3, KeyAction::ViewTag, Arg::Int(2)),
-    Key::new(&[MODKEY], keycodes::KEY_4, KeyAction::ViewTag, Arg::Int(3)),
-    Key::new(&[MODKEY], keycodes::KEY_5, KeyAction::ViewTag, Arg::Int(4)),
-    Key::new(&[MODKEY], keycodes::KEY_6, KeyAction::ViewTag, Arg::Int(5)),
-    Key::new(&[MODKEY], keycodes::KEY_7, KeyAction::ViewTag, Arg::Int(6)),
-    Key::new(&[MODKEY], keycodes::KEY_8, KeyAction::ViewTag, Arg::Int(7)),
-    Key::new(&[MODKEY], keycodes::KEY_9, KeyAction::ViewTag, Arg::Int(8)),
-    
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_1, KeyAction::MoveToTag, Arg::Int(0)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_2, KeyAction::MoveToTag, Arg::Int(1)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_3, KeyAction::MoveToTag, Arg::Int(2)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_4, KeyAction::MoveToTag, Arg::Int(3)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_5, KeyAction::MoveToTag, Arg::Int(4)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_6, KeyAction::MoveToTag, Arg::Int(5)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_7, KeyAction::MoveToTag, Arg::Int(6)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_8, KeyAction::MoveToTag, Arg::Int(7)),
-    Key::new(&[MODKEY, SHIFT], keycodes::KEY_9, KeyAction::MoveToTag, Arg::Int(8)),
-];
-
-// ========================================
-// STATUS BAR BLOCKS
-// ========================================
-pub const STATUS_BLOCKS: &[BlockConfig] = &[
-    BlockConfig {
-        format: "",
-        command: BlockCommand::Battery {
-            format_charging: "󰂄 Bat: {}%",
-            format_discharging: "󰁹 Bat:{}%",
-            format_full: "󰁹 Bat: {}%",
-        },
-        interval_secs: 30,
-        color: GREEN,
-        underline: true,
-    },
-    BlockConfig {
-        format: " │  ",
-        command: BlockCommand::Static(""),
-        interval_secs: u64::MAX,
-        color: GRAY_SEP,
-        underline: false,
-    },
-    BlockConfig {
-        format: "󰍛 {used}/{total} GB",
-        command: BlockCommand::Ram,
-        interval_secs: 5,
-        color: BLUE,
-        underline: true,
-    },
-    BlockConfig {
-        format: " │  ",
-        command: BlockCommand::Static(""),
-        interval_secs: u64::MAX,
-        color: GRAY_SEP,
-        underline: false,
-    },
-    BlockConfig {
-        format: " {}",
-        command: BlockCommand::Shell("uname -r"),
-        interval_secs: u64::MAX,
-        color: RED,
-        underline: true,
-    },
-    BlockConfig {
-        format: " │  ",
-        command: BlockCommand::Static(""),
-        interval_secs: u64::MAX,
-        color: GRAY_SEP,
-        underline: false,
-    },
-    BlockConfig {
-        format: "󰸘 {}",
-        command: BlockCommand::DateTime("%a, %b %d - %-I:%M %P"),
-        interval_secs: 1,
-        color: CYAN,
-        underline: true,
-    },
-];
-
-const SHIFT: KeyButMask = KeyButMask::SHIFT;
-pub const WM_BINARY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/target/release/oxwm");
diff --git a/src/lib.rs b/src/lib.rs
index 5a16b85..2da26e3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
 pub mod bar;
+pub mod config;
 pub mod errors;
 pub mod keyboard;
 pub mod layout;
@@ -67,7 +68,7 @@ impl Default for Config {
             border_width: 2,
             border_focused: 0x6dade3,
             border_unfocused: 0xbbbbbb,
-            font: "monospace:size=12".to_string(),
+            font: "monospace:size=10".to_string(),
             gaps_enabled: false,
             gap_inner_horizontal: 0,
             gap_inner_vertical: 0,
diff --git a/src/main.rs.backup b/src/main.rs.backup
deleted file mode 100644
index 2e3a92e..0000000
--- a/src/main.rs.backup
+++ /dev/null
@@ -1,23 +0,0 @@
-use anyhow::Result;
-mod bar;
-mod config;
-mod keyboard;
-mod layout;
-mod window_manager;
-
-fn main() -> Result<()> {
-    let args: Vec<String> = std::env::args().collect();
-
-    let mut window_manager = window_manager::WindowManager::new()?;
-    let should_restart = window_manager.run()?;
-
-    drop(window_manager);
-
-    if should_restart {
-        use std::os::unix::process::CommandExt;
-        let err = std::process::Command::new(&args[0]).args(&args[1..]).exec();
-        eprintln!("Failed to restart: {}", err);
-    }
-
-    Ok(())
-}
diff --git a/templates/config.ron b/templates/config.ron
new file mode 100644
index 0000000..0660274
--- /dev/null
+++ b/templates/config.ron
@@ -0,0 +1,66 @@
+#![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: "monospace:style=Bold:size=10",
+    
+    gaps_enabled: true,
+    gap_inner_horizontal: 5,
+    gap_inner_vertical: 5,
+    gap_outer_horizontal: 5,
+    gap_outer_vertical: 5,
+    
+    terminal: "st",
+    modkey: "Mod4",
+    
+    tags: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
+    
+    keybindings: [
+        (modifiers: ["Mod4"], key: "return", action: "Spawn", arg: "st"),
+        (modifiers: ["Mod4"], key: "d", action: "Spawn", arg: ["sh", "-c", "dmenu_run -l 10"]),
+        (modifiers: ["Mod4"], key: "s", action: "Spawn", arg: ["sh", "-c", "maim -s | xclip -selection clipboard -t image/png"]),
+        (modifiers: ["Mod4"], key: "q", action: "KillClient"),
+        (modifiers: ["Mod4", "Shift"], key: "f", action: "ToggleFullScreen"),
+        (modifiers: ["Mod4", "Shift"], key: "space", action: "ToggleFloating"),
+        (modifiers: ["Mod4"], key: "a", action: "ToggleGaps"),
+        (modifiers: ["Mod4", "Shift"], key: "q", action: "Quit"),
+        (modifiers: ["Mod4", "Shift"], key: "r", action: "Restart"),
+        (modifiers: ["Mod4"], key: "j", action: "FocusStack", arg: -1),
+        (modifiers: ["Mod4"], key: "k", action: "FocusStack", arg: 1),
+        (modifiers: ["Mod4"], key: "1", action: "ViewTag", arg: 0),
+        (modifiers: ["Mod4"], key: "2", action: "ViewTag", arg: 1),
+        (modifiers: ["Mod4"], key: "3", action: "ViewTag", arg: 2),
+        (modifiers: ["Mod4"], key: "4", action: "ViewTag", arg: 3),
+        (modifiers: ["Mod4"], key: "5", action: "ViewTag", arg: 4),
+        (modifiers: ["Mod4"], key: "6", action: "ViewTag", arg: 5),
+        (modifiers: ["Mod4"], key: "7", action: "ViewTag", arg: 6),
+        (modifiers: ["Mod4"], key: "8", action: "ViewTag", arg: 7),
+        (modifiers: ["Mod4"], key: "9", action: "ViewTag", arg: 8),
+        (modifiers: ["Mod4", "Shift"], key: "1", action: "MoveToTag", arg: 0),
+        (modifiers: ["Mod4", "Shift"], key: "2", action: "MoveToTag", arg: 1),
+        (modifiers: ["Mod4", "Shift"], key: "3", action: "MoveToTag", arg: 2),
+        (modifiers: ["Mod4", "Shift"], key: "4", action: "MoveToTag", arg: 3),
+        (modifiers: ["Mod4", "Shift"], key: "5", action: "MoveToTag", arg: 4),
+        (modifiers: ["Mod4", "Shift"], key: "6", action: "MoveToTag", arg: 5),
+        (modifiers: ["Mod4", "Shift"], key: "7", action: "MoveToTag", arg: 6),
+        (modifiers: ["Mod4", "Shift"], key: "8", action: "MoveToTag", arg: 7),
+        (modifiers: ["Mod4", "Shift"], key: "9", action: "MoveToTag", arg: 8),
+    ],
+    
+    status_blocks: [
+        (format: "󰍛 {used}/{total} GB", command: "Ram", interval_secs: 5, color: 0x7aa2f7, underline: true),
+        (format: " │  ", command: "Static", interval_secs: 18446744073709551615, color: 0xa9b1d6, underline: false),
+        (format: " {}", command: "Shell", command_arg: "uname -r", interval_secs: 18446744073709551615, color: 0xf7768e, underline: true),
+        (format: " │  ", command: "Static", interval_secs: 18446744073709551615, color: 0xa9b1d6, underline: false),
+        (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),
+)
+