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