oxwm

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

Migration to lua.

Commit
faf793bab6e280d0ff8dedd95ce392d114666c03
Parent
842e22a
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-11-08 05:11:51

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