oxwm

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

Fix bar flickering and improve configuration management

Commit
19dcad1c03a979220975e0b4dc28f1fa048b2c7a
Parent
8a5cf95
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-11-16 08:49:49
This release addresses several critical bugs and enhances the user experience:

- Implement double-buffered rendering for the status bar to eliminate flickering
- Restructure LSP configuration with automatic lib/ directory management and .luarc.json setup
- Refactor Lua API error handling, replacing verbose map_err chains with idiomatic ? operator
- Add monocle layout for maximized single-window view
- Consolidate fullscreen actions and improve floating window behavior
- Update test configuration with better documentation and cleaner bar block API
- Bump version to 0.7.1

The bar rendering now uses XPixmap for off-screen composition before copying to the window,
significantly reducing visual artifacts during updates. Configuration updates are now applied
automatically, ensuring LSP definitions stay in sync with the running version.

Diff

diff --git a/Cargo.lock b/Cargo.lock
index e029d9b..30a500a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -297,7 +297,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "oxwm"
-version = "0.7.0"
+version = "0.7.1"
 dependencies = [
  "anyhow",
  "chrono",
diff --git a/Cargo.toml b/Cargo.toml
index d1dbed4..26a4097 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "oxwm"
-version = "0.7.0"
+version = "0.7.1"
 edition = "2024"
 
 [lib]
diff --git a/PR Summary.md b/PR Summary.md
new file mode 100644
index 0000000..2bc3f2f
--- /dev/null
+++ b/PR Summary.md	
@@ -0,0 +1,17 @@
+# PR Summary
+
+## Fix bar flickering and improve configuration management
+
+This release addresses several critical bugs and enhances the user experience:
+
+- Implement double-buffered rendering for the status bar to eliminate flickering
+- Restructure LSP configuration with automatic lib/ directory management and .luarc.json setup
+- Refactor Lua API error handling, replacing verbose map_err chains with idiomatic ? operator
+- Add monocle layout for maximized single-window view
+- Consolidate fullscreen actions and improve floating window behavior
+- Update test configuration with better documentation and cleaner bar block API
+- Bump version to 0.7.1
+
+The bar rendering now uses XPixmap for off-screen composition before copying to the window,
+significantly reducing visual artifacts during updates. Configuration updates are now applied
+automatically, ensuring LSP definitions stay in sync with the running version.
diff --git a/resources/test-config.lua b/resources/test-config.lua
index f2c3a15..3ac8c57 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -3,12 +3,18 @@
 ---Using the new functional API
 ---Edit this file and reload with Mod+Alt+R
 
----Load type definitions for LSP
+---Load type definitions for LSP (lua-language-server)
+---Option 1: Copy templates/oxwm.lua to the same directory as your config
+---Option 2: Add to your LSP settings (e.g., .luarc.json):
+---  {
+---    "workspace.library": [
+---      "/path/to/oxwm/templates"
+---    ]
+---  }
+---Option 3: Symlink templates/oxwm.lua to your config directory
 ---@module 'oxwm'
 
 
--- Set variables as needed
--- Colors
 local colors = {
     lavender = 0xa9b1d6,
     light_blue = 0x7aa2f7,
@@ -22,81 +28,63 @@ local colors = {
     blue = 0x6dade3,
 }
 
-local modkey = "Mod1";
+local modkey = "Mod1"
 
--- Basic settings
 oxwm.set_terminal("st")
 oxwm.set_modkey(modkey)
 oxwm.set_tags({ "1", "2", "3", "4", "5", "6", "7", "8", "9" })
 
--- Layout symbol overrides
 oxwm.set_layout_symbol("tiling", "[T]")
 oxwm.set_layout_symbol("normie", "[F]")
 
--- 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)
 oxwm.gaps.set_outer(5, 5)
 
--- Bar configuration
 oxwm.bar.set_font("JetBrainsMono Nerd Font:style=Bold:size=12")
 
--- Bar color schemes (for tag display)
 oxwm.bar.set_scheme_normal(colors.fg, colors.bg, 0x444444)
 oxwm.bar.set_scheme_occupied(colors.cyan, colors.bg, colors.cyan)
 oxwm.bar.set_scheme_selected(colors.cyan, colors.bg, colors.purple)
 
--- Keybindings
-
--- Keychord: Mod1+Space then T to spawn terminal
 oxwm.key.chord({
     { { modkey }, "Space" },
     { {},         "T" }
 }, oxwm.spawn("st"))
 
--- Basic window management
 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({ modkey, "Shift" }, "Slash", oxwm.show_keybinds())
 
--- Client actions
 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({ 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({ modkey }, "A", oxwm.toggle_gaps())
 
--- WM controls
 oxwm.key.bind({ modkey, "Shift" }, "Q", oxwm.quit())
 oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart())
 
--- Focus direction (vim keys)
 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({ 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({ modkey }, "1", oxwm.tag.view(0))
 oxwm.key.bind({ modkey }, "2", oxwm.tag.view(1))
 oxwm.key.bind({ modkey }, "3", oxwm.tag.view(2))
@@ -107,7 +95,6 @@ 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({ 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))
@@ -118,20 +105,55 @@ 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", {
-    charging = "󰂄 Bat: {}%",
-    discharging = "󰁹 Bat:{}%",
-    full = "󰁹 Bat: {}%"
-}, 30, colors.green, true)
-
-oxwm.bar.add_block(" │  ", "Static", "", 999999999, colors.lavender, false)
-oxwm.bar.add_block("󰍛 {used}/{total} GB", "Ram", nil, 5, colors.light_blue, true)
-oxwm.bar.add_block(" │  ", "Static", "", 999999999, colors.lavender, false)
-oxwm.bar.add_block(" {}", "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")
+oxwm.bar.set_blocks({
+    oxwm.bar.block.battery({
+        charging = "󰂄 Bat: {}%",
+        discharging = "󰁹 Bat: {}%",
+        full = "󰁹 Bat: {}%",
+        format = "",
+        interval = 30,
+        color = colors.green,
+        underline = true
+    }),
+    oxwm.bar.block.static({
+        text = " │  ",
+        format = "",
+        interval = 999999999,
+        color = colors.lavender,
+        underline = false
+    }),
+    oxwm.bar.block.ram({
+        format = "󰍛 {used}/{total} GB",
+        interval = 5,
+        color = colors.light_blue,
+        underline = true
+    }),
+    oxwm.bar.block.static({
+        text = " │  ",
+        format = "",
+        interval = 999999999,
+        color = colors.lavender,
+        underline = false
+    }),
+    oxwm.bar.block.shell({
+        command = "uname -r",
+        format = " {}",
+        interval = 999999999,
+        color = colors.red,
+        underline = true
+    }),
+    oxwm.bar.block.static({
+        text = " │  ",
+        format = "",
+        interval = 999999999,
+        color = colors.lavender,
+        underline = false
+    }),
+    oxwm.bar.block.datetime({
+        format = "󰸘 {}",
+        interval = 1,
+        color = colors.cyan,
+        underline = true,
+        date_format = "%a, %b %d - %-I:%M %P"
+    })
+})
diff --git a/src/bar/bar.rs b/src/bar/bar.rs
index 756b998..6897ac4 100644
--- a/src/bar/bar.rs
+++ b/src/bar/bar.rs
@@ -13,6 +13,8 @@ pub struct Bar {
     width: u16,
     height: u16,
     graphics_context: Gcontext,
+    pixmap: x11::xlib::Pixmap,
+    display: *mut x11::xlib::Display,
 
     font_draw: FontDraw,
 
@@ -77,8 +79,19 @@ impl Bar {
 
         let visual = unsafe { x11::xlib::XDefaultVisual(display, screen_num as i32) };
         let colormap = unsafe { x11::xlib::XDefaultColormap(display, screen_num as i32) };
+        let depth = unsafe { x11::xlib::XDefaultDepth(display, screen_num as i32) };
 
-        let font_draw = FontDraw::new(display, window as x11::xlib::Drawable, visual, colormap)?;
+        let pixmap = unsafe {
+            x11::xlib::XCreatePixmap(
+                display,
+                window as x11::xlib::Drawable,
+                width as u32,
+                height as u32,
+                depth as u32,
+            )
+        };
+
+        let font_draw = FontDraw::new(display, pixmap, visual, colormap)?;
 
         let horizontal_padding = (font.height() as f32 * 0.4) as u16;
 
@@ -110,6 +123,8 @@ impl Bar {
             width,
             height,
             graphics_context,
+            pixmap,
+            display,
             font_draw,
             tag_widths,
             needs_redraw: true,
@@ -182,16 +197,22 @@ impl Bar {
             self.graphics_context,
             &ChangeGCAux::new().foreground(self.scheme_normal.background),
         )?;
-        connection.poly_fill_rectangle(
-            self.window,
-            self.graphics_context,
-            &[Rectangle {
-                x: 0,
-                y: 0,
-                width: self.width,
-                height: self.height,
-            }],
-        )?;
+        connection.flush()?;
+
+        unsafe {
+            let gc = x11::xlib::XCreateGC(display, self.pixmap, 0, std::ptr::null_mut());
+            x11::xlib::XSetForeground(display, gc, self.scheme_normal.background as u64);
+            x11::xlib::XFillRectangle(
+                display,
+                self.pixmap,
+                gc,
+                0,
+                0,
+                self.width as u32,
+                self.height as u32,
+            );
+            x11::xlib::XFreeGC(display, gc);
+        }
 
         let mut x_position: i16 = 0;
 
@@ -229,20 +250,20 @@ impl Bar {
                 let underline_width = tag_width - underline_padding;
                 let underline_x = x_position + (underline_padding / 2) as i16;
 
-                connection.change_gc(
-                    self.graphics_context,
-                    &ChangeGCAux::new().foreground(scheme.underline),
-                )?;
-                connection.poly_fill_rectangle(
-                    self.window,
-                    self.graphics_context,
-                    &[Rectangle {
-                        x: underline_x,
-                        y: underline_y,
-                        width: underline_width,
-                        height: underline_height,
-                    }],
-                )?;
+                unsafe {
+                    let gc = x11::xlib::XCreateGC(display, self.pixmap, 0, std::ptr::null_mut());
+                    x11::xlib::XSetForeground(display, gc, scheme.underline as u64);
+                    x11::xlib::XFillRectangle(
+                        display,
+                        self.pixmap,
+                        gc,
+                        underline_x as i32,
+                        underline_y as i32,
+                        underline_width as u32,
+                        underline_height as u32,
+                    );
+                    x11::xlib::XFreeGC(display, gc);
+                }
             }
 
             x_position += tag_width as i16;
@@ -304,29 +325,43 @@ impl Bar {
                         let underline_width = text_width + underline_padding;
                         let underline_x = x_position - (underline_padding / 2) as i16;
 
-                        connection.change_gc(
-                            self.graphics_context,
-                            &ChangeGCAux::new().foreground(block.color()),
-                        )?;
-
-                        connection.poly_fill_rectangle(
-                            self.window,
-                            self.graphics_context,
-                            &[Rectangle {
-                                x: underline_x,
-                                y: underline_y,
-                                width: underline_width,
-                                height: underline_height,
-                            }],
-                        )?;
+                        unsafe {
+                            let gc = x11::xlib::XCreateGC(display, self.pixmap, 0, std::ptr::null_mut());
+                            x11::xlib::XSetForeground(display, gc, block.color() as u64);
+                            x11::xlib::XFillRectangle(
+                                display,
+                                self.pixmap,
+                                gc,
+                                underline_x as i32,
+                                underline_y as i32,
+                                underline_width as u32,
+                                underline_height as u32,
+                            );
+                            x11::xlib::XFreeGC(display, gc);
+                        }
                     }
                 }
             }
         }
-        connection.flush()?;
+
         unsafe {
-            x11::xlib::XFlush(display);
+            let gc = x11::xlib::XCreateGC(display, self.window as x11::xlib::Drawable, 0, std::ptr::null_mut());
+            x11::xlib::XCopyArea(
+                display,
+                self.pixmap,
+                self.window as x11::xlib::Drawable,
+                gc,
+                0,
+                0,
+                self.width as u32,
+                self.height as u32,
+                0,
+                0,
+            );
+            x11::xlib::XFreeGC(display, gc);
+            x11::xlib::XSync(display, 0);
         }
+
         self.needs_redraw = false;
 
         Ok(())
@@ -372,3 +407,11 @@ impl Bar {
         self.needs_redraw = true;
     }
 }
+
+impl Drop for Bar {
+    fn drop(&mut self) {
+        unsafe {
+            x11::xlib::XFreePixmap(self.display, self.pixmap);
+        }
+    }
+}
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 1fb28d6..4619a09 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -73,6 +73,9 @@ fn load_config(custom_path: Option<PathBuf>) -> Result<(oxwm::Config, bool)> {
                 println!("   Please manually port your configuration to the new Lua format.");
                 println!("   See the new config.lua template for examples.\n");
             }
+        } else {
+            // Always update LSP files to match the running version
+            update_lsp_files()?;
         }
 
         lua_path
@@ -102,18 +105,42 @@ fn init_config() -> Result<()> {
     let config_path = config_dir.join("config.lua");
     std::fs::write(&config_path, config_template)?;
 
-    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)?;
+    // Update LSP files
+    update_lsp_files()?;
 
     println!("✓ Config created at {:?}", config_path);
-    println!("✓ LSP definitions installed at {:?}", oxwm_lua_path);
+    println!("✓ LSP definitions installed at {:?}/lib/oxwm.lua", config_dir);
     println!("  Edit the file and reload with Mod+Shift+R");
     println!("  No compilation needed - changes take effect immediately!");
 
     Ok(())
 }
 
+fn update_lsp_files() -> Result<()> {
+    let config_dir = get_config_path();
+
+    // Create lib directory for LSP files
+    let lib_dir = config_dir.join("lib");
+    std::fs::create_dir_all(&lib_dir)?;
+
+    // Always overwrite oxwm.lua with the version from this binary
+    let oxwm_lua_template = include_str!("../../templates/oxwm.lua");
+    let oxwm_lua_path = lib_dir.join("oxwm.lua");
+    std::fs::write(&oxwm_lua_path, oxwm_lua_template)?;
+
+    // Create/update .luarc.json for LSP configuration
+    let luarc_content = r#"{
+  "workspace.library": [
+    "lib"
+  ]
+}
+"#;
+    let luarc_path = config_dir.join(".luarc.json");
+    std::fs::write(&luarc_path, luarc_content)?;
+
+    Ok(())
+}
+
 fn get_config_path() -> PathBuf {
     dirs::config_dir()
         .expect("Could not find config directory")
diff --git a/src/config/lua.rs b/src/config/lua.rs
index 57b0885..ad0b330 100644
--- a/src/config/lua.rs
+++ b/src/config/lua.rs
@@ -394,6 +394,7 @@ fn parse_key_action(kb_table: &Table) -> Result<KeyAction, ConfigError> {
 fn string_to_key_action(s: &str) -> Result<KeyAction, ConfigError> {
     let action = match s {
         "Spawn" => KeyAction::Spawn,
+        "SpawnTerminal" => KeyAction::SpawnTerminal,
         "KillClient" => KeyAction::KillClient,
         "FocusStack" => KeyAction::FocusStack,
         "FocusDirection" => KeyAction::FocusDirection,
@@ -404,7 +405,6 @@ fn string_to_key_action(s: &str) -> Result<KeyAction, ConfigError> {
         "ViewTag" => KeyAction::ViewTag,
         "ToggleGaps" => KeyAction::ToggleGaps,
         "ToggleFullScreen" => KeyAction::ToggleFullScreen,
-        "ToggleWindowFullscreen" => KeyAction::ToggleWindowFullscreen,
         "ToggleFloating" => KeyAction::ToggleFloating,
         "ChangeLayout" => KeyAction::ChangeLayout,
         "CycleLayout" => KeyAction::CycleLayout,
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
index a7b535e..3b67657 100644
--- a/src/config/lua_api.rs
+++ b/src/config/lua_api.rs
@@ -75,8 +75,7 @@ type SharedBuilder = Rc<RefCell<ConfigBuilder>>;
 pub fn register_api(lua: &Lua) -> Result<SharedBuilder, ConfigError> {
     let builder = Rc::new(RefCell::new(ConfigBuilder::default()));
 
-    let oxwm_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create oxwm table: {}", e)))?;
+    let oxwm_table = lua.create_table()?;
 
     register_spawn(&lua, &oxwm_table, builder.clone())?;
     register_key_module(&lua, &oxwm_table, builder.clone())?;
@@ -88,8 +87,7 @@ pub fn register_api(lua: &Lua) -> Result<SharedBuilder, ConfigError> {
     register_bar_module(&lua, &oxwm_table, builder.clone())?;
     register_misc(&lua, &oxwm_table, builder.clone())?;
 
-    lua.globals().set("oxwm", oxwm_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set oxwm global: {}", e)))?;
+    lua.globals().set("oxwm", oxwm_table)?;
 
     Ok(builder)
 }
@@ -97,15 +95,17 @@ pub fn register_api(lua: &Lua) -> Result<SharedBuilder, ConfigError> {
 fn register_spawn(lua: &Lua, parent: &Table, _builder: SharedBuilder) -> Result<(), ConfigError> {
     let spawn = lua.create_function(|lua, cmd: Value| {
         create_action_table(lua, "Spawn", cmd)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create spawn: {}", e)))?;
-    parent.set("spawn", spawn)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set spawn: {}", e)))?;
+    })?;
+    let spawn_terminal = lua.create_function(|lua, ()| {
+        create_action_table(lua, "SpawnTerminal", Value::Nil)
+    })?;
+    parent.set("spawn", spawn)?;
+    parent.set("spawn_terminal", spawn_terminal)?;
     Ok(())
 }
 
 fn register_key_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<(), ConfigError> {
-    let key_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create key table: {}", e)))?;
+    let key_table = lua.create_table()?;
 
     let builder_clone = builder.clone();
     let bind = lua.create_function(move |lua, (mods, key, action): (Value, String, Value)| {
@@ -117,7 +117,7 @@ fn register_key_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
         builder_clone.borrow_mut().keybindings.push(binding);
 
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create bind: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let chord = lua.create_function(move |lua, (keys, action): (Table, Value)| {
@@ -139,38 +139,34 @@ fn register_key_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
         builder_clone.borrow_mut().keybindings.push(binding);
 
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create chord: {}", e)))?;
-
-    key_table.set("bind", bind)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set bind: {}", e)))?;
-    key_table.set("chord", chord)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set chord: {}", e)))?;
-    parent.set("key", key_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set key: {}", e)))?;
+    })?;
+
+    key_table.set("bind", bind)?;
+    key_table.set("chord", chord)?;
+    parent.set("key", key_table)?;
     Ok(())
 }
 
 fn register_gaps_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<(), ConfigError> {
-    let gaps_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create gaps table: {}", e)))?;
+    let gaps_table = lua.create_table()?;
 
     let builder_clone = builder.clone();
     let set_enabled = lua.create_function(move |_, enabled: bool| {
         builder_clone.borrow_mut().gaps_enabled = enabled;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_enabled: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let enable = lua.create_function(move |_, ()| {
         builder_clone.borrow_mut().gaps_enabled = true;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create enable: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let disable = lua.create_function(move |_, ()| {
         builder_clone.borrow_mut().gaps_enabled = false;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create disable: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_inner = lua.create_function(move |_, (h, v): (u32, u32)| {
@@ -178,7 +174,7 @@ fn register_gaps_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Re
         b.gap_inner_horizontal = h;
         b.gap_inner_vertical = v;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_inner: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_outer = lua.create_function(move |_, (h, v): (u32, u32)| {
@@ -186,227 +182,226 @@ fn register_gaps_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Re
         b.gap_outer_horizontal = h;
         b.gap_outer_vertical = v;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_outer: {}", e)))?;
-
-    gaps_table.set("set_enabled", set_enabled)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_enabled: {}", e)))?;
-    gaps_table.set("enable", enable)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set enable: {}", e)))?;
-    gaps_table.set("disable", disable)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set disable: {}", e)))?;
-    gaps_table.set("set_inner", set_inner)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_inner: {}", e)))?;
-    gaps_table.set("set_outer", set_outer)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_outer: {}", e)))?;
-    parent.set("gaps", gaps_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set gaps: {}", e)))?;
+    })?;
+
+    gaps_table.set("set_enabled", set_enabled)?;
+    gaps_table.set("enable", enable)?;
+    gaps_table.set("disable", disable)?;
+    gaps_table.set("set_inner", set_inner)?;
+    gaps_table.set("set_outer", set_outer)?;
+    parent.set("gaps", gaps_table)?;
     Ok(())
 }
 
 fn register_border_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<(), ConfigError> {
-    let border_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create border table: {}", e)))?;
+    let border_table = lua.create_table()?;
 
     let builder_clone = builder.clone();
     let set_width = lua.create_function(move |_, width: u32| {
         builder_clone.borrow_mut().border_width = width;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_width: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_focused_color = lua.create_function(move |_, color: Value| {
         let color_u32 = parse_color_value(color)?;
         builder_clone.borrow_mut().border_focused = color_u32;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_focused_color: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_unfocused_color = lua.create_function(move |_, color: Value| {
         let color_u32 = parse_color_value(color)?;
         builder_clone.borrow_mut().border_unfocused = color_u32;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_unfocused_color: {}", e)))?;
-
-    border_table.set("set_width", set_width)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_width: {}", e)))?;
-    border_table.set("set_focused_color", set_focused_color)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_focused_color: {}", e)))?;
-    border_table.set("set_unfocused_color", set_unfocused_color)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_unfocused_color: {}", e)))?;
-    parent.set("border", border_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set border: {}", e)))?;
+    })?;
+
+    border_table.set("set_width", set_width)?;
+    border_table.set("set_focused_color", set_focused_color)?;
+    border_table.set("set_unfocused_color", set_unfocused_color)?;
+    parent.set("border", border_table)?;
     Ok(())
 }
 
 fn register_client_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
-    let client_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create client table: {}", e)))?;
+    let client_table = lua.create_table()?;
 
     let kill = lua.create_function(|lua, ()| {
         create_action_table(lua, "KillClient", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create kill: {}", e)))?;
+    })?;
 
     let toggle_fullscreen = lua.create_function(|lua, ()| {
         create_action_table(lua, "ToggleFullScreen", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create toggle_fullscreen: {}", e)))?;
+    })?;
 
     let toggle_floating = lua.create_function(|lua, ()| {
         create_action_table(lua, "ToggleFloating", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create toggle_floating: {}", e)))?;
+    })?;
 
     let focus_stack = lua.create_function(|lua, dir: i32| {
         create_action_table(lua, "FocusStack", Value::Integer(dir as i64))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create focus_stack: {}", e)))?;
+    })?;
 
     let focus_direction = lua.create_function(|lua, dir: String| {
         let dir_int = direction_string_to_int(&dir)?;
         create_action_table(lua, "FocusDirection", Value::Integer(dir_int))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create focus_direction: {}", e)))?;
+    })?;
 
     let swap_direction = lua.create_function(|lua, dir: String| {
         let dir_int = direction_string_to_int(&dir)?;
         create_action_table(lua, "SwapDirection", Value::Integer(dir_int))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create swap_direction: {}", e)))?;
+    })?;
 
     let smart_move = lua.create_function(|lua, dir: String| {
         let dir_int = direction_string_to_int(&dir)?;
         create_action_table(lua, "SmartMoveWin", Value::Integer(dir_int))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create smart_move: {}", e)))?;
+    })?;
 
     let exchange = lua.create_function(|lua, ()| {
         create_action_table(lua, "ExchangeClient", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create exchange: {}", e)))?;
-
-    client_table.set("kill", kill)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set kill: {}", e)))?;
-    client_table.set("toggle_fullscreen", toggle_fullscreen)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set toggle_fullscreen: {}", e)))?;
-    client_table.set("toggle_floating", toggle_floating)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set toggle_floating: {}", e)))?;
-    client_table.set("focus_stack", focus_stack)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set focus_stack: {}", e)))?;
-    client_table.set("focus_direction", focus_direction)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set focus_direction: {}", e)))?;
-    client_table.set("swap_direction", swap_direction)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set swap_direction: {}", e)))?;
-    client_table.set("smart_move", smart_move)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set smart_move: {}", e)))?;
-    client_table.set("exchange", exchange)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set exchange: {}", e)))?;
-
-    parent.set("client", client_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set client: {}", e)))?;
+    })?;
+
+    client_table.set("kill", kill)?;
+    client_table.set("toggle_fullscreen", toggle_fullscreen)?;
+    client_table.set("toggle_floating", toggle_floating)?;
+    client_table.set("focus_stack", focus_stack)?;
+    client_table.set("focus_direction", focus_direction)?;
+    client_table.set("swap_direction", swap_direction)?;
+    client_table.set("smart_move", smart_move)?;
+    client_table.set("exchange", exchange)?;
+
+    parent.set("client", client_table)?;
     Ok(())
 }
 
 fn register_layout_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
-    let layout_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create layout table: {}", e)))?;
+    let layout_table = lua.create_table()?;
 
     let cycle = lua.create_function(|lua, ()| {
         create_action_table(lua, "CycleLayout", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create cycle: {}", e)))?;
+    })?;
 
     let set = lua.create_function(|lua, name: String| {
         create_action_table(lua, "ChangeLayout", Value::String(lua.create_string(&name)?))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set: {}", e)))?;
-
-    layout_table.set("cycle", cycle)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set cycle: {}", e)))?;
-    layout_table.set("set", set)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set: {}", e)))?;
-    parent.set("layout", layout_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set layout: {}", e)))?;
+    })?;
+
+    layout_table.set("cycle", cycle)?;
+    layout_table.set("set", set)?;
+    parent.set("layout", layout_table)?;
     Ok(())
 }
 
 fn register_tag_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
-    let tag_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create tag table: {}", e)))?;
+    let tag_table = lua.create_table()?;
 
     let view = lua.create_function(|lua, idx: i32| {
         create_action_table(lua, "ViewTag", Value::Integer(idx as i64))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create view: {}", e)))?;
+    })?;
 
     let move_to = lua.create_function(|lua, idx: i32| {
         create_action_table(lua, "MoveToTag", Value::Integer(idx as i64))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create move_to: {}", e)))?;
-
-    tag_table.set("view", view)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set view: {}", e)))?;
-    tag_table.set("move_to", move_to)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set move_to: {}", e)))?;
-    parent.set("tag", tag_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set tag: {}", e)))?;
+    })?;
+
+    tag_table.set("view", view)?;
+    tag_table.set("move_to", move_to)?;
+    parent.set("tag", tag_table)?;
     Ok(())
 }
 
 fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<(), ConfigError> {
-    let bar_table = lua.create_table()
-        .map_err(|e| ConfigError::LuaError(format!("Failed to create bar table: {}", e)))?;
+    let bar_table = lua.create_table()?;
 
     let builder_clone = builder.clone();
     let set_font = lua.create_function(move |_, font: String| {
         builder_clone.borrow_mut().font = font;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_font: {}", e)))?;
-
+    })?;
+
+    let block_table = lua.create_table()?;
+
+    let ram = lua.create_function(|lua, config: Table| {
+        create_block_config(lua, config, "Ram", None)
+    })?;
+
+    let datetime = lua.create_function(|lua, config: Table| {
+        let date_format: String = config.get("date_format")
+            .map_err(|_| mlua::Error::RuntimeError("oxwm.bar.block.datetime: 'date_format' field is required (e.g., '%H:%M')".into()))?;
+        create_block_config(lua, config, "DateTime", Some(Value::String(lua.create_string(&date_format)?)))
+    })?;
+
+    let shell = lua.create_function(|lua, config: Table| {
+        let command: String = config.get("command")
+            .map_err(|_| mlua::Error::RuntimeError("oxwm.bar.block.shell: 'command' field is required".into()))?;
+        create_block_config(lua, config, "Shell", Some(Value::String(lua.create_string(&command)?)))
+    })?;
+
+    let static_block = lua.create_function(|lua, config: Table| {
+        let text: String = config.get("text")
+            .map_err(|_| mlua::Error::RuntimeError("oxwm.bar.block.static: 'text' field is required".into()))?;
+        create_block_config(lua, config, "Static", Some(Value::String(lua.create_string(&text)?)))
+    })?;
+
+    let battery = lua.create_function(|lua, config: Table| {
+        let charging: String = config.get("charging")
+            .map_err(|_| mlua::Error::RuntimeError("oxwm.bar.block.battery: 'charging' field is required".into()))?;
+        let discharging: String = config.get("discharging")
+            .map_err(|_| mlua::Error::RuntimeError("oxwm.bar.block.battery: 'discharging' field is required".into()))?;
+        let full: String = config.get("full")
+            .map_err(|_| mlua::Error::RuntimeError("oxwm.bar.block.battery: 'full' field is required".into()))?;
+
+        let formats_table = lua.create_table()?;
+        formats_table.set("charging", charging)?;
+        formats_table.set("discharging", discharging)?;
+        formats_table.set("full", full)?;
+
+        create_block_config(lua, config, "Battery", Some(Value::Table(formats_table)))
+    })?;
+
+    block_table.set("ram", ram)?;
+    block_table.set("datetime", datetime)?;
+    block_table.set("shell", shell)?;
+    block_table.set("static", static_block)?;
+    block_table.set("battery", battery)?;
+
+    // Deprecated add_block() function for backwards compatibility
+    // This allows old configs to still work, but users should migrate to set_blocks()
     let builder_clone = builder.clone();
-    let add_block = lua.create_function(move |_, (format, command, arg, interval, color, underline): (String, String, Option<Value>, u64, Value, bool)| {
-        use crate::bar::BlockCommand;
+    let add_block = lua.create_function(move |_, (format, block_type, arg, interval, color, underline): (String, String, Value, u64, Value, Option<bool>)| -> mlua::Result<()> {
+        eprintln!("WARNING: oxwm.bar.add_block() is deprecated. Please migrate to oxwm.bar.set_blocks() with block constructors.");
+        eprintln!("See the migration guide for details.");
 
-        let cmd = match command.as_str() {
+        let cmd = match block_type.as_str() {
             "DateTime" => {
-                let fmt = arg.and_then(|v| {
-                    if let Value::String(s) = v {
-                        s.to_str().ok().map(|s| s.to_string())
-                    } else {
-                        None
-                    }
-                }).ok_or_else(|| mlua::Error::RuntimeError("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)
+                let fmt = if let Value::String(s) = arg {
+                    s.to_str()?.to_string()
+                } else {
+                    return Err(mlua::Error::RuntimeError("DateTime block requires format string as third argument".into()));
+                };
+                crate::bar::BlockCommand::DateTime(fmt)
             }
             "Shell" => {
-                let cmd_str = arg.and_then(|v| {
-                    if let Value::String(s) = v {
-                        s.to_str().ok().map(|s| s.to_string())
-                    } else {
-                        None
-                    }
-                }).ok_or_else(|| mlua::Error::RuntimeError("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)
+                let cmd_str = if let Value::String(s) = arg {
+                    s.to_str()?.to_string()
+                } else {
+                    return Err(mlua::Error::RuntimeError("Shell block requires command string as third argument".into()));
+                };
+                crate::bar::BlockCommand::Shell(cmd_str)
             }
-            "Ram" => BlockCommand::Ram,
+            "Ram" => crate::bar::BlockCommand::Ram,
             "Static" => {
-                let text = arg.and_then(|v| {
-                    if let Value::String(s) = v {
-                        s.to_str().ok().map(|s| s.to_string())
-                    } else {
-                        None
-                    }
-                }).unwrap_or_default();
-                BlockCommand::Static(text)
+                let text = if let Value::String(s) = arg {
+                    s.to_str()?.to_string()
+                } else {
+                    String::new()
+                };
+                crate::bar::BlockCommand::Static(text)
             }
             "Battery" => {
-                let formats = arg.and_then(|v| {
-                    if let Value::Table(t) = v {
-                        Some(t)
-                    } else {
-                        None
-                    }
-                }).ok_or_else(|| mlua::Error::RuntimeError("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")?;
-                let full: String = formats.get("full")?;
-
-                BlockCommand::Battery {
-                    format_charging: charging,
-                    format_discharging: discharging,
-                    format_full: full,
-                }
+                return Err(mlua::Error::RuntimeError(
+                    "Battery block is not supported with add_block(). Please use oxwm.bar.set_blocks() with oxwm.bar.block.battery()".into()
+                ));
             }
-            _ => return Err(mlua::Error::RuntimeError(format!("oxwm.bar.add_block: unknown block command '{}'. valid commands: DateTime, Shell, Ram, Static, Battery", command))),
+            _ => return Err(mlua::Error::RuntimeError(format!("Unknown block type '{}'", block_type))),
         };
 
         let color_u32 = parse_color_value(color)?;
@@ -416,12 +411,98 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
             command: cmd,
             interval_secs: interval,
             color: color_u32,
-            underline,
+            underline: underline.unwrap_or(false),
         };
 
         builder_clone.borrow_mut().status_blocks.push(block);
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create add_block: {}", e)))?;
+    })?;
+
+    let builder_clone = builder.clone();
+    let set_blocks = lua.create_function(move |_, blocks: Table| {
+        use crate::bar::BlockCommand;
+
+        let mut block_configs = Vec::new();
+
+        for i in 1..=blocks.len()? {
+            let block_table: Table = blocks.get(i)?;
+            let block_type: String = block_table.get("__block_type")?;
+            let format: String = block_table.get("format").unwrap_or_default();
+            let interval: u64 = block_table.get("interval")?;
+            let color_val: Value = block_table.get("color")?;
+            let underline: bool = block_table.get("underline").unwrap_or(false);
+            let arg: Option<Value> = block_table.get("__arg").ok();
+
+            let cmd = match block_type.as_str() {
+                "DateTime" => {
+                    let fmt = arg.and_then(|v| {
+                        if let Value::String(s) = v {
+                            s.to_str().ok().map(|s| s.to_string())
+                        } else {
+                            None
+                        }
+                    }).ok_or_else(|| mlua::Error::RuntimeError("DateTime block missing format".into()))?;
+                    BlockCommand::DateTime(fmt)
+                }
+                "Shell" => {
+                    let cmd_str = arg.and_then(|v| {
+                        if let Value::String(s) = v {
+                            s.to_str().ok().map(|s| s.to_string())
+                        } else {
+                            None
+                        }
+                    }).ok_or_else(|| mlua::Error::RuntimeError("Shell block missing command".into()))?;
+                    BlockCommand::Shell(cmd_str)
+                }
+                "Ram" => BlockCommand::Ram,
+                "Static" => {
+                    let text = arg.and_then(|v| {
+                        if let Value::String(s) = v {
+                            s.to_str().ok().map(|s| s.to_string())
+                        } else {
+                            None
+                        }
+                    }).unwrap_or_default();
+                    BlockCommand::Static(text)
+                }
+                "Battery" => {
+                    let formats = arg.and_then(|v| {
+                        if let Value::Table(t) = v {
+                            Some(t)
+                        } else {
+                            None
+                        }
+                    }).ok_or_else(|| mlua::Error::RuntimeError("Battery block missing formats".into()))?;
+
+                    let charging: String = formats.get("charging")?;
+                    let discharging: String = formats.get("discharging")?;
+                    let full: String = formats.get("full")?;
+
+                    BlockCommand::Battery {
+                        format_charging: charging,
+                        format_discharging: discharging,
+                        format_full: full,
+                    }
+                }
+                _ => return Err(mlua::Error::RuntimeError(format!("Unknown block type '{}'", block_type))),
+            };
+
+            let color_u32 = parse_color_value(color_val)?;
+
+            let block = crate::bar::BlockConfig {
+                format,
+                command: cmd,
+                interval_secs: interval,
+                color: color_u32,
+                underline,
+            };
+
+            block_configs.push(block);
+        }
+
+        builder_clone.borrow_mut().status_blocks = block_configs;
+        Ok(())
+    })?;
 
     let builder_clone = builder.clone();
     let set_scheme_normal = lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
@@ -435,7 +516,7 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
             underline,
         };
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_scheme_normal: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_scheme_occupied = lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
@@ -449,7 +530,7 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
             underline,
         };
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_scheme_occupied: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_scheme_selected = lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
@@ -463,20 +544,16 @@ fn register_bar_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Res
             underline,
         };
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_scheme_selected: {}", e)))?;
-
-    bar_table.set("set_font", set_font)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_font: {}", e)))?;
-    bar_table.set("add_block", add_block)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set add_block: {}", e)))?;
-    bar_table.set("set_scheme_normal", set_scheme_normal)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_scheme_normal: {}", e)))?;
-    bar_table.set("set_scheme_occupied", set_scheme_occupied)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_scheme_occupied: {}", e)))?;
-    bar_table.set("set_scheme_selected", set_scheme_selected)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_scheme_selected: {}", e)))?;
-    parent.set("bar", bar_table)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set bar: {}", e)))?;
+    })?;
+
+    bar_table.set("set_font", set_font)?;
+    bar_table.set("block", block_table)?;
+    bar_table.set("add_block", add_block)?;  // Deprecated, for backwards compatibility
+    bar_table.set("set_blocks", set_blocks)?;
+    bar_table.set("set_scheme_normal", set_scheme_normal)?;
+    bar_table.set("set_scheme_occupied", set_scheme_occupied)?;
+    bar_table.set("set_scheme_selected", set_scheme_selected)?;
+    parent.set("bar", bar_table)?;
     Ok(())
 }
 
@@ -485,7 +562,7 @@ fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<()
     let set_terminal = lua.create_function(move |_, term: String| {
         builder_clone.borrow_mut().terminal = term;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_terminal: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_modkey = lua.create_function(move |_, modkey_str: String| {
@@ -493,37 +570,37 @@ fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<()
             .map_err(|e| mlua::Error::RuntimeError(format!("{}", e)))?;
         builder_clone.borrow_mut().modkey = modkey;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_modkey: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_tags = lua.create_function(move |_, tags: Vec<String>| {
         builder_clone.borrow_mut().tags = tags;
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_tags: {}", e)))?;
+    })?;
 
     let quit = lua.create_function(|lua, ()| {
         create_action_table(lua, "Quit", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create quit: {}", e)))?;
+    })?;
 
     let restart = lua.create_function(|lua, ()| {
         create_action_table(lua, "Restart", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create restart: {}", e)))?;
+    })?;
 
     let recompile = lua.create_function(|lua, ()| {
         create_action_table(lua, "Recompile", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create recompile: {}", e)))?;
+    })?;
 
     let toggle_gaps = lua.create_function(|lua, ()| {
         create_action_table(lua, "ToggleGaps", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create toggle_gaps: {}", e)))?;
+    })?;
 
     let show_keybinds = lua.create_function(|lua, ()| {
         create_action_table(lua, "ShowKeybindOverlay", Value::Nil)
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create show_keybinds: {}", e)))?;
+    })?;
 
     let focus_monitor = lua.create_function(|lua, idx: i32| {
         create_action_table(lua, "FocusMonitor", Value::Integer(idx as i64))
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create focus_monitor: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let set_layout_symbol = lua.create_function(move |_, (name, symbol): (String, String)| {
@@ -532,36 +609,25 @@ fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<()
             symbol,
         });
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create set_layout_symbol: {}", e)))?;
+    })?;
 
     let builder_clone = builder.clone();
     let autostart = lua.create_function(move |_, cmd: String| {
         builder_clone.borrow_mut().autostart.push(cmd);
         Ok(())
-    }).map_err(|e| ConfigError::LuaError(format!("Failed to create autostart: {}", e)))?;
-
-    parent.set("set_terminal", set_terminal)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_terminal: {}", e)))?;
-    parent.set("set_modkey", set_modkey)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_modkey: {}", e)))?;
-    parent.set("set_tags", set_tags)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_tags: {}", e)))?;
-    parent.set("set_layout_symbol", set_layout_symbol)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set set_layout_symbol: {}", e)))?;
-    parent.set("autostart", autostart)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set autostart: {}", e)))?;
-    parent.set("quit", quit)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set quit: {}", e)))?;
-    parent.set("restart", restart)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set restart: {}", e)))?;
-    parent.set("recompile", recompile)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set recompile: {}", e)))?;
-    parent.set("toggle_gaps", toggle_gaps)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set toggle_gaps: {}", e)))?;
-    parent.set("show_keybinds", show_keybinds)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set show_keybinds: {}", e)))?;
-    parent.set("focus_monitor", focus_monitor)
-        .map_err(|e| ConfigError::LuaError(format!("Failed to set focus_monitor: {}", e)))?;
+    })?;
+
+    parent.set("set_terminal", set_terminal)?;
+    parent.set("set_modkey", set_modkey)?;
+    parent.set("set_tags", set_tags)?;
+    parent.set("set_layout_symbol", set_layout_symbol)?;
+    parent.set("autostart", autostart)?;
+    parent.set("quit", quit)?;
+    parent.set("restart", restart)?;
+    parent.set("recompile", recompile)?;
+    parent.set("toggle_gaps", toggle_gaps)?;
+    parent.set("show_keybinds", show_keybinds)?;
+    parent.set("focus_monitor", focus_monitor)?;
     Ok(())
 }
 
@@ -638,6 +704,7 @@ fn parse_action_value(_lua: &Lua, value: Value) -> mlua::Result<(KeyAction, Arg)
 fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
     match s {
         "Spawn" => Ok(KeyAction::Spawn),
+        "SpawnTerminal" => Ok(KeyAction::SpawnTerminal),
         "KillClient" => Ok(KeyAction::KillClient),
         "FocusStack" => Ok(KeyAction::FocusStack),
         "FocusDirection" => Ok(KeyAction::FocusDirection),
@@ -719,3 +786,24 @@ fn parse_color_value(value: Value) -> mlua::Result<u32> {
         )),
     }
 }
+
+fn create_block_config(lua: &Lua, config: Table, block_type: &str, arg: Option<Value>) -> mlua::Result<Table> {
+    let table = lua.create_table()?;
+    table.set("__block_type", block_type)?;
+
+    let format: String = config.get("format").unwrap_or_default();
+    let interval: u64 = config.get("interval")?;
+    let color: Value = config.get("color")?;
+    let underline: bool = config.get("underline").unwrap_or(false);
+
+    table.set("format", format)?;
+    table.set("interval", interval)?;
+    table.set("color", color)?;
+    table.set("underline", underline)?;
+
+    if let Some(arg_val) = arg {
+        table.set("__arg", arg_val)?;
+    }
+
+    Ok(table)
+}
diff --git a/src/errors.rs b/src/errors.rs
index f2286e2..fad5b15 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -161,3 +161,19 @@ impl From<x11rb::errors::ReplyOrIdError> for X11Error {
         X11Error::ReplyOrIdError(value)
     }
 }
+
+impl From<mlua::Error> for ConfigError {
+    fn from(err: mlua::Error) -> Self {
+        ConfigError::LuaError(err.to_string())
+    }
+}
+
+pub trait LuaResultExt<T> {
+    fn lua_context(self, context: &str) -> Result<T, ConfigError>;
+}
+
+impl<T> LuaResultExt<T> for Result<T, mlua::Error> {
+    fn lua_context(self, context: &str) -> Result<T, ConfigError> {
+        self.map_err(|e| ConfigError::LuaError(format!("{}: {}", context, e)))
+    }
+}
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 677ca0e..fabb6cd 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -12,6 +12,7 @@ use crate::keyboard::keysyms::{self, Keysym};
 #[derive(Debug, Copy, Clone, Deserialize, PartialEq)]
 pub enum KeyAction {
     Spawn,
+    SpawnTerminal,
     KillClient,
     FocusStack,
     FocusDirection,
@@ -22,7 +23,6 @@ pub enum KeyAction {
     ViewTag,
     ToggleGaps,
     ToggleFullScreen,
-    ToggleWindowFullscreen,
     ToggleFloating,
     ChangeLayout,
     CycleLayout,
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index eaac7d0..08bb24e 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,4 +1,5 @@
 pub mod grid;
+pub mod monocle;
 pub mod normie;
 pub mod tiling;
 
@@ -17,6 +18,7 @@ pub enum LayoutType {
     Tiling,
     Normie,
     Grid,
+    Monocle,
 }
 
 impl LayoutType {
@@ -25,6 +27,7 @@ impl LayoutType {
             Self::Tiling => Box::new(tiling::TilingLayout),
             Self::Normie => Box::new(normie::NormieLayout),
             Self::Grid => Box::new(grid::GridLayout),
+            Self::Monocle => Box::new(monocle::MonocleLayout),
         }
     }
 
@@ -32,7 +35,8 @@ impl LayoutType {
         match self {
             Self::Tiling => Self::Normie,
             Self::Normie => Self::Grid,
-            Self::Grid => Self::Tiling,
+            Self::Grid => Self::Monocle,
+            Self::Monocle => Self::Tiling,
         }
     }
 
@@ -41,6 +45,7 @@ impl LayoutType {
             Self::Tiling => "tiling",
             Self::Normie => "normie",
             Self::Grid => "grid",
+            Self::Monocle => "monocle",
         }
     }
 
@@ -49,6 +54,7 @@ impl LayoutType {
             "tiling" => Ok(Self::Tiling),
             "normie" | "floating" => Ok(Self::Normie),
             "grid" => Ok(Self::Grid),
+            "monocle" => Ok(Self::Monocle),
             _ => Err(format!("Invalid Layout Type: {}", s)),
         }
     }
@@ -79,6 +85,7 @@ pub trait Layout {
     fn symbol(&self) -> &'static str;
 }
 
+#[derive(Clone)]
 pub struct WindowGeometry {
     pub x_coordinate: i32,
     pub y_coordinate: i32,
diff --git a/src/layout/monocle.rs b/src/layout/monocle.rs
new file mode 100644
index 0000000..8bc8d10
--- /dev/null
+++ b/src/layout/monocle.rs
@@ -0,0 +1,41 @@
+use super::{GapConfig, Layout, WindowGeometry};
+use x11rb::protocol::xproto::Window;
+
+pub struct MonocleLayout;
+
+impl Layout for MonocleLayout {
+    fn name(&self) -> &'static str {
+        super::LayoutType::Monocle.as_str()
+    }
+
+    fn symbol(&self) -> &'static str {
+        "[M]"
+    }
+
+    fn arrange(
+        &self,
+        windows: &[Window],
+        screen_width: u32,
+        screen_height: u32,
+        gaps: &GapConfig,
+    ) -> Vec<WindowGeometry> {
+        let window_count = windows.len();
+        if window_count == 0 {
+            return Vec::new();
+        }
+
+        let x = gaps.outer_horizontal as i32;
+        let y = gaps.outer_vertical as i32;
+        let width = screen_width.saturating_sub(2 * gaps.outer_horizontal);
+        let height = screen_height.saturating_sub(2 * gaps.outer_vertical);
+
+        let geometry = WindowGeometry {
+            x_coordinate: x,
+            y_coordinate: y,
+            width,
+            height,
+        };
+
+        vec![geometry; window_count]
+    }
+}
diff --git a/src/monitor.rs b/src/monitor.rs
index cfd2d06..1693cac 100644
--- a/src/monitor.rs
+++ b/src/monitor.rs
@@ -13,7 +13,6 @@ pub struct Monitor {
     pub height: u32,
     pub selected_tags: u32,
     pub focused_window: Option<Window>,
-    pub fullscreen_enabled: bool,
 }
 
 impl Monitor {
@@ -25,7 +24,6 @@ impl Monitor {
             height,
             selected_tags: 1,
             focused_window: None,
-            fullscreen_enabled: false,
         }
     }
 
diff --git a/src/overlay/keybind.rs b/src/overlay/keybind.rs
index 6bad95c..e0d9fdf 100644
--- a/src/overlay/keybind.rs
+++ b/src/overlay/keybind.rs
@@ -141,6 +141,7 @@ impl KeybindOverlay {
             KeyAction::Restart,
             KeyAction::KillClient,
             KeyAction::Spawn,
+            KeyAction::SpawnTerminal,
             KeyAction::ToggleFullScreen,
             KeyAction::ToggleFloating,
             KeyAction::CycleLayout,
@@ -200,6 +201,7 @@ impl KeybindOverlay {
                 Arg::Array(arr) if !arr.is_empty() => format!("Launch: {}", arr[0]),
                 _ => "Launch Program".to_string(),
             },
+            KeyAction::SpawnTerminal => "Launch Terminal".to_string(),
             KeyAction::FocusStack => "Focus Next/Previous Window".to_string(),
             KeyAction::FocusDirection => "Focus Window in Direction".to_string(),
             KeyAction::SwapDirection => "Swap Window in Direction".to_string(),
@@ -210,7 +212,6 @@ impl KeybindOverlay {
             KeyAction::MoveToTag => "Move Window to Workspace".to_string(),
             KeyAction::ToggleGaps => "Toggle Window Gaps".to_string(),
             KeyAction::ToggleFullScreen => "Toggle Fullscreen Mode".to_string(),
-            KeyAction::ToggleWindowFullscreen => "Toggle Window Fullscreen".to_string(),
             KeyAction::ToggleFloating => "Toggle Floating Mode".to_string(),
             KeyAction::ChangeLayout => "Change Layout".to_string(),
             KeyAction::CycleLayout => "Cycle Through Layouts".to_string(),
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 6f60e12..ee9ee69 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -87,11 +87,12 @@ pub struct WindowManager {
     layout: LayoutBox,
     window_tags: std::collections::HashMap<Window, TagMask>,
     window_monitor: std::collections::HashMap<Window, usize>,
-    window_geometries: std::collections::HashMap<Window, (i16, i16, u16, u16)>,
     gaps_enabled: bool,
     floating_windows: HashSet<Window>,
     fullscreen_windows: HashSet<Window>,
     bars: Vec<Bar>,
+    show_bar: bool,
+    last_layout: Option<&'static str>,
     monitors: Vec<Monitor>,
     selected_monitor: usize,
     atoms: AtomCache,
@@ -211,11 +212,12 @@ impl WindowManager {
             layout: Box::new(TilingLayout),
             window_tags: std::collections::HashMap::new(),
             window_monitor: std::collections::HashMap::new(),
-            window_geometries: std::collections::HashMap::new(),
             gaps_enabled,
             floating_windows: HashSet::new(),
             fullscreen_windows: HashSet::new(),
             bars,
+            show_bar: true,
+            last_layout: None,
             monitors,
             selected_monitor: 0,
             atoms,
@@ -857,22 +859,6 @@ impl WindowManager {
         Ok(())
     }
 
-    fn toggle_fullscreen(&mut self) -> WmResult<()> {
-        if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
-            monitor.fullscreen_enabled = !monitor.fullscreen_enabled;
-
-            if let Some(bar) = self.bars.get(self.selected_monitor) {
-                if monitor.fullscreen_enabled {
-                    self.connection.unmap_window(bar.window())?;
-                } else {
-                    self.connection.map_window(bar.window())?;
-                }
-            }
-
-            self.apply_layout()?;
-        }
-        Ok(())
-    }
 
     fn get_layout_symbol(&self) -> String {
         let layout_name = self.layout.name();
@@ -961,6 +947,11 @@ impl WindowManager {
     fn handle_key_action(&mut self, action: KeyAction, arg: &Arg) -> WmResult<()> {
         match action {
             KeyAction::Spawn => handlers::handle_spawn_action(action, arg)?,
+            KeyAction::SpawnTerminal => {
+                use std::process::Command;
+                let terminal = &self.config.terminal;
+                let _ = Command::new(terminal).spawn();
+            }
             KeyAction::KillClient => {
                 if let Some(focused) = self
                     .monitors
@@ -971,45 +962,16 @@ impl WindowManager {
                 }
             }
             KeyAction::ToggleFullScreen => {
-                // Smart fullscreen:
-                // - In normie (floating) layout: always fullscreen the focused window
-                // - With explicitly floating window: fullscreen that window
-                // - Otherwise: do monitor-level fullscreen (hide bar)
-                let is_normie_layout = self.layout.name() == "normie";
-
-                if let Some(focused) = self
-                    .monitors
-                    .get(self.selected_monitor)
-                    .and_then(|m| m.focused_window)
-                {
-                    if is_normie_layout || self.floating_windows.contains(&focused) {
-                        // In floating layout or window is floating - do per-window fullscreen
-                        let is_fullscreen = self.fullscreen_windows.contains(&focused);
-                        self.set_window_fullscreen(focused, !is_fullscreen)?;
-                    } else {
-                        // Window is tiled - do monitor fullscreen
-                        self.toggle_fullscreen()?;
-                    }
-                } else {
-                    // No focused window - do monitor fullscreen
-                    self.toggle_fullscreen()?;
-                }
-            }
-            KeyAction::ToggleWindowFullscreen => {
-                if let Some(focused) = self
-                    .monitors
-                    .get(self.selected_monitor)
-                    .and_then(|m| m.focused_window)
-                {
-                    let is_fullscreen = self.fullscreen_windows.contains(&focused);
-                    self.set_window_fullscreen(focused, !is_fullscreen)?;
-                }
+                self.fullscreen()?;
             }
             KeyAction::ChangeLayout => {
                 if let Arg::Str(layout_name) = arg {
                     match layout_from_str(layout_name) {
                         Ok(layout) => {
                             self.layout = layout;
+                            if layout_name != "normie" && layout_name != "floating" {
+                                self.floating_windows.clear();
+                            }
                             self.apply_layout()?;
                             self.update_bar()?;
                         }
@@ -1023,6 +985,9 @@ impl WindowManager {
                 match layout_from_str(next_name) {
                     Ok(layout) => {
                         self.layout = layout;
+                        if next_name != "normie" && next_name != "floating" {
+                            self.floating_windows.clear();
+                        }
                         self.apply_layout()?;
                         self.update_bar()?;
                     }
@@ -1583,14 +1548,26 @@ impl WindowManager {
         Ok(true)
     }
 
-    fn set_window_fullscreen(&mut self, window: Window, fullscreen: bool) -> WmResult<()> {
-        if fullscreen && !self.fullscreen_windows.contains(&window) {
-            // Save current geometry before fullscreen
-            if let Ok(geom) = self.connection.get_geometry(window)?.reply() {
-                self.window_geometries.insert(window, (geom.x, geom.y, geom.width, geom.height));
+    fn fullscreen(&mut self) -> WmResult<()> {
+        if self.show_bar {
+            self.last_layout = Some(self.layout.name());
+            if let Ok(layout) = layout_from_str("monocle") {
+                self.layout = layout;
             }
+        } else {
+            if let Some(last) = self.last_layout {
+                if let Ok(layout) = layout_from_str(last) {
+                    self.layout = layout;
+                }
+            }
+        }
+        self.toggle_bar()?;
+        Ok(())
+    }
 
-            // Set the _NET_WM_STATE property to fullscreen
+    fn set_window_fullscreen(&mut self, window: Window, fullscreen: bool) -> WmResult<()> {
+        if fullscreen && !self.fullscreen_windows.contains(&window) {
+            let bytes = self.atoms.net_wm_state_fullscreen.to_ne_bytes().to_vec();
             self.connection.change_property(
                 PropMode::REPLACE,
                 window,
@@ -1598,56 +1575,82 @@ impl WindowManager {
                 AtomEnum::ATOM,
                 32,
                 1,
-                &self.atoms.net_wm_state_fullscreen.to_ne_bytes(),
+                &bytes,
             )?;
 
             self.fullscreen_windows.insert(window);
+
+            self.connection.configure_window(
+                window,
+                &x11rb::protocol::xproto::ConfigureWindowAux::new()
+                    .border_width(0),
+            )?;
+
             self.floating_windows.insert(window);
 
-            // Get monitor dimensions
-            let window_mon = self.window_monitor.get(&window).copied().unwrap_or(self.selected_monitor);
-            let monitor = &self.monitors[window_mon];
+            let monitor_idx = *self.window_monitor.get(&window).unwrap_or(&self.selected_monitor);
+            let monitor = &self.monitors[monitor_idx];
 
-            // Make window fullscreen across the entire monitor
             self.connection.configure_window(
                 window,
-                &ConfigureWindowAux::new()
+                &x11rb::protocol::xproto::ConfigureWindowAux::new()
                     .x(monitor.x)
                     .y(monitor.y)
-                    .width(monitor.width)
-                    .height(monitor.height)
-                    .border_width(0)
-                    .stack_mode(StackMode::ABOVE),
+                    .width(monitor.width as u32)
+                    .height(monitor.height as u32),
+            )?;
+
+            self.connection.configure_window(
+                window,
+                &x11rb::protocol::xproto::ConfigureWindowAux::new()
+                    .stack_mode(x11rb::protocol::xproto::StackMode::ABOVE),
             )?;
 
             self.connection.flush()?;
         } else if !fullscreen && self.fullscreen_windows.contains(&window) {
-            // Clear the _NET_WM_STATE property
-            self.connection.delete_property(window, self.atoms.net_wm_state)?;
+            self.connection.change_property(
+                PropMode::REPLACE,
+                window,
+                self.atoms.net_wm_state,
+                AtomEnum::ATOM,
+                32,
+                0,
+                &[],
+            )?;
 
             self.fullscreen_windows.remove(&window);
 
-            // Restore saved geometry
-            if let Some((x, y, width, height)) = self.window_geometries.get(&window) {
-                self.connection.configure_window(
-                    window,
-                    &ConfigureWindowAux::new()
-                        .x(*x as i32)
-                        .y(*y as i32)
-                        .width(*width as u32)
-                        .height(*height as u32)
-                        .border_width(self.config.border_width),
-                )?;
+            if !self.is_transient_window(window) {
+                self.floating_windows.remove(&window);
             }
 
+            self.connection.configure_window(
+                window,
+                &x11rb::protocol::xproto::ConfigureWindowAux::new()
+                    .border_width(self.config.border_width),
+            )?;
+
             self.apply_layout()?;
+        }
+
+        Ok(())
+    }
+
+    fn toggle_bar(&mut self) -> WmResult<()> {
+        self.show_bar = !self.show_bar;
+        if let Some(bar) = self.bars.get(self.selected_monitor) {
+            if self.show_bar {
+                self.connection.map_window(bar.window())?;
+            } else {
+                self.connection.unmap_window(bar.window())?;
+            }
             self.connection.flush()?;
         }
+        self.apply_layout()?;
         Ok(())
     }
 
     fn is_transient_window(&self, window: Window) -> bool {
-        // Check if window is transient (e.g., dialog, modal)
         let transient_for = self.connection
             .get_property(
                 false,
@@ -1900,11 +1903,6 @@ impl WindowManager {
                     return Ok(None);
                 }
 
-                if let Ok(geom) = self.connection.get_geometry(event.window)?.reply() {
-                    self.window_geometries
-                        .insert(event.window, (geom.x, geom.y, geom.width, geom.height));
-                }
-
                 self.connection.map_window(event.window)?;
                 self.connection.change_window_attributes(
                     event.window,
@@ -1924,37 +1922,61 @@ impl WindowManager {
                 self.set_wm_state(event.window, 1)?;
                 let _ = self.save_client_tag(event.window, selected_tags);
 
-                // Auto-float transient windows (e.g., dialogs, modals)
-                if self.is_transient_window(event.window) {
+                let is_transient = self.is_transient_window(event.window);
+                if is_transient {
                     self.floating_windows.insert(event.window);
                 }
 
+                let is_normie_layout = self.layout.name() == "normie";
+                if is_normie_layout && !is_transient {
+                    let mut pointer_x: i32 = 0;
+                    let mut pointer_y: i32 = 0;
+                    let mut root_return: x11::xlib::Window = 0;
+                    let mut child_return: x11::xlib::Window = 0;
+                    let mut win_x: i32 = 0;
+                    let mut win_y: i32 = 0;
+                    let mut mask_return: u32 = 0;
+
+                    let pointer_queried = unsafe {
+                        x11::xlib::XQueryPointer(
+                            self.display,
+                            self.root as x11::xlib::Window,
+                            &mut root_return,
+                            &mut child_return,
+                            &mut pointer_x,
+                            &mut pointer_y,
+                            &mut win_x,
+                            &mut win_y,
+                            &mut mask_return,
+                        ) != 0
+                    };
+
+                    if pointer_queried {
+                        let window_width = (self.screen.width_in_pixels as f32 * 0.6) as u32;
+                        let window_height = (self.screen.height_in_pixels as f32 * 0.6) as u32;
+
+                        let spawn_x = pointer_x - (window_width as i32 / 2);
+                        let spawn_y = pointer_y - (window_height as i32 / 2);
+
+                        self.connection.configure_window(
+                            event.window,
+                            &ConfigureWindowAux::new()
+                                .x(spawn_x)
+                                .y(spawn_y)
+                                .width(window_width)
+                                .height(window_height)
+                                .border_width(self.config.border_width)
+                                .stack_mode(StackMode::ABOVE),
+                        )?;
+
+                        self.floating_windows.insert(event.window);
+                    }
+                }
+
                 self.apply_layout()?;
                 self.update_bar()?;
                 self.set_focus(event.window)?;
             }
-            Event::ClientMessage(event) => {
-                // Handle _NET_WM_STATE fullscreen requests from clients
-                if event.type_ == self.atoms.net_wm_state {
-                    let data = event.data.as_data32();
-                    let action = data[0];  // 0 = remove, 1 = add, 2 = toggle
-                    let property1 = data[1];
-                    let property2 = data[2];
-
-                    if property1 == self.atoms.net_wm_state_fullscreen || property2 == self.atoms.net_wm_state_fullscreen {
-                        if self.windows.contains(&event.window) {
-                            let is_fullscreen = self.fullscreen_windows.contains(&event.window);
-                            let should_fullscreen = match action {
-                                0 => false,  // Remove
-                                1 => true,   // Add
-                                2 => !is_fullscreen,  // Toggle
-                                _ => is_fullscreen,
-                            };
-                            self.set_window_fullscreen(event.window, should_fullscreen)?;
-                        }
-                    }
-                }
-            }
             Event::UnmapNotify(event) => {
                 if self.windows.contains(&event.window) && self.is_window_visible(event.window) {
                     self.remove_window(event.window)?;
@@ -2093,6 +2115,22 @@ impl WindowManager {
                     }
                 }
             }
+            Event::ClientMessage(event) => {
+                if event.type_ == self.atoms.net_wm_state {
+                    if let Some(data) = event.data.as_data32().get(1) {
+                        if *data == self.atoms.net_wm_state_fullscreen {
+                            let action = event.data.as_data32()[0];
+                            let fullscreen = match action {
+                                1 => true,
+                                0 => false,
+                                2 => !self.fullscreen_windows.contains(&event.window),
+                                _ => return Ok(None),
+                            };
+                            self.set_window_fullscreen(event.window, fullscreen)?;
+                        }
+                    }
+                }
+            }
             _ => {}
         }
         Ok(None)
@@ -2104,13 +2142,9 @@ impl WindowManager {
         }
 
         for (monitor_index, monitor) in self.monitors.iter().enumerate() {
-            let border_width = if monitor.fullscreen_enabled {
-                0
-            } else {
-                self.config.border_width
-            };
+            let border_width = self.config.border_width;
 
-            let gaps = if self.gaps_enabled && !monitor.fullscreen_enabled {
+            let gaps = if self.gaps_enabled {
                 GapConfig {
                     inner_horizontal: self.config.gap_inner_horizontal,
                     inner_vertical: self.config.gap_inner_vertical,
@@ -2146,13 +2180,13 @@ impl WindowManager {
                 .copied()
                 .collect();
 
-            let bar_height = if monitor.fullscreen_enabled {
-                0
-            } else {
+            let bar_height = if self.show_bar {
                 self.bars
                     .get(monitor_index)
                     .map(|b| b.height() as u32)
                     .unwrap_or(0)
+            } else {
+                0
             };
             let usable_height = monitor.height.saturating_sub(bar_height);
 
@@ -2191,12 +2225,11 @@ impl WindowManager {
 
     fn remove_window(&mut self, window: Window) -> WmResult<()> {
         let initial_count = self.windows.len();
+
         self.windows.retain(|&w| w != window);
         self.window_tags.remove(&window);
         self.window_monitor.remove(&window);
-        self.window_geometries.remove(&window);
         self.floating_windows.remove(&window);
-        self.fullscreen_windows.remove(&window);
 
         if self.windows.len() < initial_count {
             let focused = self
diff --git a/templates/config.lua b/templates/config.lua
index 2fe38aa..1f5cf95 100644
--- a/templates/config.lua
+++ b/templates/config.lua
@@ -173,27 +173,66 @@ oxwm.key.chord({
 -------------------------------------------------------------------------------
 -- Status Bar Blocks
 -------------------------------------------------------------------------------
--- Add informational blocks to the status bar
--- Format: oxwm.bar.add_block(format, type, data, update_interval, color, separator)
+-- Add informational blocks to the status bar using block constructors
+-- Each block is created with oxwm.bar.block.<type>() and configured with a table:
 --   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)
+--   interval: Seconds between updates
 --   color: Text color (from color palette)
---   separator: Whether to add space after this block
-
-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)
-
--- Uncomment to add battery status (useful for laptops)
--- oxwm.bar.add_block("Bat: {}%", "Battery", {
---     charging = "⚡ Bat: {}%",
---     discharging = "🔋 Bat: {}%",
---     full = "✓ Bat: {}%"
--- }, 30, colors.green, true)
+--   underline: Whether to underline the block
+--
+-- Available block types:
+--   ram(config)         - Memory usage
+--   datetime(config)    - Date and time (requires date_format field)
+--   shell(config)       - Shell command output (requires command field)
+--   static(config)      - Static text (requires text field)
+--   battery(config)     - Battery status (requires charging, discharging, full fields)
+
+oxwm.bar.set_blocks({
+    oxwm.bar.block.ram({
+        format = "Ram: {used}/{total} GB",
+        interval = 5,
+        color = colors.light_blue,
+        underline = true,
+    }),
+    oxwm.bar.block.static({
+        format = "{}",
+        text = " │  ",
+        interval = 999999999,
+        color = colors.lavender,
+        underline = false,
+    }),
+    oxwm.bar.block.shell({
+        format = "Kernel: {}",
+        command = "uname -r",
+        interval = 999999999,
+        color = colors.red,
+        underline = true,
+    }),
+    oxwm.bar.block.static({
+        format = "{}",
+        text = " │  ",
+        interval = 999999999,
+        color = colors.lavender,
+        underline = false,
+    }),
+    oxwm.bar.block.datetime({
+        format = "{}",
+        date_format = "%a, %b %d - %-I:%M %P",
+        interval = 1,
+        color = colors.cyan,
+        underline = true,
+    }),
+    -- Uncomment to add battery status (useful for laptops)
+    -- oxwm.bar.block.battery({
+    --     format = "Bat: {}%",
+    --     charging = "⚡ Bat: {}%",
+    --     discharging = "🔋 Bat: {}%",
+    --     full = "✓ Bat: {}%",
+    --     interval = 30,
+    --     color = colors.green,
+    --     underline = true,
+    -- }),
+})
 
 -------------------------------------------------------------------------------
 -- Autostart
diff --git a/templates/oxwm.lua b/templates/oxwm.lua
index f79e5cb..811f47d 100644
--- a/templates/oxwm.lua
+++ b/templates/oxwm.lua
@@ -1,6 +1,18 @@
 ---@meta
 
----OXWM Configuration API
+---OXWM Configuration API Type Definitions
+---
+---DO NOT EDIT THIS FILE
+---This file is automatically generated and managed by OXWM.
+---It will be overwritten on every OXWM update to match the running version.
+---
+---Location: ~/.config/oxwm/lib/oxwm.lua
+---Configured via: ~/.config/oxwm/.luarc.json
+---
+---This file provides LSP autocomplete and type checking for your config.lua.
+---To use it, add this to the top of your config.lua:
+---  ---@module 'oxwm'
+---
 ---@class oxwm
 oxwm = {}
 
@@ -9,6 +21,10 @@ oxwm = {}
 ---@return table Action table for keybinding
 function oxwm.spawn(cmd) end
 
+---Spawn the configured terminal emulator
+---@return table Action table for keybinding
+function oxwm.spawn_terminal() end
+
 ---Set the terminal emulator
 ---@param terminal string Terminal command (e.g., "st", "alacritty")
 function oxwm.set_terminal(terminal) end
@@ -181,15 +197,49 @@ oxwm.bar = {}
 ---@param font string Font string (e.g., "monospace:style=Bold:size=10")
 function oxwm.bar.set_font(font) end
 
----Add a status bar block
+---DEPRECATED: Add a status bar block (use oxwm.bar.set_blocks with block constructors instead)
+---@deprecated
 ---@param format string Format string with {} placeholders
----@param command "DateTime"|"Shell"|"Ram"|"Static"|"Battery" Block command type
----@param arg string|table|nil Command argument (format for DateTime, command for Shell, text for Static, formats table for Battery, nil for Ram)
+---@param command "DateTime"|"Shell"|"Ram"|"Static" Block command type (Battery not supported)
+---@param arg string|nil Command argument (format for DateTime, command for Shell, text for Static, nil for Ram)
 ---@param interval integer Update interval in seconds
 ---@param color string|integer Color as hex string or integer
 ---@param underline boolean Whether to underline the block
 function oxwm.bar.add_block(format, command, arg, interval, color, underline) end
 
+---Set status bar blocks using block constructors
+---@param blocks table[] Array of block configurations created with oxwm.bar.block.*
+function oxwm.bar.set_blocks(blocks) end
+
+---Block constructors module
+---@class oxwm.bar.block
+oxwm.bar.block = {}
+
+---Create a RAM usage block
+---@param config {format: string, interval: integer, color: string|integer, underline: boolean} Block configuration
+---@return table Block configuration
+function oxwm.bar.block.ram(config) end
+
+---Create a date/time block
+---@param config {format: string, date_format: string, interval: integer, color: string|integer, underline: boolean} Block configuration (format is display template with {}, date_format is strftime format)
+---@return table Block configuration
+function oxwm.bar.block.datetime(config) end
+
+---Create a shell command block
+---@param config {format: string, command: string, interval: integer, color: string|integer, underline: boolean} Block configuration
+---@return table Block configuration
+function oxwm.bar.block.shell(config) end
+
+---Create a static text block
+---@param config {format: string, text: string, interval: integer, color: string|integer, underline: boolean} Block configuration
+---@return table Block configuration
+function oxwm.bar.block.static(config) end
+
+---Create a battery status block
+---@param config {format: string, charging: string, discharging: string, full: string, interval: integer, color: string|integer, underline: boolean} Block configuration
+---@return table Block configuration
+function oxwm.bar.block.battery(config) end
+
 ---Set normal tag color scheme (unselected, no windows)
 ---@param foreground string|integer Foreground color
 ---@param background string|integer Background color