oxwm

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

Updated error handling in lua api, and configured restart correctly with super shift r keybind. also handled bar redraw correctly.

Commit
39f847d81215af9360883ac48d990f30eb46122f
Parent
89f2203
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-11-13 04:23:21

Diff

diff --git a/Cargo.lock b/Cargo.lock
index d90c691..e029d9b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -297,7 +297,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "oxwm"
-version = "0.6.0"
+version = "0.7.0"
 dependencies = [
  "anyhow",
  "chrono",
diff --git a/Cargo.toml b/Cargo.toml
index bce4eb0..d1dbed4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "oxwm"
-version = "0.6.0"
+version = "0.7.0"
 edition = "2024"
 
 [lib]
diff --git a/resources/test-config.lua b/resources/test-config.lua
index 4ea370a..f2c3a15 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -6,7 +6,9 @@
 ---Load type definitions for LSP
 ---@module 'oxwm'
 
--- Color palette
+
+-- Set variables as needed
+-- Colors
 local colors = {
     lavender = 0xa9b1d6,
     light_blue = 0x7aa2f7,
@@ -20,9 +22,11 @@ local colors = {
     blue = 0x6dade3,
 }
 
+local modkey = "Mod1";
+
 -- Basic settings
 oxwm.set_terminal("st")
-oxwm.set_modkey("Mod1")
+oxwm.set_modkey(modkey)
 oxwm.set_tags({ "1", "2", "3", "4", "5", "6", "7", "8", "9" })
 
 -- Layout symbol overrides
@@ -51,68 +55,68 @@ oxwm.bar.set_scheme_selected(colors.cyan, colors.bg, colors.purple)
 
 -- Keychord: Mod1+Space then T to spawn terminal
 oxwm.key.chord({
-    { { "Mod1" }, "Space" },
+    { { modkey }, "Space" },
     { {},         "T" }
 }, oxwm.spawn("st"))
 
 -- Basic window management
-oxwm.key.bind({ "Mod1" }, "Return", oxwm.spawn("st"))
-oxwm.key.bind({ "Mod1" }, "D", oxwm.spawn({ "sh", "-c", "dmenu_run -l 10" }))
-oxwm.key.bind({ "Mod1" }, "S", oxwm.spawn({ "sh", "-c", "maim -s | xclip -selection clipboard -t image/png" }))
-oxwm.key.bind({ "Mod1" }, "Q", oxwm.client.kill())
+oxwm.key.bind({ modkey }, "Return", oxwm.spawn("st"))
+oxwm.key.bind({ modkey }, "D", oxwm.spawn({ "sh", "-c", "dmenu_run -l 10" }))
+oxwm.key.bind({ modkey }, "S", oxwm.spawn({ "sh", "-c", "maim -s | xclip -selection clipboard -t image/png" }))
+oxwm.key.bind({ modkey }, "Q", oxwm.client.kill())
 
 -- Keybind overlay
-oxwm.key.bind({ "Mod1", "Shift" }, "Slash", oxwm.show_keybinds())
+oxwm.key.bind({ modkey, "Shift" }, "Slash", oxwm.show_keybinds())
 
 -- Client actions
-oxwm.key.bind({ "Mod1", "Shift" }, "F", oxwm.client.toggle_fullscreen())
-oxwm.key.bind({ "Mod1", "Shift" }, "Space", oxwm.client.toggle_floating())
+oxwm.key.bind({ modkey, "Shift" }, "F", oxwm.client.toggle_fullscreen())
+oxwm.key.bind({ modkey, "Shift" }, "Space", oxwm.client.toggle_floating())
 
 -- Layout management
-oxwm.key.bind({ "Mod1" }, "F", oxwm.layout.set("normie"))
-oxwm.key.bind({ "Mod1" }, "C", oxwm.layout.set("tiling"))
-oxwm.key.bind({ "Mod1" }, "N", oxwm.layout.cycle())
+oxwm.key.bind({ modkey }, "F", oxwm.layout.set("normie"))
+oxwm.key.bind({ modkey }, "C", oxwm.layout.set("tiling"))
+oxwm.key.bind({ modkey }, "N", oxwm.layout.cycle())
 
 -- Gaps toggle
-oxwm.key.bind({ "Mod1" }, "A", oxwm.toggle_gaps())
+oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps())
 
 -- WM controls
-oxwm.key.bind({ "Mod1", "Shift" }, "Q", oxwm.quit())
-oxwm.key.bind({ "Mod1", "Shift" }, "R", oxwm.restart())
+oxwm.key.bind({ modkey, "Shift" }, "Q", oxwm.quit())
+oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart())
 
 -- Focus direction (vim keys)
-oxwm.key.bind({ "Mod1" }, "H", oxwm.client.focus_direction("left"))
-oxwm.key.bind({ "Mod1" }, "J", oxwm.client.focus_direction("down"))
-oxwm.key.bind({ "Mod1" }, "K", oxwm.client.focus_direction("up"))
-oxwm.key.bind({ "Mod1" }, "L", oxwm.client.focus_direction("right"))
+oxwm.key.bind({ modkey }, "H", oxwm.client.focus_direction("left"))
+oxwm.key.bind({ modkey }, "J", oxwm.client.focus_direction("down"))
+oxwm.key.bind({ modkey }, "K", oxwm.client.focus_direction("up"))
+oxwm.key.bind({ modkey }, "L", oxwm.client.focus_direction("right"))
 
 -- Swap windows in direction
-oxwm.key.bind({ "Mod1", "Shift" }, "H", oxwm.client.swap_direction("left"))
-oxwm.key.bind({ "Mod1", "Shift" }, "J", oxwm.client.swap_direction("down"))
-oxwm.key.bind({ "Mod1", "Shift" }, "K", oxwm.client.swap_direction("up"))
-oxwm.key.bind({ "Mod1", "Shift" }, "L", oxwm.client.swap_direction("right"))
+oxwm.key.bind({ modkey, "Shift" }, "H", oxwm.client.swap_direction("left"))
+oxwm.key.bind({ modkey, "Shift" }, "J", oxwm.client.swap_direction("down"))
+oxwm.key.bind({ modkey, "Shift" }, "K", oxwm.client.swap_direction("up"))
+oxwm.key.bind({ modkey, "Shift" }, "L", oxwm.client.swap_direction("right"))
 
 -- Tag viewing
-oxwm.key.bind({ "Mod1" }, "1", oxwm.tag.view(0))
-oxwm.key.bind({ "Mod1" }, "2", oxwm.tag.view(1))
-oxwm.key.bind({ "Mod1" }, "3", oxwm.tag.view(2))
-oxwm.key.bind({ "Mod1" }, "4", oxwm.tag.view(3))
-oxwm.key.bind({ "Mod1" }, "5", oxwm.tag.view(4))
-oxwm.key.bind({ "Mod1" }, "6", oxwm.tag.view(5))
-oxwm.key.bind({ "Mod1" }, "7", oxwm.tag.view(6))
-oxwm.key.bind({ "Mod1" }, "8", oxwm.tag.view(7))
-oxwm.key.bind({ "Mod1" }, "9", oxwm.tag.view(8))
+oxwm.key.bind({ modkey }, "1", oxwm.tag.view(0))
+oxwm.key.bind({ modkey }, "2", oxwm.tag.view(1))
+oxwm.key.bind({ modkey }, "3", oxwm.tag.view(2))
+oxwm.key.bind({ modkey }, "4", oxwm.tag.view(3))
+oxwm.key.bind({ modkey }, "5", oxwm.tag.view(4))
+oxwm.key.bind({ modkey }, "6", oxwm.tag.view(5))
+oxwm.key.bind({ modkey }, "7", oxwm.tag.view(6))
+oxwm.key.bind({ modkey }, "8", oxwm.tag.view(7))
+oxwm.key.bind({ modkey }, "9", oxwm.tag.view(8))
 
 -- Move window to tag
-oxwm.key.bind({ "Mod1", "Shift" }, "1", oxwm.tag.move_to(0))
-oxwm.key.bind({ "Mod1", "Shift" }, "2", oxwm.tag.move_to(1))
-oxwm.key.bind({ "Mod1", "Shift" }, "3", oxwm.tag.move_to(2))
-oxwm.key.bind({ "Mod1", "Shift" }, "4", oxwm.tag.move_to(3))
-oxwm.key.bind({ "Mod1", "Shift" }, "5", oxwm.tag.move_to(4))
-oxwm.key.bind({ "Mod1", "Shift" }, "6", oxwm.tag.move_to(5))
-oxwm.key.bind({ "Mod1", "Shift" }, "7", oxwm.tag.move_to(6))
-oxwm.key.bind({ "Mod1", "Shift" }, "8", oxwm.tag.move_to(7))
-oxwm.key.bind({ "Mod1", "Shift" }, "9", oxwm.tag.move_to(8))
+oxwm.key.bind({ modkey, "Shift" }, "1", oxwm.tag.move_to(0))
+oxwm.key.bind({ modkey, "Shift" }, "2", oxwm.tag.move_to(1))
+oxwm.key.bind({ modkey, "Shift" }, "3", oxwm.tag.move_to(2))
+oxwm.key.bind({ modkey, "Shift" }, "4", oxwm.tag.move_to(3))
+oxwm.key.bind({ modkey, "Shift" }, "5", oxwm.tag.move_to(4))
+oxwm.key.bind({ modkey, "Shift" }, "6", oxwm.tag.move_to(5))
+oxwm.key.bind({ modkey, "Shift" }, "7", oxwm.tag.move_to(6))
+oxwm.key.bind({ modkey, "Shift" }, "8", oxwm.tag.move_to(7))
+oxwm.key.bind({ modkey, "Shift" }, "9", oxwm.tag.move_to(8))
 
 -- Status bar blocks
 oxwm.bar.add_block("", "Battery", {
diff --git a/src/bar/bar.rs b/src/bar/bar.rs
index 652d5df..756b998 100644
--- a/src/bar/bar.rs
+++ b/src/bar/bar.rs
@@ -347,4 +347,28 @@ impl Bar {
     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.status_text.clear();
+        self.needs_redraw = true;
+    }
 }
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 519965b..484fa22 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -54,7 +54,6 @@ fn load_config(custom_path: Option<PathBuf>) -> Result<oxwm::Config> {
         let lua_path = config_dir.join("config.lua");
 
         if !lua_path.exists() {
-            // Check if user had an old RON config
             let ron_path = config_dir.join("config.ron");
             let had_ron_config = ron_path.exists();
 
@@ -63,7 +62,7 @@ fn load_config(custom_path: Option<PathBuf>) -> Result<oxwm::Config> {
             init_config()?;
 
             if had_ron_config {
-                println!("\n⚠️  NOTICE: OXWM has migrated to Lua configuration.");
+                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.");
@@ -86,12 +85,10 @@ fn init_config() -> Result<()> {
     let config_dir = get_config_path();
     std::fs::create_dir_all(&config_dir)?;
 
-    // Copy config.lua template
     let config_template = include_str!("../../templates/config.lua");
     let config_path = config_dir.join("config.lua");
     std::fs::write(&config_path, config_template)?;
 
-    // Copy oxwm.lua API definitions for LSP support
     let oxwm_lua_template = include_str!("../../templates/oxwm.lua");
     let oxwm_lua_path = config_dir.join("oxwm.lua");
     std::fs::write(&oxwm_lua_path, oxwm_lua_template)?;
diff --git a/src/config/lua.rs b/src/config/lua.rs
index 74a0973..49a8fcb 100644
--- a/src/config/lua.rs
+++ b/src/config/lua.rs
@@ -28,7 +28,7 @@ pub fn parse_lua_config(
 
     lua.load(input)
         .exec()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to execute Lua config: {}", e)))?;
+        .map_err(|e| ConfigError::LuaError(format!("{}", e)))?;
 
     let builder_data = builder.borrow().clone();
 
@@ -59,7 +59,7 @@ pub fn parse_lua_config(
     let config: Table = lua
         .load(input)
         .eval()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to execute Lua config: {}", e)))?;
+        .map_err(|e| ConfigError::LuaError(format!("{}", e)))?;
     let border_width: u32 = get_table_field(&config, "border_width")?;
     let border_focused: u32 = parse_color(&config, "border_focused")?;
     let border_unfocused: u32 = parse_color(&config, "border_unfocused")?;
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
index d1c1840..a7b535e 100644
--- a/src/config/lua_api.rs
+++ b/src/config/lua_api.rs
@@ -363,7 +363,7 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
                     } else {
                         None
                     }
-                }).ok_or_else(|| mlua::Error::RuntimeError("DateTime requires format string".into()))?;
+                }).ok_or_else(|| mlua::Error::RuntimeError("oxwm.bar.add_block: DateTime command requires a format string as the third argument. example: oxwm.bar.add_block(\"\", \"DateTime\", \"%H:%M\", 60, 0xffffff, false)".into()))?;
                 BlockCommand::DateTime(fmt)
             }
             "Shell" => {
@@ -373,7 +373,7 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
                     } else {
                         None
                     }
-                }).ok_or_else(|| mlua::Error::RuntimeError("Shell requires command string".into()))?;
+                }).ok_or_else(|| mlua::Error::RuntimeError("oxwm.bar.add_block: Shell command requires a shell command string as the third argument. example: oxwm.bar.add_block(\"\", \"Shell\", \"date +%H:%M\", 60, 0xffffff, false)".into()))?;
                 BlockCommand::Shell(cmd_str)
             }
             "Ram" => BlockCommand::Ram,
@@ -394,7 +394,7 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
                     } else {
                         None
                     }
-                }).ok_or_else(|| mlua::Error::RuntimeError("Battery requires formats table".into()))?;
+                }).ok_or_else(|| mlua::Error::RuntimeError("oxwm.bar.add_block: Battery command requires a formats table as the third argument. example: {charging=\"CHR {percentage}%\", discharging=\"BAT {percentage}%\", full=\"FULL\"}".into()))?;
 
                 let charging: String = formats.get("charging")?;
                 let discharging: String = formats.get("discharging")?;
@@ -406,7 +406,7 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
                     format_full: full,
                 }
             }
-            _ => return Err(mlua::Error::RuntimeError(format!("Unknown block command: {}", command))),
+            _ => return Err(mlua::Error::RuntimeError(format!("oxwm.bar.add_block: unknown block command '{}'. valid commands: DateTime, Shell, Ram, Static, Battery", command))),
         };
 
         let color_u32 = parse_color_value(color)?;
@@ -572,7 +572,7 @@ fn parse_modifiers_value(_lua: &Lua, value: Value) -> mlua::Result<Vec<KeyButMas
             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!("{}", e)))?;
+                    .map_err(|e| mlua::Error::RuntimeError(format!("oxwm.key.bind: invalid modifier - {}", e)))?;
                 mods.push(mask);
             }
             Ok(mods)
@@ -580,11 +580,11 @@ fn parse_modifiers_value(_lua: &Lua, value: Value) -> mlua::Result<Vec<KeyButMas
         Value::String(s) => {
             let s_str = s.to_str()?;
             let mask = parse_modkey_string(&s_str)
-                .map_err(|e| mlua::Error::RuntimeError(format!("{}", e)))?;
+                .map_err(|e| mlua::Error::RuntimeError(format!("oxwm.key.bind: invalid modifier - {}", e)))?;
             Ok(vec![mask])
         }
         _ => Err(mlua::Error::RuntimeError(
-            "modifiers must be string or table".into(),
+            "oxwm.key.bind: first argument must be a table of modifiers like {\"Mod4\"} or {\"Mod4\", \"Shift\"}".into(),
         )),
     }
 }
@@ -598,19 +598,23 @@ fn parse_modkey_string(s: &str) -> Result<KeyButMask, ConfigError> {
         "Mod5" => Ok(KeyButMask::MOD5),
         "Shift" => Ok(KeyButMask::SHIFT),
         "Control" => Ok(KeyButMask::CONTROL),
-        _ => Err(ConfigError::InvalidModkey(s.to_string())),
+        _ => 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: {}", 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) => {
-            // Check if this table has our action metadata
             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") {
@@ -622,11 +626,11 @@ fn parse_action_value(_lua: &Lua, value: Value) -> mlua::Result<(KeyAction, Arg)
             }
 
             Err(mlua::Error::RuntimeError(
-                "action table missing __action field".into(),
+                "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 actions".into(),
+            "action must be a table returned by oxwm functions like oxwm.spawn(), oxwm.client.kill(), oxwm.quit(), etc.".into(),
         )),
     }
 }
@@ -652,7 +656,7 @@ fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
         "SmartMoveWin" => Ok(KeyAction::SmartMoveWin),
         "ExchangeClient" => Ok(KeyAction::ExchangeClient),
         "ShowKeybindOverlay" => Ok(KeyAction::ShowKeybindOverlay),
-        _ => Err(mlua::Error::RuntimeError(format!("Unknown action: {}", s))),
+        _ => Err(mlua::Error::RuntimeError(format!("unknown action '{}'. this is an internal error, please report it", s))),
     }
 }
 
@@ -688,7 +692,7 @@ fn direction_string_to_int(dir: &str) -> mlua::Result<i64> {
         "left" => Ok(2),
         "right" => Ok(3),
         _ => Err(mlua::Error::RuntimeError(
-            format!("Invalid direction '{}', must be one of: up, down, left, right", dir)
+            format!("invalid direction '{}'. must be one of: up, down, left, right", dir)
         )),
     }
 }
@@ -701,17 +705,17 @@ fn parse_color_value(value: Value) -> mlua::Result<u32> {
             let s = s.to_str()?;
             if s.starts_with('#') {
                 u32::from_str_radix(&s[1..], 16)
-                    .map_err(|e| mlua::Error::RuntimeError(format!("Invalid hex color: {}", e)))
+                    .map_err(|e| mlua::Error::RuntimeError(format!("invalid hex color '{}': {}. use format like #ff0000 or 0xff0000", s, e)))
             } else if s.starts_with("0x") {
                 u32::from_str_radix(&s[2..], 16)
-                    .map_err(|e| mlua::Error::RuntimeError(format!("Invalid hex color: {}", e)))
+                    .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: {}", e)))
+                    .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 number or string".into(),
+            "color must be a number (0xff0000) or string ('#ff0000' or '0xff0000')".into(),
         )),
     }
 }
diff --git a/src/errors.rs b/src/errors.rs
index 256ae38..f2286e2 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -73,15 +73,15 @@ impl std::error::Error for X11Error {}
 impl std::fmt::Display for ConfigError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            Self::LuaError(msg) => write!(f, "Lua config error: {}", msg),
-            Self::InvalidModkey(key) => write!(f, "Invalid modkey: {}", key),
-            Self::UnknownKey(key) => write!(f, "Unknown key: {}", key),
-            Self::UnknownAction(action) => write!(f, "Unknown action: {}", action),
-            Self::UnknownBlockCommand(cmd) => write!(f, "Unknown block command: {}", cmd),
+            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, "Config validation error: {}", msg),
+            Self::ValidationError(msg) => write!(f, "{}", msg),
         }
     }
 }
diff --git a/src/overlay/error.rs b/src/overlay/error.rs
index 42ef9cc..256c584 100644
--- a/src/overlay/error.rs
+++ b/src/overlay/error.rs
@@ -55,8 +55,9 @@ impl ErrorOverlay {
         screen_width: u16,
         screen_height: u16,
     ) -> Result<(), X11Error> {
-        let max_line_width = (screen_width as i16 - PADDING * 4).max(300) as u16;
-        self.lines = self.wrap_text(error_text, font, max_line_width);
+        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_text);
+        self.lines = self.wrap_text(&error_with_instruction, font, max_line_width);
 
         let mut content_width = 0u16;
         for line in &self.lines {
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 8e68e30..ed717b1 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -263,10 +263,15 @@ impl WindowManager {
             .map_err(|e| format!("Failed to read config: {}", e))?;
 
         let new_config = crate::config::parse_lua_config(&config_str, Some(&config_dir))
-            .map_err(|e| format!("Config error: {}", e))?;
+            .map_err(|e| format!("{}", e))?;
 
         self.config = new_config;
         self.error_message = None;
+
+        for bar in &mut self.bars {
+            bar.update_from_config(&self.config);
+        }
+
         Ok(())
     }
 
@@ -1773,16 +1778,20 @@ impl WindowManager {
                                     self.update_bar()?;
                                 }
                                 Err(err) => {
+                                    eprintln!("Config reload error: {}", err);
                                     self.error_message = Some(err.clone());
                                     let screen_width = self.screen.width_in_pixels;
                                     let screen_height = self.screen.height_in_pixels;
-                                    let _ = self.overlay.show_error(
+                                    match self.overlay.show_error(
                                         &self.connection,
                                         &self.font,
                                         &err,
                                         screen_width,
                                         screen_height,
-                                    );
+                                    ) {
+                                        Ok(()) => eprintln!("Error modal displayed"),
+                                        Err(e) => eprintln!("Failed to show error modal: {:?}", e),
+                                    }
                                 }
                             },
                             _ => self.handle_key_action(action, &arg)?,
diff --git a/templates/config.lua b/templates/config.lua
index 08e1523..f73a125 100644
--- a/templates/config.lua
+++ b/templates/config.lua
@@ -1,12 +1,30 @@
 ---@meta
----OXWM Configuration File (Lua)
----Using the new functional API
----Edit this file and reload with Mod+Shift+R (no compilation needed!)
+-------------------------------------------------------------------------------
+-- OXWM Configuration File
+-------------------------------------------------------------------------------
+-- This is the default configuration for OXWM, a dynamic window manager.
+-- Edit this file and reload with Mod+Shift+R (no compilation needed!)
+--
+-- For more information about configuring OXWM, see the documentation.
+-- The Lua Language Server provides autocomplete and type checking.
+-------------------------------------------------------------------------------
 
 ---Load type definitions for LSP
 ---@module 'oxwm'
 
--- Color palette
+-------------------------------------------------------------------------------
+-- Variables
+-------------------------------------------------------------------------------
+-- Define your variables here for easy customization throughout the config.
+-- This makes it simple to change keybindings, colors, and settings in one place.
+
+-- Modifier key: "Mod4" is the Super/Windows key, "Mod1" is Alt
+local modkey = "Mod4"
+
+-- Terminal emulator command
+local terminal = "st"
+
+-- Color palette - customize these to match your theme
 local colors = {
     fg = "#bbbbbb",
     red = "#f7768e",
@@ -20,111 +38,170 @@ local colors = {
     purple = "#ad8ee6",
 }
 
--- Basic settings
-oxwm.set_terminal("st")
-oxwm.set_modkey("Mod4")
-oxwm.set_tags({ "1", "2", "3", "4", "5", "6", "7", "8", "9" })
+-- Workspace tags - can be numbers, names, or icons (requires a Nerd Font)
+local tags = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }
+
+-- Font for the status bar (use "fc-list" to see available fonts)
+local bar_font = "monospace:style=Bold:size=10"
+
+-------------------------------------------------------------------------------
+-- Basic Settings
+-------------------------------------------------------------------------------
+oxwm.set_terminal(terminal)
+oxwm.set_modkey(modkey)
+oxwm.set_tags(tags)
 
--- Layout symbol overrides
+-------------------------------------------------------------------------------
+-- Layouts
+-------------------------------------------------------------------------------
+-- Set custom symbols for layouts (displayed in the status bar)
+-- Available layouts: "tiling" (master-stack), "normie" (floating)
 oxwm.set_layout_symbol("tiling", "[T]")
 oxwm.set_layout_symbol("normie", "[F]")
 
+-------------------------------------------------------------------------------
+-- Appearance
+-------------------------------------------------------------------------------
 -- Border configuration
-oxwm.border.set_width(2)
-oxwm.border.set_focused_color(colors.blue)
-oxwm.border.set_unfocused_color(colors.grey)
-
--- Gap configuration
-oxwm.gaps.set_enabled(true)
-oxwm.gaps.set_inner(5, 5) -- horizontal, vertical
-oxwm.gaps.set_outer(5, 5) -- horizontal, vertical
-
--- Bar configuration
-oxwm.bar.set_font("monospace:style=Bold:size=10")
-
--- Bar color schemes (for tag display)
-oxwm.bar.set_scheme_normal(colors.fg, colors.bg, "#444444")
-oxwm.bar.set_scheme_occupied(colors.cyan, colors.bg, colors.cyan)
-oxwm.bar.set_scheme_selected(colors.cyan, colors.bg, colors.purple)
-
+oxwm.border.set_width(2)                        -- Width in pixels
+oxwm.border.set_focused_color(colors.blue)      -- Color of focused window border
+oxwm.border.set_unfocused_color(colors.grey)    -- Color of unfocused window borders
+
+-- Gap configuration (space between windows and screen edges)
+oxwm.gaps.set_enabled(true)                     -- Enable or disable gaps
+oxwm.gaps.set_inner(5, 5)                       -- Inner gaps (horizontal, vertical) in pixels
+oxwm.gaps.set_outer(5, 5)                       -- Outer gaps (horizontal, vertical) in pixels
+
+-------------------------------------------------------------------------------
+-- Status Bar Configuration
+-------------------------------------------------------------------------------
+-- Font configuration
+oxwm.bar.set_font(bar_font)
+
+-- Bar color schemes (for workspace tag display)
+-- Parameters: foreground, background, border
+oxwm.bar.set_scheme_normal(colors.fg, colors.bg, "#444444")        -- Unoccupied tags
+oxwm.bar.set_scheme_occupied(colors.cyan, colors.bg, colors.cyan)  -- Occupied tags
+oxwm.bar.set_scheme_selected(colors.cyan, colors.bg, colors.purple) -- Currently selected tag
+
+-------------------------------------------------------------------------------
 -- Keybindings
+-------------------------------------------------------------------------------
+-- Keybindings are defined using oxwm.key.bind(modifiers, key, action)
+-- Modifiers: {"Mod4"}, {"Mod1"}, {"Shift"}, {"Control"}, or combinations like {"Mod4", "Shift"}
+-- Keys: Use uppercase for letters (e.g., "Return", "H", "J", "K", "L")
+-- Actions: Functions that return actions (e.g., oxwm.spawn(), oxwm.client.kill())
+--
+-- A list of available keysyms can be found in the X11 keysym definitions.
+-- Common keys: Return, Space, Tab, Escape, Backspace, Delete, Left, Right, Up, Down
 
 -- Basic window management
-oxwm.key.bind({ "Mod4" }, "Return", oxwm.spawn("st"))
-oxwm.key.bind({ "Mod4" }, "D", oxwm.spawn({ "sh", "-c", "dmenu_run -l 10" }))
-oxwm.key.bind({ "Mod4" }, "S", oxwm.spawn({ "sh", "-c", "maim -s | xclip -selection clipboard -t image/png" }))
-oxwm.key.bind({ "Mod4" }, "Q", oxwm.client.kill())
+oxwm.key.bind({ modkey }, "Return", oxwm.spawn(terminal))                          -- Spawn terminal
+oxwm.key.bind({ modkey }, "D", oxwm.spawn({ "sh", "-c", "dmenu_run -l 10" }))     -- Application launcher
+oxwm.key.bind({ modkey }, "S", oxwm.spawn({ "sh", "-c", "maim -s | xclip -selection clipboard -t image/png" }))  -- Screenshot selection
+oxwm.key.bind({ modkey }, "Q", oxwm.client.kill())                                 -- Close focused window
 
--- Keybind overlay
-oxwm.key.bind({ "Mod4", "Shift" }, "Slash", oxwm.show_keybinds())
+-- Keybind overlay - Shows important keybindings on screen
+oxwm.key.bind({ modkey, "Shift" }, "Slash", oxwm.show_keybinds())
 
--- Client actions
-oxwm.key.bind({ "Mod4", "Shift" }, "F", oxwm.client.toggle_fullscreen())
-oxwm.key.bind({ "Mod4", "Shift" }, "Space", oxwm.client.toggle_floating())
+-- Window state toggles
+oxwm.key.bind({ modkey, "Shift" }, "F", oxwm.client.toggle_fullscreen())           -- Toggle fullscreen
+oxwm.key.bind({ modkey, "Shift" }, "Space", oxwm.client.toggle_floating())         -- Toggle floating mode
 
 -- Layout management
-oxwm.key.bind({ "Mod4" }, "F", oxwm.layout.set("normie"))
-oxwm.key.bind({ "Mod4" }, "C", oxwm.layout.set("tiling"))
-oxwm.key.bind({ "Mod1" }, "N", oxwm.layout.cycle())
+oxwm.key.bind({ modkey }, "F", oxwm.layout.set("normie"))                          -- Set floating layout
+oxwm.key.bind({ modkey }, "C", oxwm.layout.set("tiling"))                          -- Set tiling layout
+oxwm.key.bind({ "Mod1" }, "N", oxwm.layout.cycle())                                -- Cycle through layouts
 
 -- Gaps toggle
-oxwm.key.bind({ "Mod4" }, "A", oxwm.toggle_gaps())
-
--- WM controls
-oxwm.key.bind({ "Mod4", "Shift" }, "Q", oxwm.quit())
-oxwm.key.bind({ "Mod4", "Shift" }, "R", oxwm.restart())
-
--- Focus direction (vim keys: h=left=2, j=down=1, k=up=0, l=right=3)
-oxwm.key.bind({ "Mod4" }, "H", oxwm.client.focus_direction("left"))
-oxwm.key.bind({ "Mod4" }, "J", oxwm.client.focus_direction("down"))
-oxwm.key.bind({ "Mod4" }, "K", oxwm.client.focus_direction("up"))
-oxwm.key.bind({ "Mod4" }, "L", oxwm.client.focus_direction("right"))
-
--- Monitor focus
-oxwm.key.bind({ "Mod4" }, "Comma", oxwm.focus_monitor(-1))
-oxwm.key.bind({ "Mod4" }, "Period", oxwm.focus_monitor(1))
-
--- Tag viewing
-oxwm.key.bind({ "Mod4" }, "1", oxwm.tag.view(0))
-oxwm.key.bind({ "Mod4" }, "2", oxwm.tag.view(1))
-oxwm.key.bind({ "Mod4" }, "3", oxwm.tag.view(2))
-oxwm.key.bind({ "Mod4" }, "4", oxwm.tag.view(3))
-oxwm.key.bind({ "Mod4" }, "5", oxwm.tag.view(4))
-oxwm.key.bind({ "Mod4" }, "6", oxwm.tag.view(5))
-oxwm.key.bind({ "Mod4" }, "7", oxwm.tag.view(6))
-oxwm.key.bind({ "Mod4" }, "8", oxwm.tag.view(7))
-oxwm.key.bind({ "Mod4" }, "9", oxwm.tag.view(8))
-
--- Move window to tag
-oxwm.key.bind({ "Mod4", "Shift" }, "1", oxwm.tag.move_to(0))
-oxwm.key.bind({ "Mod4", "Shift" }, "2", oxwm.tag.move_to(1))
-oxwm.key.bind({ "Mod4", "Shift" }, "3", oxwm.tag.move_to(2))
-oxwm.key.bind({ "Mod4", "Shift" }, "4", oxwm.tag.move_to(3))
-oxwm.key.bind({ "Mod4", "Shift" }, "5", oxwm.tag.move_to(4))
-oxwm.key.bind({ "Mod4", "Shift" }, "6", oxwm.tag.move_to(5))
-oxwm.key.bind({ "Mod4", "Shift" }, "7", oxwm.tag.move_to(6))
-oxwm.key.bind({ "Mod4", "Shift" }, "8", oxwm.tag.move_to(7))
-oxwm.key.bind({ "Mod4", "Shift" }, "9", oxwm.tag.move_to(8))
-
--- Swap windows in direction
-oxwm.key.bind({ "Mod4", "Shift" }, "H", oxwm.client.swap_direction("left"))
-oxwm.key.bind({ "Mod4", "Shift" }, "J", oxwm.client.swap_direction("down"))
-oxwm.key.bind({ "Mod4", "Shift" }, "K", oxwm.client.swap_direction("up"))
-oxwm.key.bind({ "Mod4", "Shift" }, "L", oxwm.client.swap_direction("right"))
-
--- Keychord example: Mod4+Space then T to spawn terminal
+oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps())                                 -- Toggle gaps on/off
+
+-- Window manager controls
+oxwm.key.bind({ modkey, "Shift" }, "Q", oxwm.quit())                               -- Quit OXWM
+oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart())                            -- Restart OXWM (reloads config)
+
+-- Focus movement (vim keys)
+oxwm.key.bind({ modkey }, "H", oxwm.client.focus_direction("left"))                -- Focus window to the left
+oxwm.key.bind({ modkey }, "J", oxwm.client.focus_direction("down"))                -- Focus window below
+oxwm.key.bind({ modkey }, "K", oxwm.client.focus_direction("up"))                  -- Focus window above
+oxwm.key.bind({ modkey }, "L", oxwm.client.focus_direction("right"))               -- Focus window to the right
+
+-- Multi-monitor support
+oxwm.key.bind({ modkey }, "Comma", oxwm.focus_monitor(-1))                         -- Focus previous monitor
+oxwm.key.bind({ modkey }, "Period", oxwm.focus_monitor(1))                         -- Focus next monitor
+
+-- Workspace (tag) navigation
+-- Switch to workspace N (tags are 0-indexed, so tag "1" is index 0)
+oxwm.key.bind({ modkey }, "1", oxwm.tag.view(0))
+oxwm.key.bind({ modkey }, "2", oxwm.tag.view(1))
+oxwm.key.bind({ modkey }, "3", oxwm.tag.view(2))
+oxwm.key.bind({ modkey }, "4", oxwm.tag.view(3))
+oxwm.key.bind({ modkey }, "5", oxwm.tag.view(4))
+oxwm.key.bind({ modkey }, "6", oxwm.tag.view(5))
+oxwm.key.bind({ modkey }, "7", oxwm.tag.view(6))
+oxwm.key.bind({ modkey }, "8", oxwm.tag.view(7))
+oxwm.key.bind({ modkey }, "9", oxwm.tag.view(8))
+
+-- Move focused window to workspace N
+oxwm.key.bind({ modkey, "Shift" }, "1", oxwm.tag.move_to(0))
+oxwm.key.bind({ modkey, "Shift" }, "2", oxwm.tag.move_to(1))
+oxwm.key.bind({ modkey, "Shift" }, "3", oxwm.tag.move_to(2))
+oxwm.key.bind({ modkey, "Shift" }, "4", oxwm.tag.move_to(3))
+oxwm.key.bind({ modkey, "Shift" }, "5", oxwm.tag.move_to(4))
+oxwm.key.bind({ modkey, "Shift" }, "6", oxwm.tag.move_to(5))
+oxwm.key.bind({ modkey, "Shift" }, "7", oxwm.tag.move_to(6))
+oxwm.key.bind({ modkey, "Shift" }, "8", oxwm.tag.move_to(7))
+oxwm.key.bind({ modkey, "Shift" }, "9", oxwm.tag.move_to(8))
+
+-- Swap windows in direction (vim keys with Shift)
+oxwm.key.bind({ modkey, "Shift" }, "H", oxwm.client.swap_direction("left"))        -- Swap with window to the left
+oxwm.key.bind({ modkey, "Shift" }, "J", oxwm.client.swap_direction("down"))        -- Swap with window below
+oxwm.key.bind({ modkey, "Shift" }, "K", oxwm.client.swap_direction("up"))          -- Swap with window above
+oxwm.key.bind({ modkey, "Shift" }, "L", oxwm.client.swap_direction("right"))       -- Swap with window to the right
+
+-------------------------------------------------------------------------------
+-- Advanced: Keychords
+-------------------------------------------------------------------------------
+-- Keychords allow you to bind multiple-key sequences (like Emacs or Vim)
+-- Format: {{modifiers}, key1}, {{modifiers}, key2}, ...
+-- Example: Press Mod4+Space, then release and press T to spawn a terminal
 oxwm.key.chord({
-    { { "Mod4" }, "Space" },
-    { {},       "T" }
-}, oxwm.spawn("st"))
+    { { modkey }, "Space" },
+    { {},         "T" }
+}, oxwm.spawn(terminal))
+
+-------------------------------------------------------------------------------
+-- Status Bar Blocks
+-------------------------------------------------------------------------------
+-- Add informational blocks to the status bar
+-- Format: oxwm.bar.add_block(format, type, data, update_interval, color, separator)
+--   format: Display format with {} placeholders
+--   type: Block type ("Ram", "DateTime", "Shell", "Static", "Battery")
+--   data: Type-specific data (command for Shell, format for DateTime, etc.)
+--   update_interval: Seconds between updates (large number for static content)
+--   color: Text color (from color palette)
+--   separator: Whether to add space after this block
 
--- Status bar blocks
 oxwm.bar.add_block("Ram: {used}/{total} GB", "Ram", nil, 5, colors.light_blue, true)
 oxwm.bar.add_block(" │  ", "Static", " │  ", 999999999, colors.lavender, false)
 oxwm.bar.add_block("Kernel: {}", "Shell", "uname -r", 999999999, colors.red, true)
 oxwm.bar.add_block(" │  ", "Static", " │  ", 999999999, colors.lavender, false)
 oxwm.bar.add_block("{}", "DateTime", "%a, %b %d - %-I:%M %P", 1, colors.cyan, true)
 
--- Autostart commands (runs once at startup)
--- oxwm.autostart("picom")
--- oxwm.autostart("feh --bg-scale ~/wallpaper.jpg")
+-- Uncomment to add battery status (useful for laptops)
+-- oxwm.bar.add_block("Bat: {}%", "Battery", {
+--     charging = "⚡ Bat: {}%",
+--     discharging = "🔋 Bat: {}%",
+--     full = "✓ Bat: {}%"
+-- }, 30, colors.green, true)
+
+-------------------------------------------------------------------------------
+-- Autostart
+-------------------------------------------------------------------------------
+-- Commands to run once when OXWM starts
+-- Uncomment and modify these examples, or add your own
+
+-- oxwm.autostart("picom")                                  -- Compositor for transparency and effects
+-- oxwm.autostart("feh --bg-scale ~/wallpaper.jpg")        -- Set wallpaper
+-- oxwm.autostart("dunst")                                  -- Notification daemon
+-- oxwm.autostart("nm-applet")                              -- Network manager applet