Diff
diff --git a/Cargo.toml b/Cargo.toml
index 0917b3a..8383b76 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "oxwm"
-version = "0.5.0"
+version = "0.6.0"
edition = "2024"
[lib]
diff --git a/default.nix b/default.nix
index d0e1c0c..f0ca1bc 100644
--- a/default.nix
+++ b/default.nix
@@ -8,7 +8,7 @@
}:
rustPlatform.buildRustPackage (finalAttrs: {
pname = "oxwm";
- version = "0.4.0";
+ version = "0.6.0";
src = ./.;
diff --git a/readme.org b/readme.org
index 93828e8..9423936 100644
--- a/readme.org
+++ b/readme.org
@@ -79,7 +79,7 @@ Add this to your =configuration.nix=:
*** Initialize your config
After rebuilding your system with =sudo nixos-rebuild switch=, log in via your display manager.
-On first launch, your initial config file will be automatically created and placed in =~/.config/oxwm/config.ron=. Edit it and reload with =Mod+Shift+R=.
+On first launch, your initial config file will be automatically created and placed in =~/.config/oxwm/config.lua=. Edit it and reload with =Mod+Shift+R=.
*** Advanced: Using a specific oxwm version
If you want to pin or customize the oxwm package:
@@ -154,22 +154,33 @@ startx
If using a display manager (LightDM, GDM, SDDM), OXWM should appear in the session list after installation.
* Configuration
-OXWM was inspired by dwm, but ditched the suckless philosophy. This philosophy quite literally discourages users from using the software for the sake of 'elitism'. I find that quite nonsensical, so I went ahead and created this project to be user friendly. The configuration is done by editing =~/.config/oxwm/config.ron= and the binary can be reloaded with a hotkey (Super+Shift+R by default).
+OXWM was inspired by dwm, but ditched the suckless philosophy. This philosophy quite literally discourages users from using the software for the sake of 'elitism'. I find that quite nonsensical, so I went ahead and created this project to be user friendly. The configuration is done by editing =~/.config/oxwm/config.lua= and the binary can be reloaded with a hotkey (Super+Shift+R by default).
-Edit =~/.config/oxwm/config.ron= to customize:
+Edit =~/.config/oxwm/config.lua= to customize:
- Keybindings
- Colors and appearance
- Status bar blocks
- Gaps and borders
- Terminal and applications
-After making changes, reload OXWM with =Mod+Shift+R=
+After making changes, reload OXWM with =Mod+Shift+R=
+
+** Migrating from RON
+If you have an old RON config (=config.ron=), use the migration tool to automatically convert it:
+
+#+begin_src sh
+oxwm --migrate
+# Or specify a path:
+oxwm --migrate ~/.config/oxwm/config.ron
+#+end_src
+
+This will convert your =config.ron= to =config.lua=, preserving all your settings. Your old config will remain intact for backup.
* Contributing
When contributing to OXWM:
-1. Never commit your personal =~/.config/oxwm/config.ron=
-2. Only modify =templates/config.ron= if adding new configuration options
+1. Never commit your personal =~/.config/oxwm/config.lua=
+2. Only modify =templates/config.lua= if adding new configuration options
3. Test your changes with =just test= using Xephyr/Xwayland
4. Document any new features or keybindings
diff --git a/resources/test-config.lua b/resources/test-config.lua
index 28524c2..d0c5aac 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -1,111 +1,31 @@
--- OXWM Test Configuration File (Lua)
--- This config uses Mod1 (Alt) as the modkey for testing in Xephyr
+-- OXWM Configuration File (Lua)
+-- Migrated from config.ron
+-- Edit this file and reload with Mod+Shift+R (no compilation needed!)
----@class LayoutSymbol
----@field name string The internal layout name (e.g., "tiling", "normie")
----@field symbol string The display symbol for the layout (e.g., "[T]", "[F]")
-
----@class KeyBinding
----@field modifiers string[] List of modifiers: "Mod", "Mod1"-"Mod5", "Shift", "Control"
----@field key string The key name (e.g., "Return", "Q", "1", "Space")
----@field action string Action to perform (see below for list)
----@field arg? string|number|string[] Optional argument for the action
-
----@class KeyChord
----@field keys {modifiers: string[], key: string}[] Sequence of keys to press
----@field action string Action to perform when chord completes
----@field arg? string|number|string[] Optional argument for the action
-
----@class StatusBlock
----@field format string Display format with {} placeholders
----@field command string Command type: "Ram", "DateTime", "Shell", "Static", "Battery"
----@field command_arg? string Argument for command (shell command, date format, etc.)
----@field interval_secs number Update interval in seconds
----@field color number Color as hex number (e.g., 0xff0000 for red)
----@field underline boolean Whether to show underline
----@field battery_formats? {charging: string, discharging: string, full: string} Battery format strings
-
----@class ColorScheme
----@field foreground number Foreground color (hex)
----@field background number Background color (hex)
----@field underline number Underline color (hex)
-
----@class Config
----@field border_width number Width of window borders in pixels
----@field border_focused number Color for focused window border (hex)
----@field border_unfocused number Color for unfocused window border (hex)
----@field font string Font specification (e.g., "monospace:style=Bold:size=10")
----@field gaps_enabled boolean Whether gaps are enabled
----@field gap_inner_horizontal number Inner horizontal gap size
----@field gap_inner_vertical number Inner vertical gap size
----@field gap_outer_horizontal number Outer horizontal gap size
----@field gap_outer_vertical number Outer vertical gap size
----@field modkey string Main modifier key (e.g., "Mod4" for Super)
----@field terminal string Terminal emulator command
----@field tags string[] List of workspace tag names
----@field layout_symbols LayoutSymbol[] Custom layout symbols
----@field keybindings (KeyBinding|KeyChord)[] List of keybindings
----@field status_blocks StatusBlock[] Status bar configuration blocks
----@field scheme_normal ColorScheme Color scheme for normal tags
----@field scheme_occupied ColorScheme Color scheme for occupied tags
----@field scheme_selected ColorScheme Color scheme for selected tag
----@field autostart string[] Commands to run on startup
-
--- Available Actions:
--- "Spawn" - Launch a program (arg: string or string[])
--- "KillClient" - Close focused window
--- "FocusStack" - Focus next/prev in stack (arg: 1 or -1)
--- "FocusDirection" - Focus by direction (arg: 0=up, 1=down, 2=left, 3=right)
--- "SwapDirection" - Swap window by direction (arg: 0=up, 1=down, 2=left, 3=right)
--- "Quit" - Exit window manager
--- "Restart" - Restart and reload config
--- "Recompile" - Recompile and restart
--- "ViewTag" - Switch to tag (arg: tag index)
--- "MoveToTag" - Move window to tag (arg: tag index)
--- "ToggleGaps" - Toggle gaps on/off
--- "ToggleFullScreen" - Toggle fullscreen mode
--- "ToggleFloating" - Toggle floating mode
--- "ChangeLayout" - Switch to specific layout (arg: layout name)
--- "CycleLayout" - Cycle through layouts
--- "FocusMonitor" - Focus monitor (arg: 1 or -1)
--- "SmartMoveWin" - Smart window movement
--- "ExchangeClient" - Exchange window positions
-
--- Available Modifiers:
--- "Mod" - Replaced with configured modkey
--- "Mod1" - Alt key
--- "Mod4" - Super/Windows key
--- "Shift" - Shift key
--- "Control" - Control key
--- "Mod2", "Mod3", "Mod5" - Additional modifiers
-
--- Define variables for easy customization
local terminal = "st"
-local modkey = "Mod1" -- Alt key for Xephyr testing
-local secondary_modkey = "Control"
+local modkey = "Mod4"
--- Color palette (Tokyo Night theme)
+-- Color palette
local colors = {
- blue = 0x6dade3,
- grey = 0xbbbbbb,
- green = 0x9ece6a,
- red = 0xf7768e,
- cyan = 0x0db9d7,
- purple = 0xad8ee6,
- lavender = 0xa9b1d6,
- bg = 0x1a1b26,
- fg = 0xbbbbbb,
- light_blue = 0x7aa2f7,
+ lavender = "#a9b1d6",
+ light_blue = "#7aa2f7",
+ grey = "#bbbbbb",
+ purple = "#ad8ee6",
+ cyan = "#0db9d7",
+ bg = "#1a1b26",
+ green = "#9ece6a",
+ red = "#f7768e",
+ fg = "#bbbbbb",
+ blue = "#6dade3",
}
-- Main configuration table
----@type Config
-config = {
+return {
-- Appearance
border_width = 2,
border_focused = colors.blue,
border_unfocused = colors.grey,
- font = "monospace:style=Bold:size=10",
+ font = "JetBrainsMono Nerd Font:style=Bold:size=12",
-- Window gaps
gaps_enabled = true,
@@ -115,92 +35,91 @@ config = {
gap_outer_vertical = 5,
-- Basics
- modkey = modkey,
- terminal = terminal,
+ modkey = "Mod1",
+ terminal = "st",
-- Workspace tags
- tags = {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
+ tags = { "1", "2", "3", "4", "5", "6", "7", "8", "9" },
-- Layout symbol overrides
layout_symbols = {
- {name = "tiling", symbol = "[T]"},
- {name = "normie", symbol = "[F]"},
+ { name = "tiling", symbol = "[T]" },
+ { name = "normie", symbol = "[F]" },
},
-- Keybindings
- -- Using Mod1 (Alt) for Xephyr testing so it doesn't conflict with host WM
keybindings = {
- -- Basic window management
- {modifiers = {"Mod"}, key = "Return", action = "Spawn", arg = terminal},
- {modifiers = {"Mod"}, key = "D", action = "Spawn", arg = {"sh", "-c", "dmenu_run -l 10"}},
- {modifiers = {"Mod"}, key = "S", action = "Spawn", arg = {"sh", "-c", "maim -s | xclip -selection clipboard -t image/png"}},
- {modifiers = {"Mod"}, key = "Q", action = "KillClient"},
- {modifiers = {"Mod", "Shift"}, key = "F", action = "ToggleFullScreen"},
- {modifiers = {"Mod", "Shift"}, key = "Space", action = "ToggleFloating"},
-
- -- Layout management
- {modifiers = {"Mod"}, key = "F", action = "ChangeLayout", arg = "normie"},
- {modifiers = {"Mod"}, key = "C", action = "ChangeLayout", arg = "tiling"},
- {modifiers = {secondary_modkey}, key = "N", action = "CycleLayout"},
- {modifiers = {"Mod"}, key = "A", action = "ToggleGaps"},
-
- -- WM control
- {modifiers = {"Mod", "Shift"}, key = "Q", action = "Quit"},
- {modifiers = {"Mod", "Shift"}, key = "R", action = "Restart"},
-
- -- Focus movement (vim-style hjkl)
- {modifiers = {"Mod"}, key = "H", action = "FocusDirection", arg = 2}, -- left
- {modifiers = {"Mod"}, key = "J", action = "FocusDirection", arg = 1}, -- down
- {modifiers = {"Mod"}, key = "K", action = "FocusDirection", arg = 0}, -- up
- {modifiers = {"Mod"}, key = "L", action = "FocusDirection", arg = 3}, -- right
-
- -- Window swapping (vim-style hjkl)
- {modifiers = {"Mod", "Shift"}, key = "H", action = "SwapDirection", arg = 2}, -- left
- {modifiers = {"Mod", "Shift"}, key = "J", action = "SwapDirection", arg = 1}, -- down
- {modifiers = {"Mod", "Shift"}, key = "K", action = "SwapDirection", arg = 0}, -- up
- {modifiers = {"Mod", "Shift"}, key = "L", action = "SwapDirection", arg = 3}, -- right
-
- -- Monitor focus
- {modifiers = {"Mod"}, key = "Comma", action = "FocusMonitor", arg = -1},
- {modifiers = {"Mod"}, key = "Period", action = "FocusMonitor", arg = 1},
-
- -- View tags
- {modifiers = {"Mod"}, key = "1", action = "ViewTag", arg = 0},
- {modifiers = {"Mod"}, key = "2", action = "ViewTag", arg = 1},
- {modifiers = {"Mod"}, key = "3", action = "ViewTag", arg = 2},
- {modifiers = {"Mod"}, key = "4", action = "ViewTag", arg = 3},
- {modifiers = {"Mod"}, key = "5", action = "ViewTag", arg = 4},
- {modifiers = {"Mod"}, key = "6", action = "ViewTag", arg = 5},
- {modifiers = {"Mod"}, key = "7", action = "ViewTag", arg = 6},
- {modifiers = {"Mod"}, key = "8", action = "ViewTag", arg = 7},
- {modifiers = {"Mod"}, key = "9", action = "ViewTag", arg = 8},
-
- -- Move window to tag
- {modifiers = {"Mod", "Shift"}, key = "1", action = "MoveToTag", arg = 0},
- {modifiers = {"Mod", "Shift"}, key = "2", action = "MoveToTag", arg = 1},
- {modifiers = {"Mod", "Shift"}, key = "3", action = "MoveToTag", arg = 2},
- {modifiers = {"Mod", "Shift"}, key = "4", action = "MoveToTag", arg = 3},
- {modifiers = {"Mod", "Shift"}, key = "5", action = "MoveToTag", arg = 4},
- {modifiers = {"Mod", "Shift"}, key = "6", action = "MoveToTag", arg = 5},
- {modifiers = {"Mod", "Shift"}, key = "7", action = "MoveToTag", arg = 6},
- {modifiers = {"Mod", "Shift"}, key = "8", action = "MoveToTag", arg = 7},
- {modifiers = {"Mod", "Shift"}, key = "9", action = "MoveToTag", arg = 8},
-
- -- Example keychord: Alt+Space, then T to spawn terminal
{
keys = {
- {modifiers = {"Mod1"}, key = "Space"},
- {modifiers = {}, key = "T"}
+ { modifiers = { "Mod1" }, key = "Space" },
+ { modifiers = { }, key = "T" },
},
action = "Spawn",
- arg = terminal
+ arg = "st"
},
+ { modifiers = { "Mod1" }, key = "Return", action = "Spawn", arg = "st" },
+ { modifiers = { "Mod1" }, key = "D", action = "Spawn", arg = { "sh", "-c", "dmenu_run -l 10" } },
+ { modifiers = { "Mod1" }, key = "S", action = "Spawn", arg = { "sh", "-c", "maim -s | xclip -selection clipboard -t image/png" } },
+ { modifiers = { "Mod1" }, key = "Q", action = "KillClient" },
+ { modifiers = { "Mod1", "Shift" }, key = "F", action = "ToggleFullScreen" },
+ { modifiers = { "Mod1", "Shift" }, key = "Space", action = "ToggleFloating" },
+ { modifiers = { "Mod1" }, key = "F", action = "ChangeLayout", arg = "normie" },
+ { modifiers = { "Mod1" }, key = "C", action = "ChangeLayout", arg = "tiling" },
+ { modifiers = { "Mod1" }, key = "N", action = "CycleLayout" },
+ { modifiers = { "Mod1" }, key = "A", action = "ToggleGaps" },
+ { modifiers = { "Mod1", "Shift" }, key = "Q", action = "Quit" },
+ { modifiers = { "Mod1", "Shift" }, key = "R", action = "Restart" },
+ { modifiers = { "Mod1" }, key = "H", action = "FocusDirection", arg = 2 },
+ { modifiers = { "Mod1" }, key = "J", action = "FocusDirection", arg = 1 },
+ { modifiers = { "Mod1" }, key = "K", action = "FocusDirection", arg = 0 },
+ { modifiers = { "Mod1" }, key = "L", action = "FocusDirection", arg = 3 },
+ { modifiers = { "Mod1", "Shift" }, key = "H", action = "SwapDirection", arg = 2 },
+ { modifiers = { "Mod1", "Shift" }, key = "J", action = "SwapDirection", arg = 1 },
+ { modifiers = { "Mod1", "Shift" }, key = "K", action = "SwapDirection", arg = 0 },
+ { modifiers = { "Mod1", "Shift" }, key = "L", action = "SwapDirection", arg = 3 },
+ { modifiers = { "Mod1" }, key = "1", action = "ViewTag", arg = 0 },
+ { modifiers = { "Mod1" }, key = "2", action = "ViewTag", arg = 1 },
+ { modifiers = { "Mod1" }, key = "3", action = "ViewTag", arg = 2 },
+ { modifiers = { "Mod1" }, key = "4", action = "ViewTag", arg = 3 },
+ { modifiers = { "Mod1" }, key = "5", action = "ViewTag", arg = 4 },
+ { modifiers = { "Mod1" }, key = "6", action = "ViewTag", arg = 5 },
+ { modifiers = { "Mod1" }, key = "7", action = "ViewTag", arg = 6 },
+ { modifiers = { "Mod1" }, key = "8", action = "ViewTag", arg = 7 },
+ { modifiers = { "Mod1" }, key = "9", action = "ViewTag", arg = 8 },
+ { modifiers = { "Mod1", "Shift" }, key = "1", action = "MoveToTag", arg = 0 },
+ { modifiers = { "Mod1", "Shift" }, key = "2", action = "MoveToTag", arg = 1 },
+ { modifiers = { "Mod1", "Shift" }, key = "3", action = "MoveToTag", arg = 2 },
+ { modifiers = { "Mod1", "Shift" }, key = "4", action = "MoveToTag", arg = 3 },
+ { modifiers = { "Mod1", "Shift" }, key = "5", action = "MoveToTag", arg = 4 },
+ { modifiers = { "Mod1", "Shift" }, key = "6", action = "MoveToTag", arg = 5 },
+ { modifiers = { "Mod1", "Shift" }, key = "7", action = "MoveToTag", arg = 6 },
+ { modifiers = { "Mod1", "Shift" }, key = "8", action = "MoveToTag", arg = 7 },
+ { modifiers = { "Mod1", "Shift" }, key = "9", action = "MoveToTag", arg = 8 },
},
-- Status bar blocks
status_blocks = {
{
- format = "Ram: {used}/{total} GB",
+ format = "",
+ command = "Battery",
+ battery_formats = {
+ charging = " Bat: {}%",
+ discharging = " Bat:{}%",
+ full = " Bat: {}%"
+ },
+ interval_secs = 30,
+ color = colors.green,
+ underline = true
+ },
+ {
+ format = " │ ",
+ command = "Static",
+ interval_secs = 999999999,
+ color = colors.lavender,
+ underline = false
+ },
+ {
+ format = " {used}/{total} GB",
command = "Ram",
interval_secs = 5,
color = colors.light_blue,
@@ -209,12 +128,12 @@ config = {
{
format = " │ ",
command = "Static",
- interval_secs = 999999999, -- Very large number for static blocks
+ interval_secs = 999999999,
color = colors.lavender,
underline = false
},
{
- format = "Kernel: {}",
+ format = " {}",
command = "Shell",
command_arg = "uname -r",
interval_secs = 999999999,
@@ -229,7 +148,7 @@ config = {
underline = false
},
{
- format = "{}",
+ format = " {}",
command = "DateTime",
command_arg = "%a, %b %d - %-I:%M %P",
interval_secs = 1,
@@ -242,7 +161,7 @@ config = {
scheme_normal = {
foreground = colors.fg,
background = colors.bg,
- underline = 0x444444
+ underline = "#444444"
},
scheme_occupied = {
foreground = colors.cyan,
diff --git a/src/bin/main.rs b/src/bin/main.rs
index af27e0b..f01c378 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -19,6 +19,11 @@ fn main() -> Result<()> {
init_config()?;
return Ok(());
}
+ Some("--migrate") => {
+ let path = args.get(2).map(PathBuf::from);
+ migrate_config(path)?;
+ return Ok(());
+ }
Some("--config") => {
if let Some(path) = args.get(2) {
custom_config_path = Some(PathBuf::from(path));
@@ -106,12 +111,50 @@ fn get_config_path() -> PathBuf {
.join("oxwm")
}
+fn migrate_config(custom_path: Option<PathBuf>) -> Result<()> {
+ let ron_path = if let Some(path) = custom_path {
+ path
+ } else {
+ // Default to ~/.config/oxwm/config.ron
+ get_config_path().join("config.ron")
+ };
+
+ if !ron_path.exists() {
+ eprintln!("Error: RON config file not found at {:?}", ron_path);
+ eprintln!("Please specify a path: oxwm --migrate <path/to/config.ron>");
+ std::process::exit(1);
+ }
+
+ println!("Migrating RON config to Lua...");
+ println!(" Reading: {:?}", ron_path);
+
+ let ron_content = std::fs::read_to_string(&ron_path)
+ .with_context(|| format!("Failed to read RON config from {:?}", ron_path))?;
+
+ let lua_content = oxwm::config::migrate::ron_to_lua(&ron_content)
+ .map_err(|e| anyhow::anyhow!("Migration failed: {}", e))?;
+
+ // Determine output path (same directory, .lua extension)
+ let lua_path = ron_path.with_extension("lua");
+
+ std::fs::write(&lua_path, lua_content)
+ .with_context(|| format!("Failed to write Lua config to {:?}", lua_path))?;
+
+ println!("✓ Migration complete!");
+ println!(" Output: {:?}", lua_path);
+ println!("\nYour old config.ron is still intact.");
+ println!("Review the new config.lua and then you can delete config.ron if everything looks good.");
+
+ Ok(())
+}
+
fn print_help() {
println!("OXWM - A dynamic window manager written in Rust\n");
println!("USAGE:");
println!(" oxwm [OPTIONS]\n");
println!("OPTIONS:");
println!(" --init Create default config in ~/.config/oxwm/config.lua");
+ println!(" --migrate [PATH] Convert RON config to Lua (default: ~/.config/oxwm/config.ron)");
println!(" --config <PATH> Use custom config file (.lua or .ron)");
println!(" --version Print version information");
println!(" --help Print this help message\n");
@@ -119,6 +162,9 @@ fn print_help() {
println!(" Location: ~/.config/oxwm/config.lua (or config.ron for legacy)");
println!(" Edit the config file and use Mod+Shift+R to reload");
println!(" No compilation needed - instant hot-reload!\n");
+ println!("MIGRATION:");
+ println!(" To migrate from RON to Lua: oxwm --migrate");
+ println!(" Or specify a custom path: oxwm --migrate /path/to/config.ron\n");
println!("FIRST RUN:");
println!(" Run 'oxwm --init' to create a config file");
println!(" Or just start oxwm and it will create one automatically\n");
diff --git a/src/config/lua.rs b/src/config/lua.rs
index e1a9930..6dfde14 100644
--- a/src/config/lua.rs
+++ b/src/config/lua.rs
@@ -10,18 +10,9 @@ use x11rb::protocol::xproto::KeyButMask;
pub fn parse_lua_config(input: &str) -> Result<crate::Config, ConfigError> {
let lua = Lua::new();
- // Execute the config file
- lua.load(input)
- .exec()
+ let config: Table = lua.load(input)
+ .eval()
.map_err(|e| ConfigError::LuaError(format!("Failed to execute Lua config: {}", e)))?;
-
- // Get the config table from global scope
- let globals = lua.globals();
- let config: Table = globals
- .get("config")
- .map_err(|e| ConfigError::LuaError(format!("Config table not found: {}", e)))?;
-
- // Parse each config section
let border_width: u32 = get_table_field(&config, "border_width")?;
let border_focused: u32 = parse_color(&config, "border_focused")?;
let border_unfocused: u32 = parse_color(&config, "border_unfocused")?;
@@ -527,7 +518,7 @@ mod tests {
#[test]
fn test_parse_minimal_lua_config() {
let config_str = r#"
-config = {
+return {
border_width = 2,
border_focused = 0x6dade3,
border_unfocused = 0xbbbbbb,
diff --git a/src/config/migrate.rs b/src/config/migrate.rs
new file mode 100644
index 0000000..441f9b7
--- /dev/null
+++ b/src/config/migrate.rs
@@ -0,0 +1,552 @@
+use std::collections::HashMap;
+
+pub fn ron_to_lua(ron_content: &str) -> Result<String, String> {
+ let mut lua_output = String::new();
+ let defines = extract_defines(ron_content);
+
+ lua_output.push_str("-- OXWM Configuration File (Lua)\n");
+ lua_output.push_str("-- Migrated from config.ron\n");
+ lua_output.push_str("-- Edit this file and reload with Mod+Shift+R (no compilation needed!)\n\n");
+
+ let terminal = resolve_value(&defines.get("$terminal").cloned().unwrap_or_else(|| "\"st\"".to_string()), &defines);
+ let modkey = resolve_value(&defines.get("$modkey").cloned().unwrap_or_else(|| "Mod4".to_string()), &defines);
+ let secondary_modkey = defines.get("$secondary_modkey").map(|v| resolve_value(v, &defines));
+
+ lua_output.push_str(&format!("local terminal = {}\n", terminal));
+ lua_output.push_str(&format!("local modkey = \"{}\"\n", modkey.trim_matches('"')));
+ if let Some(sec_mod) = secondary_modkey {
+ lua_output.push_str(&format!("local secondary_modkey = \"{}\"\n", sec_mod.trim_matches('"')));
+ }
+ lua_output.push_str("\n");
+
+ lua_output.push_str("-- Color palette\n");
+ lua_output.push_str("local colors = {\n");
+ for (key, value) in &defines {
+ if key.starts_with("$color_") {
+ let color_name = &key[7..];
+ let color_value = if value.starts_with("0x") {
+ format!("\"#{}\"", &value[2..])
+ } else {
+ value.clone()
+ };
+ lua_output.push_str(&format!(" {} = {},\n", color_name, color_value));
+ }
+ }
+ lua_output.push_str("}\n\n");
+
+ lua_output.push_str("-- Main configuration table\n");
+ lua_output.push_str("return {\n");
+
+ if let Some(config_start) = ron_content.find('(') {
+ let config_content = &ron_content[config_start + 1..];
+
+ lua_output.push_str(" -- Appearance\n");
+ if let Some(val) = extract_field(config_content, "border_width") {
+ lua_output.push_str(&format!(" border_width = {},\n", val));
+ }
+ if let Some(val) = extract_field(config_content, "border_focused") {
+ lua_output.push_str(&format!(" border_focused = {},\n", resolve_color_value(&val, &defines)));
+ }
+ if let Some(val) = extract_field(config_content, "border_unfocused") {
+ lua_output.push_str(&format!(" border_unfocused = {},\n", resolve_color_value(&val, &defines)));
+ }
+ if let Some(val) = extract_field(config_content, "font") {
+ lua_output.push_str(&format!(" font = {},\n", val));
+ }
+
+ lua_output.push_str("\n -- Window gaps\n");
+ if let Some(val) = extract_field(config_content, "gaps_enabled") {
+ lua_output.push_str(&format!(" gaps_enabled = {},\n", val));
+ }
+ if let Some(val) = extract_field(config_content, "gap_inner_horizontal") {
+ lua_output.push_str(&format!(" gap_inner_horizontal = {},\n", val));
+ }
+ if let Some(val) = extract_field(config_content, "gap_inner_vertical") {
+ lua_output.push_str(&format!(" gap_inner_vertical = {},\n", val));
+ }
+ if let Some(val) = extract_field(config_content, "gap_outer_horizontal") {
+ lua_output.push_str(&format!(" gap_outer_horizontal = {},\n", val));
+ }
+ if let Some(val) = extract_field(config_content, "gap_outer_vertical") {
+ lua_output.push_str(&format!(" gap_outer_vertical = {},\n", val));
+ }
+
+ lua_output.push_str("\n -- Basics\n");
+ if let Some(val) = extract_field(config_content, "modkey") {
+ let resolved = resolve_value(&val, &defines).trim_matches('"').to_string();
+ if resolved == "modkey" {
+ lua_output.push_str(" modkey = modkey,\n");
+ } else {
+ lua_output.push_str(&format!(" modkey = \"{}\",\n", resolved));
+ }
+ }
+ if let Some(val) = extract_field(config_content, "terminal") {
+ let resolved = resolve_value(&val, &defines);
+ if resolved == "terminal" {
+ lua_output.push_str(" terminal = terminal,\n");
+ } else {
+ lua_output.push_str(&format!(" terminal = {},\n", resolved));
+ }
+ }
+
+ lua_output.push_str("\n -- Workspace tags\n");
+ if let Some(val) = extract_field(config_content, "tags") {
+ lua_output.push_str(&format!(" tags = {},\n", convert_array_to_lua(&val)));
+ }
+
+ lua_output.push_str("\n -- Layout symbol overrides\n");
+ if let Some(val) = extract_field(config_content, "layout_symbols") {
+ lua_output.push_str(" layout_symbols = ");
+ lua_output.push_str(&convert_layout_symbols(&val));
+ lua_output.push_str(",\n");
+ }
+
+ lua_output.push_str("\n -- Keybindings\n");
+ if let Some(val) = extract_field(config_content, "keybindings") {
+ lua_output.push_str(" keybindings = ");
+ lua_output.push_str(&convert_keybindings(&val, &defines));
+ lua_output.push_str(",\n");
+ }
+
+ lua_output.push_str("\n -- Status bar blocks\n");
+ if let Some(val) = extract_field(config_content, "status_blocks") {
+ lua_output.push_str(" status_blocks = ");
+ lua_output.push_str(&convert_status_blocks(&val, &defines));
+ lua_output.push_str(",\n");
+ }
+
+ lua_output.push_str("\n -- Color schemes for bar\n");
+ if let Some(val) = extract_field(config_content, "scheme_normal") {
+ lua_output.push_str(" scheme_normal = ");
+ lua_output.push_str(&convert_color_scheme(&val, &defines));
+ lua_output.push_str(",\n");
+ }
+ if let Some(val) = extract_field(config_content, "scheme_occupied") {
+ lua_output.push_str(" scheme_occupied = ");
+ lua_output.push_str(&convert_color_scheme(&val, &defines));
+ lua_output.push_str(",\n");
+ }
+ if let Some(val) = extract_field(config_content, "scheme_selected") {
+ lua_output.push_str(" scheme_selected = ");
+ lua_output.push_str(&convert_color_scheme(&val, &defines));
+ lua_output.push_str(",\n");
+ }
+
+ lua_output.push_str("\n -- Autostart commands\n");
+ if let Some(val) = extract_field(config_content, "autostart") {
+ let converted = convert_array_to_lua(&val);
+ lua_output.push_str(" autostart = ");
+ lua_output.push_str(&converted);
+ lua_output.push_str(",\n");
+ } else {
+ lua_output.push_str(" autostart = {},\n");
+ }
+ }
+
+ lua_output.push_str("}\n");
+
+ Ok(lua_output)
+}
+
+fn extract_defines(content: &str) -> HashMap<String, String> {
+ let mut defines = HashMap::new();
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if trimmed.starts_with("#DEFINE") {
+ if let Some(rest) = trimmed.strip_prefix("#DEFINE") {
+ if let Some(eq_pos) = rest.find('=') {
+ let var_name = rest[..eq_pos].trim().to_string();
+ let value = rest[eq_pos + 1..].trim().trim_end_matches(',').to_string();
+ defines.insert(var_name, value);
+ }
+ }
+ }
+ }
+ defines
+}
+
+fn resolve_value(value: &str, defines: &HashMap<String, String>) -> String {
+ if let Some(resolved) = defines.get(value) {
+ resolved.clone()
+ } else {
+ value.to_string()
+ }
+}
+
+fn resolve_color_value(value: &str, defines: &HashMap<String, String>) -> String {
+ let resolved = resolve_value(value, defines);
+ if resolved.starts_with("$color_") {
+ format!("colors.{}", &resolved[7..])
+ } else if value.starts_with("$color_") {
+ format!("colors.{}", &value[7..])
+ } else if resolved.starts_with("0x") {
+ format!("\"#{}\"", &resolved[2..])
+ } else {
+ resolved
+ }
+}
+
+fn extract_field(content: &str, field_name: &str) -> Option<String> {
+ let pattern = format!("{}:", field_name);
+ let cleaned_content = remove_comments(content);
+
+ if let Some(start) = cleaned_content.find(&pattern) {
+ let after_colon = &cleaned_content[start + pattern.len()..];
+ let value_start = after_colon.trim_start();
+
+ if value_start.starts_with('[') {
+ extract_bracketed(value_start, '[', ']')
+ } else if value_start.starts_with('(') {
+ extract_bracketed(value_start, '(', ')')
+ } else if value_start.starts_with('"') {
+ if let Some(end) = value_start[1..].find('"') {
+ Some(value_start[..end + 2].to_string())
+ } else {
+ None
+ }
+ } else {
+ let end = value_start
+ .find(|c: char| c == ',' || c == '\n' || c == ')')
+ .unwrap_or(value_start.len());
+ Some(value_start[..end].trim().to_string())
+ }
+ } else {
+ None
+ }
+}
+
+fn extract_bracketed(s: &str, open: char, close: char) -> Option<String> {
+ if !s.starts_with(open) {
+ return None;
+ }
+ let mut depth = 0;
+ let mut end = 0;
+ for (i, c) in s.char_indices() {
+ if c == open {
+ depth += 1;
+ } else if c == close {
+ depth -= 1;
+ if depth == 0 {
+ end = i + 1;
+ break;
+ }
+ }
+ }
+ if end > 0 {
+ Some(s[..end].to_string())
+ } else {
+ None
+ }
+}
+
+fn convert_array_to_lua(ron_array: &str) -> String {
+ let inner = ron_array.trim_start_matches('[').trim_end_matches(']');
+ let items: Vec<&str> = inner.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
+ format!("{{ {} }}", items.join(", "))
+}
+
+fn convert_layout_symbols(ron_array: &str) -> String {
+ let mut result = String::from("{\n");
+ let inner = ron_array.trim_start_matches('[').trim_end_matches(']');
+
+ let items = extract_all_bracketed(inner, '(', ')');
+ for item in items {
+ let item_inner = item.trim_start_matches('(').trim_end_matches(')');
+ if let (Some(name), Some(symbol)) = (extract_quoted_value(item_inner, "name"), extract_quoted_value(item_inner, "symbol")) {
+ result.push_str(&format!(" {{ name = \"{}\", symbol = \"{}\" }},\n", name, symbol));
+ }
+ }
+
+ result.push_str(" }");
+ result
+}
+
+fn convert_keybindings(ron_array: &str, defines: &HashMap<String, String>) -> String {
+ let mut result = String::from("{\n");
+ let inner = ron_array.trim_start_matches('[').trim_end_matches(']');
+
+ let items = extract_all_bracketed(inner, '(', ')');
+ for item in items {
+ let binding = convert_keybinding(&item, defines);
+ result.push_str(&binding);
+ result.push_str(",\n");
+ }
+
+ result.push_str(" }");
+ result
+}
+
+fn convert_keybinding(ron_binding: &str, defines: &HashMap<String, String>) -> String {
+ let inner = ron_binding.trim_start_matches('(').trim_end_matches(')');
+
+ if inner.contains("keys:") {
+ convert_keychord(inner, defines)
+ } else {
+ convert_single_key(inner, defines)
+ }
+}
+
+fn convert_keychord(inner: &str, defines: &HashMap<String, String>) -> String {
+ let mut result = String::from(" {\n keys = {\n");
+
+ if let Some(keys_str) = extract_field(inner, "keys") {
+ let keys = extract_all_bracketed(&keys_str, '(', ')');
+ for key in keys {
+ let key_inner = key.trim_start_matches('(').trim_end_matches(')');
+ let mods = extract_modifiers(key_inner, defines);
+ let key_name = extract_key(key_inner);
+ result.push_str(&format!(" {{ modifiers = {{ {} }}, key = \"{}\" }},\n", mods, key_name));
+ }
+ }
+
+ result.push_str(" },\n");
+
+ if let Some(action) = extract_identifier(inner, "action") {
+ result.push_str(&format!(" action = \"{}\",\n", action));
+ }
+
+ if let Some(arg) = extract_arg(inner, defines) {
+ result.push_str(&format!(" arg = {}\n", arg));
+ }
+
+ result.push_str(" }");
+ result
+}
+
+fn convert_single_key(inner: &str, defines: &HashMap<String, String>) -> String {
+ let mods = extract_modifiers(inner, defines);
+ let key = extract_key(inner);
+ let action = extract_identifier(inner, "action").unwrap_or_default();
+
+ let mut result = format!(" {{ modifiers = {{ {} }}, key = \"{}\", action = \"{}\"", mods, key, action);
+
+ if let Some(arg) = extract_arg(inner, defines) {
+ result.push_str(&format!(", arg = {}", arg));
+ }
+
+ result.push_str(" }");
+ result
+}
+
+fn extract_modifiers(content: &str, defines: &HashMap<String, String>) -> String {
+ if let Some(mods_str) = extract_field(content, "modifiers") {
+ let inner = mods_str.trim_start_matches('[').trim_end_matches(']').trim();
+ if inner.is_empty() {
+ return String::new();
+ }
+ let mods: Vec<String> = inner
+ .split(',')
+ .map(|s| {
+ let trimmed = s.trim();
+ if !trimmed.is_empty() {
+ let resolved = resolve_value(trimmed, defines);
+ format!("\"{}\"", resolved)
+ } else {
+ String::new()
+ }
+ })
+ .filter(|s| !s.is_empty())
+ .collect();
+ mods.join(", ")
+ } else {
+ String::new()
+ }
+}
+
+fn extract_key(content: &str) -> String {
+ if let Some(key_str) = extract_identifier(content, "key") {
+ if key_str.starts_with("Key") && key_str.len() == 4 {
+ if let Some(digit) = key_str.chars().nth(3) {
+ if digit.is_ascii_digit() {
+ return digit.to_string();
+ }
+ }
+ }
+ key_str
+ } else {
+ String::from("Return")
+ }
+}
+
+fn extract_identifier(content: &str, field_name: &str) -> Option<String> {
+ let pattern = format!("{}:", field_name);
+ if let Some(start) = content.find(&pattern) {
+ let after_colon = &content[start + pattern.len()..];
+ let value_start = after_colon.trim_start();
+ let end = value_start
+ .find(|c: char| c == ',' || c == ')' || c == '\n')
+ .unwrap_or(value_start.len());
+ Some(value_start[..end].trim().to_string())
+ } else {
+ None
+ }
+}
+
+fn extract_arg(content: &str, defines: &HashMap<String, String>) -> Option<String> {
+ if let Some(arg_str) = extract_field(content, "arg") {
+ let resolved = resolve_value(&arg_str, defines);
+ if resolved.starts_with('[') {
+ Some(convert_array_to_lua(&resolved))
+ } else if resolved.starts_with('"') || resolved.parse::<i32>().is_ok() || resolved.starts_with("0x") {
+ Some(resolved)
+ } else {
+ Some(format!("\"{}\"", resolved))
+ }
+ } else {
+ None
+ }
+}
+
+fn convert_status_blocks(ron_array: &str, defines: &HashMap<String, String>) -> String {
+ let mut result = String::from("{\n");
+ let inner = ron_array.trim_start_matches('[').trim_end_matches(']');
+
+ let items = extract_all_bracketed(inner, '(', ')');
+ for item in items {
+ let block = convert_status_block(&item, defines);
+ if !block.trim().ends_with("{\n }") {
+ result.push_str(&block);
+ result.push_str(",\n");
+ }
+ }
+
+ result.push_str(" }");
+ result
+}
+
+fn convert_status_block(ron_block: &str, defines: &HashMap<String, String>) -> String {
+ let mut result = String::from(" {\n");
+ let inner = ron_block.trim_start_matches('(').trim_end_matches(')');
+
+ if let Some(format) = extract_field(inner, "format") {
+ result.push_str(&format!(" format = {},\n", format));
+ }
+ if let Some(command) = extract_field(inner, "command") {
+ result.push_str(&format!(" command = {},\n", command));
+ }
+ if let Some(command_arg) = extract_field(inner, "command_arg") {
+ result.push_str(&format!(" command_arg = {},\n", command_arg));
+ }
+ if inner.contains("battery_formats:") {
+ if let Some(battery_str) = extract_field(inner, "battery_formats") {
+ result.push_str(" battery_formats = {\n");
+ let battery_inner = battery_str.trim_start_matches('(').trim_end_matches(')');
+ if let Some(charging) = extract_quoted_value(battery_inner, "charging") {
+ result.push_str(&format!(" charging = \"{}\",\n", charging));
+ }
+ if let Some(discharging) = extract_quoted_value(battery_inner, "discharging") {
+ result.push_str(&format!(" discharging = \"{}\",\n", discharging));
+ }
+ if let Some(full) = extract_quoted_value(battery_inner, "full") {
+ result.push_str(&format!(" full = \"{}\"\n", full));
+ }
+ result.push_str(" },\n");
+ }
+ }
+ if let Some(interval) = extract_field(inner, "interval_secs") {
+ let interval_val = if interval.len() > 10 {
+ "999999999".to_string()
+ } else {
+ interval
+ };
+ result.push_str(&format!(" interval_secs = {},\n", interval_val));
+ }
+ if let Some(color) = extract_field(inner, "color") {
+ let resolved = resolve_color_value(&color, defines);
+ result.push_str(&format!(" color = {},\n", resolved));
+ }
+ if let Some(underline) = extract_field(inner, "underline") {
+ result.push_str(&format!(" underline = {}\n", underline));
+ }
+
+ result.push_str(" }");
+ result
+}
+
+fn convert_color_scheme(ron_scheme: &str, defines: &HashMap<String, String>) -> String {
+ let mut result = String::from("{\n");
+ let inner = ron_scheme.trim_start_matches('(').trim_end_matches(')');
+
+ if let Some(fg) = extract_field(inner, "foreground") {
+ let resolved = resolve_color_value(&fg, defines);
+ result.push_str(&format!(" foreground = {},\n", resolved));
+ }
+ if let Some(bg) = extract_field(inner, "background") {
+ let resolved = resolve_color_value(&bg, defines);
+ result.push_str(&format!(" background = {},\n", resolved));
+ }
+ if let Some(ul) = extract_field(inner, "underline") {
+ let resolved = resolve_color_value(&ul, defines);
+ result.push_str(&format!(" underline = {}\n", resolved));
+ }
+
+ result.push_str(" }");
+ result
+}
+
+fn extract_all_bracketed(s: &str, open: char, close: char) -> Vec<String> {
+ let mut results = Vec::new();
+ let mut depth = 0;
+ let mut start = None;
+
+ let cleaned = remove_comments(s);
+
+ for (i, c) in cleaned.char_indices() {
+ if c == open {
+ if depth == 0 {
+ start = Some(i);
+ }
+ depth += 1;
+ } else if c == close {
+ depth -= 1;
+ if depth == 0 {
+ if let Some(start_idx) = start {
+ results.push(cleaned[start_idx..=i].to_string());
+ start = None;
+ }
+ }
+ }
+ }
+
+ results
+}
+
+fn remove_comments(s: &str) -> String {
+ let mut result = String::new();
+ for line in s.lines() {
+ let mut in_string = false;
+ let mut comment_start = None;
+
+ for (i, c) in line.char_indices() {
+ if c == '"' && (i == 0 || line.chars().nth(i - 1) != Some('\\')) {
+ in_string = !in_string;
+ }
+ if !in_string && i + 1 < line.len() && &line[i..i + 2] == "//" {
+ comment_start = Some(i);
+ break;
+ }
+ }
+
+ if let Some(pos) = comment_start {
+ result.push_str(&line[..pos]);
+ } else {
+ result.push_str(line);
+ }
+ result.push('\n');
+ }
+ result
+}
+
+fn extract_quoted_value(content: &str, field_name: &str) -> Option<String> {
+ let pattern = format!("{}:", field_name);
+ if let Some(start) = content.find(&pattern) {
+ let after_colon = &content[start + pattern.len()..];
+ let trimmed = after_colon.trim_start();
+ if trimmed.starts_with('"') {
+ if let Some(end) = trimmed[1..].find('"') {
+ return Some(trimmed[1..end + 1].to_string());
+ }
+ }
+ }
+ None
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 02fbd8f..abdc0bf 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -1,4 +1,5 @@
mod lua;
+pub mod migrate;
use crate::bar::{BlockCommand, BlockConfig};
use crate::errors::ConfigError;
diff --git a/templates/config.lua b/templates/config.lua
index fd3eabb..577c911 100644
--- a/templates/config.lua
+++ b/templates/config.lua
@@ -1,106 +1,27 @@
-- OXWM Configuration File (Lua)
+-- Migrated from config.ron
-- Edit this file and reload with Mod+Shift+R (no compilation needed!)
----@class LayoutSymbol
----@field name string The internal layout name (e.g., "tiling", "normie")
----@field symbol string The display symbol for the layout (e.g., "[T]", "[F]")
-
----@class KeyBinding
----@field modifiers string[] List of modifiers: "Mod", "Mod1"-"Mod5", "Shift", "Control"
----@field key string The key name (e.g., "Return", "Q", "1", "Space")
----@field action string Action to perform (see below for list)
----@field arg? string|number|string[] Optional argument for the action
-
----@class KeyChord
----@field keys {modifiers: string[], key: string}[] Sequence of keys to press
----@field action string Action to perform when chord completes
----@field arg? string|number|string[] Optional argument for the action
-
----@class StatusBlock
----@field format string Display format with {} placeholders
----@field command string Command type: "Ram", "DateTime", "Shell", "Static", "Battery"
----@field command_arg? string Argument for command (shell command, date format, etc.)
----@field interval_secs number Update interval in seconds
----@field color number Color as hex number (e.g., 0xff0000 for red)
----@field underline boolean Whether to show underline
----@field battery_formats? {charging: string, discharging: string, full: string} Battery format strings
-
----@class ColorScheme
----@field foreground number Foreground color (hex)
----@field background number Background color (hex)
----@field underline number Underline color (hex)
-
----@class Config
----@field border_width number Width of window borders in pixels
----@field border_focused number Color for focused window border (hex)
----@field border_unfocused number Color for unfocused window border (hex)
----@field font string Font specification (e.g., "monospace:style=Bold:size=10")
----@field gaps_enabled boolean Whether gaps are enabled
----@field gap_inner_horizontal number Inner horizontal gap size
----@field gap_inner_vertical number Inner vertical gap size
----@field gap_outer_horizontal number Outer horizontal gap size
----@field gap_outer_vertical number Outer vertical gap size
----@field modkey string Main modifier key (e.g., "Mod4" for Super)
----@field terminal string Terminal emulator command
----@field tags string[] List of workspace tag names
----@field layout_symbols LayoutSymbol[] Custom layout symbols
----@field keybindings (KeyBinding|KeyChord)[] List of keybindings
----@field status_blocks StatusBlock[] Status bar configuration blocks
----@field scheme_normal ColorScheme Color scheme for normal tags
----@field scheme_occupied ColorScheme Color scheme for occupied tags
----@field scheme_selected ColorScheme Color scheme for selected tag
----@field autostart string[] Commands to run on startup
-
--- Available Actions:
--- "Spawn" - Launch a program (arg: string or string[])
--- "KillClient" - Close focused window
--- "FocusStack" - Focus next/prev in stack (arg: 1 or -1)
--- "FocusDirection" - Focus by direction (arg: 0=up, 1=down, 2=left, 3=right)
--- "SwapDirection" - Swap window by direction (arg: 0=up, 1=down, 2=left, 3=right)
--- "Quit" - Exit window manager
--- "Restart" - Restart and reload config
--- "Recompile" - Recompile and restart
--- "ViewTag" - Switch to tag (arg: tag index)
--- "MoveToTag" - Move window to tag (arg: tag index)
--- "ToggleGaps" - Toggle gaps on/off
--- "ToggleFullScreen" - Toggle fullscreen mode
--- "ToggleFloating" - Toggle floating mode
--- "ChangeLayout" - Switch to specific layout (arg: layout name)
--- "CycleLayout" - Cycle through layouts
--- "FocusMonitor" - Focus monitor (arg: 1 or -1)
--- "SmartMoveWin" - Smart window movement
--- "ExchangeClient" - Exchange window positions
-
--- Available Modifiers:
--- "Mod" - Replaced with configured modkey
--- "Mod1" - Alt key
--- "Mod4" - Super/Windows key
--- "Shift" - Shift key
--- "Control" - Control key
--- "Mod2", "Mod3", "Mod5" - Additional modifiers
-
--- Define variables for easy customization
local terminal = "st"
local modkey = "Mod4"
local secondary_modkey = "Mod1"
--- Color palette (Tokyo Night theme)
+-- Color palette
local colors = {
- blue = 0x6dade3,
- grey = 0xbbbbbb,
- green = 0x9ece6a,
- red = 0xf7768e,
- cyan = 0x0db9d7,
- purple = 0xad8ee6,
- lavender = 0xa9b1d6,
- bg = 0x1a1b26,
- fg = 0xbbbbbb,
- light_blue = 0x7aa2f7,
+ fg = "#bbbbbb",
+ red = "#f7768e",
+ bg = "#1a1b26",
+ cyan = "#0db9d7",
+ green = "#9ece6a",
+ lavender = "#a9b1d6",
+ light_blue = "#7aa2f7",
+ grey = "#bbbbbb",
+ blue = "#6dade3",
+ purple = "#ad8ee6",
}
-- Main configuration table
----@type Config
-config = {
+return {
-- Appearance
border_width = 2,
border_focused = colors.blue,
@@ -115,119 +36,71 @@ config = {
gap_outer_vertical = 5,
-- Basics
- modkey = modkey,
- terminal = terminal,
+ modkey = "Mod4",
+ terminal = "st",
-- Workspace tags
- tags = {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
+ tags = { "1", "2", "3", "4", "5", "6", "7", "8", "9" },
-- Layout symbol overrides
layout_symbols = {
- {name = "tiling", symbol = "[T]"},
- {name = "normie", symbol = "[F]"},
+ { name = "tiling", symbol = "[T]" },
+ { name = "normie", symbol = "[F]" },
},
-- Keybindings
- --
- -- Format:
- -- Single key: {modifiers = {...}, key = "X", action = "...", arg = ...}
- -- Keychord: {keys = {{modifiers = {...}, key = "X"}, {modifiers = {...}, key = "Y"}}, action = "...", arg = ...}
- --
- -- Available modifiers: "Mod", "Mod1", "Mod2", "Mod3", "Mod4", "Mod5", "Shift", "Control"
- -- "Mod" will be replaced with the configured modkey
- --
- -- Available actions:
- -- "Spawn", "KillClient", "FocusStack", "FocusDirection", "SwapDirection",
- -- "Quit", "Restart", "Recompile", "ViewTag", "ToggleGaps", "ToggleFullScreen",
- -- "ToggleFloating", "ChangeLayout", "CycleLayout", "MoveToTag", "FocusMonitor",
- -- "SmartMoveWin", "ExchangeClient"
- --
- -- arg can be: string, number, array of strings, or omitted (nil)
- --
- -- You can cancel any in-progress keychord by pressing Escape.
keybindings = {
- -- Basic window management
- {modifiers = {"Mod"}, key = "Return", action = "Spawn", arg = terminal},
- {modifiers = {"Mod"}, key = "D", action = "Spawn", arg = {"sh", "-c", "dmenu_run -l 10"}},
- {modifiers = {"Mod"}, key = "S", action = "Spawn", arg = {"sh", "-c", "maim -s | xclip -selection clipboard -t image/png"}},
- {modifiers = {"Mod"}, key = "Q", action = "KillClient"},
- {modifiers = {"Mod", "Shift"}, key = "F", action = "ToggleFullScreen"},
- {modifiers = {"Mod", "Shift"}, key = "Space", action = "ToggleFloating"},
-
- -- Layout management
- {modifiers = {"Mod"}, key = "F", action = "ChangeLayout", arg = "normie"},
- {modifiers = {"Mod"}, key = "C", action = "ChangeLayout", arg = "tiling"},
- {modifiers = {secondary_modkey}, key = "N", action = "CycleLayout"},
- {modifiers = {"Mod"}, key = "A", action = "ToggleGaps"},
-
- -- WM control
- {modifiers = {"Mod", "Shift"}, key = "Q", action = "Quit"},
- {modifiers = {"Mod", "Shift"}, key = "R", action = "Restart"},
-
- -- Focus movement (vim-style hjkl)
- {modifiers = {"Mod"}, key = "H", action = "FocusDirection", arg = 2}, -- left
- {modifiers = {"Mod"}, key = "J", action = "FocusDirection", arg = 1}, -- down
- {modifiers = {"Mod"}, key = "K", action = "FocusDirection", arg = 0}, -- up
- {modifiers = {"Mod"}, key = "L", action = "FocusDirection", arg = 3}, -- right
-
- -- Window swapping (vim-style hjkl)
- {modifiers = {"Mod", "Shift"}, key = "H", action = "SwapDirection", arg = 2}, -- left
- {modifiers = {"Mod", "Shift"}, key = "J", action = "SwapDirection", arg = 1}, -- down
- {modifiers = {"Mod", "Shift"}, key = "K", action = "SwapDirection", arg = 0}, -- up
- {modifiers = {"Mod", "Shift"}, key = "L", action = "SwapDirection", arg = 3}, -- right
-
- -- Monitor focus
- {modifiers = {"Mod"}, key = "Comma", action = "FocusMonitor", arg = -1},
- {modifiers = {"Mod"}, key = "Period", action = "FocusMonitor", arg = 1},
-
- -- View tags
- {modifiers = {"Mod"}, key = "1", action = "ViewTag", arg = 0},
- {modifiers = {"Mod"}, key = "2", action = "ViewTag", arg = 1},
- {modifiers = {"Mod"}, key = "3", action = "ViewTag", arg = 2},
- {modifiers = {"Mod"}, key = "4", action = "ViewTag", arg = 3},
- {modifiers = {"Mod"}, key = "5", action = "ViewTag", arg = 4},
- {modifiers = {"Mod"}, key = "6", action = "ViewTag", arg = 5},
- {modifiers = {"Mod"}, key = "7", action = "ViewTag", arg = 6},
- {modifiers = {"Mod"}, key = "8", action = "ViewTag", arg = 7},
- {modifiers = {"Mod"}, key = "9", action = "ViewTag", arg = 8},
-
- -- Move window to tag
- {modifiers = {"Mod", "Shift"}, key = "1", action = "MoveToTag", arg = 0},
- {modifiers = {"Mod", "Shift"}, key = "2", action = "MoveToTag", arg = 1},
- {modifiers = {"Mod", "Shift"}, key = "3", action = "MoveToTag", arg = 2},
- {modifiers = {"Mod", "Shift"}, key = "4", action = "MoveToTag", arg = 3},
- {modifiers = {"Mod", "Shift"}, key = "5", action = "MoveToTag", arg = 4},
- {modifiers = {"Mod", "Shift"}, key = "6", action = "MoveToTag", arg = 5},
- {modifiers = {"Mod", "Shift"}, key = "7", action = "MoveToTag", arg = 6},
- {modifiers = {"Mod", "Shift"}, key = "8", action = "MoveToTag", arg = 7},
- {modifiers = {"Mod", "Shift"}, key = "9", action = "MoveToTag", arg = 8},
-
- -- Example keychord: Mod+Space, then T to spawn terminal
+ { modifiers = { "Mod4" }, key = "Return", action = "Spawn", arg = "st" },
+ { modifiers = { "Mod4" }, key = "D", action = "Spawn", arg = { "sh", "-c", "dmenu_run -l 10" } },
+ { modifiers = { "Mod4" }, key = "S", action = "Spawn", arg = { "sh", "-c", "maim -s | xclip -selection clipboard -t image/png" } },
+ { modifiers = { "Mod4" }, key = "Q", action = "KillClient" },
+ { modifiers = { "Mod4", "Shift" }, key = "F", action = "ToggleFullScreen" },
+ { modifiers = { "Mod4", "Shift" }, key = "Space", action = "ToggleFloating" },
+ { modifiers = { "Mod4" }, key = "F", action = "ChangeLayout", arg = "normie" },
+ { modifiers = { "Mod4" }, key = "C", action = "ChangeLayout", arg = "tiling" },
+ { modifiers = { "Mod1" }, key = "N", action = "CycleLayout" },
+ { modifiers = { "Mod4" }, key = "A", action = "ToggleGaps" },
+ { modifiers = { "Mod4", "Shift" }, key = "Q", action = "Quit" },
+ { modifiers = { "Mod4", "Shift" }, key = "R", action = "Restart" },
+ { modifiers = { "Mod4" }, key = "H", action = "FocusDirection", arg = 2 },
+ { modifiers = { "Mod4" }, key = "J", action = "FocusDirection", arg = 1 },
+ { modifiers = { "Mod4" }, key = "K", action = "FocusDirection", arg = 0 },
+ { modifiers = { "Mod4" }, key = "L", action = "FocusDirection", arg = 3 },
+ { modifiers = { "Mod4" }, key = "Comma", action = "FocusMonitor", arg = -1 },
+ { modifiers = { "Mod4" }, key = "Period", action = "FocusMonitor", arg = 1 },
+ { modifiers = { "Mod4" }, key = "1", action = "ViewTag", arg = 0 },
+ { modifiers = { "Mod4" }, key = "2", action = "ViewTag", arg = 1 },
+ { modifiers = { "Mod4" }, key = "3", action = "ViewTag", arg = 2 },
+ { modifiers = { "Mod4" }, key = "4", action = "ViewTag", arg = 3 },
+ { modifiers = { "Mod4" }, key = "5", action = "ViewTag", arg = 4 },
+ { modifiers = { "Mod4" }, key = "6", action = "ViewTag", arg = 5 },
+ { modifiers = { "Mod4" }, key = "7", action = "ViewTag", arg = 6 },
+ { modifiers = { "Mod4" }, key = "8", action = "ViewTag", arg = 7 },
+ { modifiers = { "Mod4" }, key = "9", action = "ViewTag", arg = 8 },
+ { modifiers = { "Mod4", "Shift" }, key = "1", action = "MoveToTag", arg = 0 },
+ { modifiers = { "Mod4", "Shift" }, key = "2", action = "MoveToTag", arg = 1 },
+ { modifiers = { "Mod4", "Shift" }, key = "3", action = "MoveToTag", arg = 2 },
+ { modifiers = { "Mod4", "Shift" }, key = "4", action = "MoveToTag", arg = 3 },
+ { modifiers = { "Mod4", "Shift" }, key = "5", action = "MoveToTag", arg = 4 },
+ { modifiers = { "Mod4", "Shift" }, key = "6", action = "MoveToTag", arg = 5 },
+ { modifiers = { "Mod4", "Shift" }, key = "7", action = "MoveToTag", arg = 6 },
+ { modifiers = { "Mod4", "Shift" }, key = "8", action = "MoveToTag", arg = 7 },
+ { modifiers = { "Mod4", "Shift" }, key = "9", action = "MoveToTag", arg = 8 },
+ { modifiers = { "Mod4", "Shift" }, key = "H", action = "SwapDirection", arg = 2 },
+ { modifiers = { "Mod4", "Shift" }, key = "J", action = "SwapDirection", arg = 1 },
+ { modifiers = { "Mod4", "Shift" }, key = "K", action = "SwapDirection", arg = 0 },
+ { modifiers = { "Mod4", "Shift" }, key = "L", action = "SwapDirection", arg = 3 },
{
keys = {
- {modifiers = {"Mod4"}, key = "Space"},
- {modifiers = {}, key = "T"}
+ { modifiers = { "Mod4" }, key = "Space" },
+ { modifiers = { }, key = "T" },
},
action = "Spawn",
- arg = terminal
+ arg = "st"
},
},
-- Status bar blocks
- --
- -- Available commands:
- -- "Ram" - Shows RAM usage (no command_arg needed)
- -- "DateTime" - Shows date/time with strftime format in command_arg
- -- "Shell" - Runs shell command from command_arg
- -- "Static" - Static text from command_arg
- -- "Battery" - Shows battery status (requires battery_formats table)
- --
- -- Battery formats example:
- -- battery_formats = {
- -- charging = "⚡ {}%",
- -- discharging = "🔋 {}%",
- -- full = "✓ {}%"
- -- }
status_blocks = {
{
format = "Ram: {used}/{total} GB",
@@ -239,7 +112,7 @@ config = {
{
format = " │ ",
command = "Static",
- interval_secs = 999999999, -- Very large number for static blocks (never update)
+ interval_secs = 999999999,
color = colors.lavender,
underline = false
},
@@ -272,7 +145,7 @@ config = {
scheme_normal = {
foreground = colors.fg,
background = colors.bg,
- underline = 0x444444
+ underline = "#444444"
},
scheme_occupied = {
foreground = colors.cyan,
@@ -286,24 +159,5 @@ config = {
},
-- Autostart commands
- -- These are executed when the window manager starts
- -- Add your startup applications here
- autostart = {
- -- Uncomment and add your autostart commands:
- -- "picom -b",
- -- "nitrogen --restore &",
- -- "dunst &",
- },
+ autostart = { },
}
-
--- You can also add helper functions for more complex configs:
---
--- Example: Generate keybindings programmatically
--- for i = 1, 9 do
--- table.insert(config.keybindings, {
--- modifiers = {"Mod"},
--- key = tostring(i),
--- action = "ViewTag",
--- arg = i - 1
--- })
--- end