oxwm

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

Added lua parser.

Commit
842e22ae918e0b75a9a8e5f49578c738cd4d17c4
Parent
da67cfc
Author
tonybanters <tonybanters@gmail.com>
Date
2025-11-08 00:53:58

Diff

diff --git a/Cargo.lock b/Cargo.lock
index 35ac832..964d511 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -38,6 +38,16 @@ dependencies = [
  "serde_core",
 ]
 
+[[package]]
+name = "bstr"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
 [[package]]
 name = "bumpalo"
 version = "3.19.0"
@@ -100,6 +110,18 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "env_home"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
+
 [[package]]
 name = "errno"
 version = "0.3.14"
@@ -193,12 +215,74 @@ version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
 
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
 [[package]]
 name = "log"
 version = "0.4.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
 
+[[package]]
+name = "lua-src"
+version = "547.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "luajit-src"
+version = "210.5.12+a4f56a4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671"
+dependencies = [
+ "cc",
+ "which",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "mlua"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0"
+dependencies = [
+ "bstr",
+ "either",
+ "mlua-sys",
+ "num-traits",
+ "parking_lot",
+ "rustc-hash",
+ "rustversion",
+]
+
+[[package]]
+name = "mlua-sys"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "lua-src",
+ "luajit-src",
+ "pkg-config",
+]
+
 [[package]]
 name = "num-traits"
 version = "0.2.19"
@@ -227,12 +311,36 @@ dependencies = [
  "anyhow",
  "chrono",
  "dirs",
+ "mlua",
  "ron",
  "serde",
  "x11",
  "x11rb",
 ]
 
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
 [[package]]
 name = "pkg-config"
 version = "0.3.32"
@@ -257,6 +365,15 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "redox_users"
 version = "0.4.6"
@@ -280,6 +397,12 @@ dependencies = [
  "serde_derive",
 ]
 
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
 [[package]]
 name = "rustix"
 version = "1.1.2"
@@ -299,6 +422,12 @@ version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
 
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
 [[package]]
 name = "serde"
 version = "1.0.228"
@@ -335,6 +464,12 @@ version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
 [[package]]
 name = "syn"
 version = "2.0.107"
@@ -437,6 +572,18 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "which"
+version = "7.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
+dependencies = [
+ "either",
+ "env_home",
+ "rustix",
+ "winsafe",
+]
+
 [[package]]
 name = "windows-core"
 version = "0.62.2"
@@ -571,6 +718,12 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
+[[package]]
+name = "winsafe"
+version = "0.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
+
 [[package]]
 name = "x11"
 version = "2.21.0"
diff --git a/Cargo.toml b/Cargo.toml
index 813d17e..0917b3a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,3 +19,4 @@ chrono = "0.4"
 dirs = "5.0"
 serde = { version = "1.0", features = ["derive"] }
 ron = "0.8"
+mlua = { version = "0.10", features = ["lua54", "vendored"] }
diff --git a/UPDATES.md b/UPDATES.md
new file mode 100644
index 0000000..1309c84
--- /dev/null
+++ b/UPDATES.md
@@ -0,0 +1,407 @@
+# OXWM Recent Updates
+
+## Lua Configuration Support (Latest)
+
+OXWM now supports **Lua-based configuration** as an alternative to the compiled Rust configuration. This is a major update that makes the window manager much more user-friendly and easier to customize.
+
+### Key Features
+
+#### 1. **Dynamic Configuration - No Compilation Required**
+- Edit your config file and reload with `Mod+Shift+R` - changes apply instantly
+- No need to recompile the entire window manager for config changes
+- Configuration file located at `~/.config/oxwm/config.lua`
+
+#### 2. **Full Feature Parity**
+The Lua configuration supports all features previously available in Rust config:
+- Window appearance (borders, gaps, fonts)
+- Keybindings (single keys and keychords)
+- Layout management and custom symbols
+- Status bar configuration
+- Workspace tags
+- Autostart commands
+- Color schemes
+
+#### 3. **LSP Support & Autocomplete**
+Config files include comprehensive type annotations for Lua language servers:
+- Full autocomplete for all configuration options
+- Type checking to catch errors before runtime
+- Hover documentation for all fields
+- IntelliSense support in modern editors (VS Code, Neovim, etc.)
+
+### Configuration Structure
+
+```lua
+---@type Config
+config = {
+    -- Appearance
+    border_width = 2,
+    border_focused = 0x6dade3,
+    border_unfocused = 0xbbbbbb,
+    font = "monospace:style=Bold:size=10",
+
+    -- Window gaps
+    gaps_enabled = true,
+    gap_inner_horizontal = 5,
+    gap_inner_vertical = 5,
+    gap_outer_horizontal = 5,
+    gap_outer_vertical = 5,
+
+    -- Basics
+    modkey = "Mod4",
+    terminal = "st",
+
+    -- Workspace tags
+    tags = {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
+
+    -- Keybindings (see below for details)
+    keybindings = { ... },
+
+    -- Status bar blocks (see below for details)
+    status_blocks = { ... },
+
+    -- Color schemes
+    scheme_normal = { ... },
+    scheme_occupied = { ... },
+    scheme_selected = { ... },
+
+    -- Autostart commands
+    autostart = {
+        "picom -b",
+        "nitrogen --restore &",
+    },
+}
+```
+
+### Keybindings
+
+#### Single Key Bindings
+```lua
+{
+    modifiers = {"Mod", "Shift"},
+    key = "Return",
+    action = "Spawn",
+    arg = "alacritty"
+}
+```
+
+#### Keychords (Multi-key Sequences)
+```lua
+{
+    keys = {
+        {modifiers = {"Mod4"}, key = "Space"},
+        {modifiers = {}, key = "T"}
+    },
+    action = "Spawn",
+    arg = "st"
+}
+```
+
+Press Escape to cancel any in-progress keychord.
+
+#### Available Modifiers
+- `"Mod"` - Replaced with your configured modkey
+- `"Mod1"` - Alt key
+- `"Mod4"` - Super/Windows key
+- `"Shift"` - Shift key
+- `"Control"` - Control key
+- `"Mod2"`, `"Mod3"`, `"Mod5"` - Additional modifiers
+
+#### 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 (for Rust config)
+- `"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
+
+### Status Bar Configuration
+
+Status blocks support various command types for displaying system information:
+
+```lua
+status_blocks = {
+    -- RAM usage
+    {
+        format = "Ram: {used}/{total} GB",
+        command = "Ram",
+        interval_secs = 5,
+        color = 0x7aa2f7,
+        underline = true
+    },
+
+    -- Date/Time with custom format
+    {
+        format = "{}",
+        command = "DateTime",
+        command_arg = "%a, %b %d - %-I:%M %P",
+        interval_secs = 1,
+        color = 0x0db9d7,
+        underline = true
+    },
+
+    -- Custom shell command
+    {
+        format = "Kernel: {}",
+        command = "Shell",
+        command_arg = "uname -r",
+        interval_secs = 999999999,
+        color = 0xf7768e,
+        underline = true
+    },
+
+    -- Static text (separator)
+    {
+        format = " │  ",
+        command = "Static",
+        interval_secs = 999999999,
+        color = 0xa9b1d6,
+        underline = false
+    },
+
+    -- Battery status
+    {
+        format = "{}",
+        command = "Battery",
+        interval_secs = 30,
+        color = 0x9ece6a,
+        underline = true,
+        battery_formats = {
+            charging = "⚡ {}%",
+            discharging = "🔋 {}%",
+            full = "✓ {}%"
+        }
+    },
+}
+```
+
+#### Available Status Block 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 (format field determines what's shown)
+- `"Battery"` - Shows battery status (requires battery_formats table)
+
+### Layout Symbols
+
+Customize how layouts appear in the status bar:
+
+```lua
+layout_symbols = {
+    {name = "tiling", symbol = "[T]"},
+    {name = "normie", symbol = "[F]"},
+}
+```
+
+### Color Schemes
+
+Define color schemes for tag indicators in the status bar:
+
+```lua
+scheme_normal = {
+    foreground = 0xbbbbbb,
+    background = 0x1a1b26,
+    underline = 0x444444
+},
+scheme_occupied = {
+    foreground = 0x0db9d7,
+    background = 0x1a1b26,
+    underline = 0x0db9d7
+},
+scheme_selected = {
+    foreground = 0x0db9d7,
+    background = 0x1a1b26,
+    underline = 0xad8ee6
+}
+```
+
+### Autostart Commands
+
+Commands to run when the window manager starts:
+
+```lua
+autostart = {
+    "picom -b",
+    "nitrogen --restore &",
+    "dunst &",
+}
+```
+
+### Advanced Usage
+
+You can use Lua's full programming capabilities for dynamic configuration:
+
+```lua
+-- Generate keybindings programmatically
+for i = 1, 9 do
+    table.insert(config.keybindings, {
+        modifiers = {"Mod"},
+        key = tostring(i),
+        action = "ViewTag",
+        arg = i - 1
+    })
+end
+
+-- Create helper functions
+local function create_spawn_binding(mods, key, cmd)
+    return {modifiers = mods, key = key, action = "Spawn", arg = cmd}
+end
+
+table.insert(config.keybindings,
+    create_spawn_binding({"Mod"}, "Return", terminal))
+```
+
+### Migration from Rust Config
+
+For existing users with Rust-based configurations:
+1. Your old Rust config still works - no breaking changes
+2. To migrate to Lua, run OXWM and it will generate `~/.config/oxwm/config.lua`
+3. Customize the generated file to match your preferences
+4. Reload with `Mod+Shift+R` to apply changes
+
+### Technical Implementation
+
+- Configuration parsing via `mlua` (Lua 5.4)
+- Full deserialization into Rust types with `serde`
+- Hot-reloading on `Restart` action
+- Comprehensive error reporting for config issues
+- Type-safe validation of all configuration values
+
+### Files
+
+- Template: `templates/config.lua` - Default configuration template
+- User config: `~/.config/oxwm/config.lua` - User's configuration file
+- Test config: `resources/test-config.lua` - Configuration for Xephyr testing
+
+---
+
+## Documentation Suggestions
+
+### New Pages to Create
+
+1. **"Lua Configuration Guide"** - Complete guide to Lua config
+   - Getting started
+   - Configuration structure
+   - Examples for common use cases
+   - Tips and best practices
+
+2. **"Keybindings Reference"** - Detailed keybinding documentation
+   - How to define single key bindings
+   - How to create keychords
+   - List of all available actions with examples
+   - Modifier key reference
+
+3. **"Status Bar Configuration"** - Status bar setup guide
+   - Available command types
+   - Format string syntax
+   - Custom shell commands
+   - Battery configuration
+   - Creating custom blocks
+
+### Pages to Update
+
+1. **Installation/Quickstart**
+   - Mention Lua configuration as the recommended approach
+   - Add note that no recompilation needed for config changes
+   - Update first-run instructions
+
+2. **Configuration Page** (if exists)
+   - Add prominent notice about Lua support
+   - Deprecation notice for Rust config (if planned)
+   - Link to new Lua configuration guide
+
+3. **Keybindings Page** (if exists)
+   - Update with Lua syntax
+   - Add keychord examples
+   - Update modifier key documentation
+
+### Highlights for Documentation
+
+**Key Selling Points:**
+- "Edit and reload instantly - no compilation required"
+- "LSP-powered autocomplete for config editing"
+- "Full Lua programming support for dynamic configs"
+- "Keychord support for complex key combinations"
+
+**Common Questions to Address:**
+- How do I reload my config? (`Mod+Shift+R`)
+- Where is my config file? (`~/.config/oxwm/config.lua`)
+- Can I still use the Rust config? (Yes, for now)
+- How do I see config errors? (Check terminal output when starting OXWM)
+
+### Example Snippets for Docs
+
+#### Quick Config Example
+```lua
+-- Minimal working config
+config = {
+    modkey = "Mod4",
+    terminal = "alacritty",
+    tags = {"web", "code", "term"},
+    border_width = 2,
+    border_focused = 0x89b4fa,
+    border_unfocused = 0x45475a,
+    keybindings = {
+        {modifiers = {"Mod"}, key = "Return", action = "Spawn", arg = "alacritty"},
+        {modifiers = {"Mod"}, key = "Q", action = "KillClient"},
+        {modifiers = {"Mod", "Shift"}, key = "Q", action = "Quit"},
+    }
+}
+```
+
+#### Advanced Keychord Example
+```lua
+-- Open application menu with Mod+Space, then choose app
+keybindings = {
+    -- Mod+Space, then B -> browser
+    {
+        keys = {
+            {modifiers = {"Mod4"}, key = "Space"},
+            {modifiers = {}, key = "B"}
+        },
+        action = "Spawn",
+        arg = "firefox"
+    },
+    -- Mod+Space, then T -> terminal
+    {
+        keys = {
+            {modifiers = {"Mod4"}, key = "Space"},
+            {modifiers = {}, key = "T"}
+        },
+        action = "Spawn",
+        arg = "alacritty"
+    },
+}
+```
+
+---
+
+## Other Recent Updates
+
+### Autostart Support
+- Added `autostart` field to configuration
+- Commands run when window manager starts
+- Useful for launching compositors, wallpaper setters, notification daemons, etc.
+
+### Fullscreen Fixes
+- Fixed issue where fullscreen windows weren't properly applying geometries
+- Border handling corrected for fullscreen mode
+
+### Layout Updates
+- Improved layout switching behavior
+- Better handling of layout symbols in status bar
+
+---
+
+**Last Updated:** 2025-11-07
diff --git a/justfile b/justfile
index 4c8528d..0cbb0a3 100644
--- a/justfile
+++ b/justfile
@@ -10,7 +10,7 @@ install: build
 uninstall:
     rm -f /usr/bin/oxwm
     @echo "✓ oxwm uninstalled"
-    @echo "  Your config at ~/.config/oxwm/config.ron is preserved"
+    @echo "  Your config at ~/.config/oxwm/ is preserved"
 
 clean:
     cargo clean
@@ -19,23 +19,23 @@ test-clean:
 	pkill Xephyr || true
 	rm -rf ~/.config/oxwm
 	Xephyr -screen 1280x800 :1 & sleep 1
-	DISPLAY=:1 cargo run --release -- --config resources/test-config.ron
+	DISPLAY=:1 cargo run --release -- --config resources/test-config.lua
 
 test:
 	pkill Xephyr || true
 	Xephyr -screen 1280x800 :1 & sleep 1
-	DISPLAY=:1 cargo run --release -- --config resources/test-config.ron
+	DISPLAY=:1 cargo run --release -- --config resources/test-config.lua
 
 test-multimon:
 	pkill Xephyr || true
 	Xephyr +xinerama -screen 640x480 -screen 640x480 :1 & sleep 1
-	DISPLAY=:1 cargo run --release -- --config resources/test-config.ron
+	DISPLAY=:1 cargo run --release -- --config resources/test-config.lua
 
 init:
     cargo run --release -- --init
 
 edit:
-    $EDITOR ~/.config/oxwm/config.ron
+    $EDITOR ~/.config/oxwm/config.lua
 
 check:
     cargo clippy -- -W clippy::all
diff --git a/resources/test-config.lua b/resources/test-config.lua
new file mode 100644
index 0000000..28524c2
--- /dev/null
+++ b/resources/test-config.lua
@@ -0,0 +1,260 @@
+-- OXWM Test Configuration File (Lua)
+-- This config uses Mod1 (Alt) as the modkey for testing in Xephyr
+
+---@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"
+
+-- Color palette (Tokyo Night theme)
+local colors = {
+    blue = 0x6dade3,
+    grey = 0xbbbbbb,
+    green = 0x9ece6a,
+    red = 0xf7768e,
+    cyan = 0x0db9d7,
+    purple = 0xad8ee6,
+    lavender = 0xa9b1d6,
+    bg = 0x1a1b26,
+    fg = 0xbbbbbb,
+    light_blue = 0x7aa2f7,
+}
+
+-- Main configuration table
+---@type Config
+config = {
+    -- Appearance
+    border_width = 2,
+    border_focused = colors.blue,
+    border_unfocused = colors.grey,
+    font = "monospace:style=Bold:size=10",
+
+    -- Window gaps
+    gaps_enabled = true,
+    gap_inner_horizontal = 5,
+    gap_inner_vertical = 5,
+    gap_outer_horizontal = 5,
+    gap_outer_vertical = 5,
+
+    -- Basics
+    modkey = modkey,
+    terminal = terminal,
+
+    -- Workspace tags
+    tags = {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
+
+    -- Layout symbol overrides
+    layout_symbols = {
+        {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"}
+            },
+            action = "Spawn",
+            arg = terminal
+        },
+    },
+
+    -- Status bar blocks
+    status_blocks = {
+        {
+            format = "Ram: {used}/{total} GB",
+            command = "Ram",
+            interval_secs = 5,
+            color = colors.light_blue,
+            underline = true
+        },
+        {
+            format = " │  ",
+            command = "Static",
+            interval_secs = 999999999, -- Very large number for static blocks
+            color = colors.lavender,
+            underline = false
+        },
+        {
+            format = "Kernel: {}",
+            command = "Shell",
+            command_arg = "uname -r",
+            interval_secs = 999999999,
+            color = colors.red,
+            underline = true
+        },
+        {
+            format = " │  ",
+            command = "Static",
+            interval_secs = 999999999,
+            color = colors.lavender,
+            underline = false
+        },
+        {
+            format = "{}",
+            command = "DateTime",
+            command_arg = "%a, %b %d - %-I:%M %P",
+            interval_secs = 1,
+            color = colors.cyan,
+            underline = true
+        },
+    },
+
+    -- Color schemes for bar
+    scheme_normal = {
+        foreground = colors.fg,
+        background = colors.bg,
+        underline = 0x444444
+    },
+    scheme_occupied = {
+        foreground = colors.cyan,
+        background = colors.bg,
+        underline = colors.cyan
+    },
+    scheme_selected = {
+        foreground = colors.cyan,
+        background = colors.bg,
+        underline = colors.purple
+    },
+
+    -- Autostart commands
+    autostart = {},
+}
diff --git a/src/bin/main.rs b/src/bin/main.rs
index cff5555..af27e0b 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -50,27 +50,46 @@ fn load_config(custom_path: Option<PathBuf>) -> Result<oxwm::Config> {
     let config_path = if let Some(path) = custom_path {
         path
     } else {
-        let default_path = get_config_path().join("config.ron");
-        if !default_path.exists() {
-            println!("No config found at {:?}", default_path);
-            println!("Creating default config...");
+        // Try to find config.lua first, then config.ron
+        let config_dir = get_config_path();
+        let lua_path = config_dir.join("config.lua");
+        let ron_path = config_dir.join("config.ron");
+
+        if lua_path.exists() {
+            lua_path
+        } else if ron_path.exists() {
+            ron_path
+        } else {
+            println!("No config found at {:?}", config_dir);
+            println!("Creating default Lua config...");
             init_config()?;
+            config_dir.join("config.lua")
         }
-        default_path
     };
 
     let config_str =
         std::fs::read_to_string(&config_path).with_context(|| "Failed to read config file")?;
 
-    oxwm::config::parse_config(&config_str).with_context(|| "Failed to parse config")
+    // Determine config format based on file extension
+    let is_lua = config_path
+        .extension()
+        .and_then(|s| s.to_str())
+        .map(|s| s == "lua")
+        .unwrap_or(false);
+
+    if is_lua {
+        oxwm::config::parse_lua_config(&config_str).with_context(|| "Failed to parse Lua config")
+    } else {
+        oxwm::config::parse_config(&config_str).with_context(|| "Failed to parse RON config")
+    }
 }
 
 fn init_config() -> Result<()> {
     let config_dir = get_config_path();
     std::fs::create_dir_all(&config_dir)?;
 
-    let config_template = include_str!("../../templates/config.ron");
-    let config_path = config_dir.join("config.ron");
+    let config_template = include_str!("../../templates/config.lua");
+    let config_path = config_dir.join("config.lua");
 
     std::fs::write(&config_path, config_template)?;
 
@@ -92,12 +111,12 @@ fn print_help() {
     println!("USAGE:");
     println!("    oxwm [OPTIONS]\n");
     println!("OPTIONS:");
-    println!("    --init              Create default config in ~/.config/oxwm/config.ron");
-    println!("    --config <PATH>     Use custom config file");
+    println!("    --init              Create default config in ~/.config/oxwm/config.lua");
+    println!("    --config <PATH>     Use custom config file (.lua or .ron)");
     println!("    --version           Print version information");
     println!("    --help              Print this help message\n");
     println!("CONFIG:");
-    println!("    Location: ~/.config/oxwm/config.ron");
+    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!("FIRST RUN:");
diff --git a/src/config/lua.rs b/src/config/lua.rs
new file mode 100644
index 0000000..e1a9930
--- /dev/null
+++ b/src/config/lua.rs
@@ -0,0 +1,574 @@
+use crate::bar::{BlockCommand, BlockConfig};
+use crate::errors::ConfigError;
+use crate::keyboard::handlers::{KeyBinding, KeyPress};
+use crate::keyboard::keysyms::{self, Keysym};
+use crate::keyboard::{Arg, KeyAction};
+use crate::{ColorScheme, LayoutSymbolOverride};
+use mlua::{Lua, Table, Value};
+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()
+        .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")?;
+    let font: String = get_table_field(&config, "font")?;
+
+    let gaps_enabled: bool = get_table_field(&config, "gaps_enabled")?;
+    let gap_inner_horizontal: u32 = get_table_field(&config, "gap_inner_horizontal")?;
+    let gap_inner_vertical: u32 = get_table_field(&config, "gap_inner_vertical")?;
+    let gap_outer_horizontal: u32 = get_table_field(&config, "gap_outer_horizontal")?;
+    let gap_outer_vertical: u32 = get_table_field(&config, "gap_outer_vertical")?;
+
+    let terminal: String = get_table_field(&config, "terminal")?;
+    let modkey = parse_modkey(&config)?;
+
+    let tags = parse_tags(&config)?;
+    let layout_symbols = parse_layout_symbols(&config)?;
+    let keybindings = parse_keybindings(&config, modkey)?;
+    let status_blocks = parse_status_blocks(&config)?;
+
+    let scheme_normal = parse_color_scheme(&config, "scheme_normal")?;
+    let scheme_occupied = parse_color_scheme(&config, "scheme_occupied")?;
+    let scheme_selected = parse_color_scheme(&config, "scheme_selected")?;
+
+    let autostart = parse_autostart(&config)?;
+
+    Ok(crate::Config {
+        border_width,
+        border_focused,
+        border_unfocused,
+        font,
+        gaps_enabled,
+        gap_inner_horizontal,
+        gap_inner_vertical,
+        gap_outer_horizontal,
+        gap_outer_vertical,
+        terminal,
+        modkey,
+        tags,
+        layout_symbols,
+        keybindings,
+        status_blocks,
+        scheme_normal,
+        scheme_occupied,
+        scheme_selected,
+        autostart,
+    })
+}
+
+fn get_table_field<T>(table: &Table, field: &str) -> Result<T, ConfigError>
+where
+    T: mlua::FromLua,
+{
+    table
+        .get::<T>(field)
+        .map_err(|e| ConfigError::LuaError(format!("Failed to get field '{}': {}", field, e)))
+}
+
+fn parse_color(table: &Table, field: &str) -> Result<u32, ConfigError> {
+    let value: Value = table
+        .get(field)
+        .map_err(|e| ConfigError::LuaError(format!("Failed to get color field '{}': {}", field, e)))?;
+
+    match value {
+        Value::String(s) => {
+            let s = s.to_str().map_err(|e| {
+                ConfigError::LuaError(format!("Invalid UTF-8 in color string: {}", e))
+            })?;
+            parse_color_string(&s)
+        }
+        Value::Integer(i) => Ok(i as u32),
+        Value::Number(n) => Ok(n as u32),
+        _ => Err(ConfigError::LuaError(format!(
+            "Color field '{}' must be a string or number",
+            field
+        ))),
+    }
+}
+
+fn parse_color_string(s: &str) -> Result<u32, ConfigError> {
+    let s = s.trim();
+    if s.starts_with('#') {
+        u32::from_str_radix(&s[1..], 16)
+            .map_err(|e| ConfigError::LuaError(format!("Invalid hex color '{}': {}", s, e)))
+    } else if s.starts_with("0x") {
+        u32::from_str_radix(&s[2..], 16)
+            .map_err(|e| ConfigError::LuaError(format!("Invalid hex color '{}': {}", s, e)))
+    } else {
+        s.parse::<u32>()
+            .map_err(|e| ConfigError::LuaError(format!("Invalid color '{}': {}", s, e)))
+    }
+}
+
+fn parse_modkey(config: &Table) -> Result<KeyButMask, ConfigError> {
+    let modkey_str: String = get_table_field(config, "modkey")?;
+    parse_modkey_string(&modkey_str)
+}
+
+fn parse_modkey_string(s: &str) -> Result<KeyButMask, ConfigError> {
+    match s {
+        "Mod1" => Ok(KeyButMask::MOD1),
+        "Mod2" => Ok(KeyButMask::MOD2),
+        "Mod3" => Ok(KeyButMask::MOD3),
+        "Mod4" => Ok(KeyButMask::MOD4),
+        "Mod5" => Ok(KeyButMask::MOD5),
+        "Shift" => Ok(KeyButMask::SHIFT),
+        "Control" => Ok(KeyButMask::CONTROL),
+        _ => Err(ConfigError::InvalidModkey(s.to_string())),
+    }
+}
+
+fn parse_tags(config: &Table) -> Result<Vec<String>, ConfigError> {
+    let tags_table: Table = get_table_field(config, "tags")?;
+    let mut tags = Vec::new();
+
+    for i in 1..=tags_table.len().map_err(|e| {
+        ConfigError::LuaError(format!("Failed to get tags length: {}", e))
+    })? {
+        let tag: String = tags_table.get(i).map_err(|e| {
+            ConfigError::LuaError(format!("Failed to get tag at index {}: {}", i, e))
+        })?;
+        tags.push(tag);
+    }
+
+    Ok(tags)
+}
+
+fn parse_layout_symbols(config: &Table) -> Result<Vec<LayoutSymbolOverride>, ConfigError> {
+    let layout_symbols_result: Result<Table, _> = config.get("layout_symbols");
+
+    match layout_symbols_result {
+        Ok(layout_symbols_table) => {
+            let mut layout_symbols = Vec::new();
+
+            for i in 1..=layout_symbols_table.len().map_err(|e| {
+                ConfigError::LuaError(format!("Failed to get layout_symbols length: {}", e))
+            })? {
+                let entry: Table = layout_symbols_table.get(i).map_err(|e| {
+                    ConfigError::LuaError(format!("Failed to get layout_symbol at index {}: {}", i, e))
+                })?;
+
+                let name: String = get_table_field(&entry, "name")?;
+                let symbol: String = get_table_field(&entry, "symbol")?;
+
+                layout_symbols.push(LayoutSymbolOverride { name, symbol });
+            }
+
+            Ok(layout_symbols)
+        }
+        Err(_) => Ok(Vec::new()), // layout_symbols is optional
+    }
+}
+
+fn parse_keybindings(
+    config: &Table,
+    modkey: KeyButMask,
+) -> Result<Vec<KeyBinding>, ConfigError> {
+    let keybindings_table: Table = get_table_field(config, "keybindings")?;
+    let mut keybindings = Vec::new();
+
+    for i in 1..=keybindings_table.len().map_err(|e| {
+        ConfigError::LuaError(format!("Failed to get keybindings length: {}", e))
+    })? {
+        let kb_table: Table = keybindings_table.get(i).map_err(|e| {
+            ConfigError::LuaError(format!("Failed to get keybinding at index {}: {}", i, e))
+        })?;
+
+        let keys = parse_keypress_list(&kb_table, modkey)?;
+        let action = parse_key_action(&kb_table)?;
+        let arg = parse_arg(&kb_table)?;
+
+        keybindings.push(KeyBinding::new(keys, action, arg));
+    }
+
+    Ok(keybindings)
+}
+
+fn parse_keypress_list(
+    kb_table: &Table,
+    modkey: KeyButMask,
+) -> Result<Vec<KeyPress>, ConfigError> {
+    // Check if 'keys' field exists (for keychords)
+    let keys_result: Result<Table, _> = kb_table.get("keys");
+
+    if let Ok(keys_table) = keys_result {
+        // Parse keychord
+        let mut keys = Vec::new();
+        for i in 1..=keys_table.len().map_err(|e| {
+            ConfigError::LuaError(format!("Failed to get keys length: {}", e))
+        })? {
+            let key_entry: Table = keys_table.get(i).map_err(|e| {
+                ConfigError::LuaError(format!("Failed to get key at index {}: {}", i, e))
+            })?;
+
+            let modifiers = parse_modifiers(&key_entry, "modifiers", modkey)?;
+            let keysym = parse_keysym(&key_entry, "key")?;
+
+            keys.push(KeyPress { modifiers, keysym });
+        }
+        Ok(keys)
+    } else {
+        // Parse single key (old format)
+        let modifiers = parse_modifiers(kb_table, "modifiers", modkey)?;
+        let keysym = parse_keysym(kb_table, "key")?;
+
+        Ok(vec![KeyPress { modifiers, keysym }])
+    }
+}
+
+fn parse_modifiers(
+    table: &Table,
+    field: &str,
+    modkey: KeyButMask,
+) -> Result<Vec<KeyButMask>, ConfigError> {
+    let mods_table: Table = get_table_field(table, field)?;
+    let mut modifiers = Vec::new();
+
+    for i in 1..=mods_table.len().map_err(|e| {
+        ConfigError::LuaError(format!("Failed to get modifiers length: {}", e))
+    })? {
+        let mod_str: String = mods_table.get(i).map_err(|e| {
+            ConfigError::LuaError(format!("Failed to get modifier at index {}: {}", i, e))
+        })?;
+
+        let modifier = if mod_str == "Mod" {
+            modkey
+        } else {
+            parse_modkey_string(&mod_str)?
+        };
+
+        modifiers.push(modifier);
+    }
+
+    Ok(modifiers)
+}
+
+fn parse_keysym(table: &Table, field: &str) -> Result<Keysym, ConfigError> {
+    let key_str: String = get_table_field(table, field)?;
+    string_to_keysym(&key_str)
+}
+
+fn string_to_keysym(s: &str) -> Result<Keysym, ConfigError> {
+    let keysym = match s {
+        "Return" => keysyms::XK_RETURN,
+        "Q" => keysyms::XK_Q,
+        "Escape" => keysyms::XK_ESCAPE,
+        "Space" => keysyms::XK_SPACE,
+        "Tab" => keysyms::XK_TAB,
+        "Backspace" => keysyms::XK_BACKSPACE,
+        "Delete" => keysyms::XK_DELETE,
+        "F1" => keysyms::XK_F1,
+        "F2" => keysyms::XK_F2,
+        "F3" => keysyms::XK_F3,
+        "F4" => keysyms::XK_F4,
+        "F5" => keysyms::XK_F5,
+        "F6" => keysyms::XK_F6,
+        "F7" => keysyms::XK_F7,
+        "F8" => keysyms::XK_F8,
+        "F9" => keysyms::XK_F9,
+        "F10" => keysyms::XK_F10,
+        "F11" => keysyms::XK_F11,
+        "F12" => keysyms::XK_F12,
+        "A" => keysyms::XK_A,
+        "B" => keysyms::XK_B,
+        "C" => keysyms::XK_C,
+        "D" => keysyms::XK_D,
+        "E" => keysyms::XK_E,
+        "F" => keysyms::XK_F,
+        "G" => keysyms::XK_G,
+        "H" => keysyms::XK_H,
+        "I" => keysyms::XK_I,
+        "J" => keysyms::XK_J,
+        "K" => keysyms::XK_K,
+        "L" => keysyms::XK_L,
+        "M" => keysyms::XK_M,
+        "N" => keysyms::XK_N,
+        "O" => keysyms::XK_O,
+        "P" => keysyms::XK_P,
+        "R" => keysyms::XK_R,
+        "S" => keysyms::XK_S,
+        "T" => keysyms::XK_T,
+        "U" => keysyms::XK_U,
+        "V" => keysyms::XK_V,
+        "W" => keysyms::XK_W,
+        "X" => keysyms::XK_X,
+        "Y" => keysyms::XK_Y,
+        "Z" => keysyms::XK_Z,
+        "0" => keysyms::XK_0,
+        "1" => keysyms::XK_1,
+        "2" => keysyms::XK_2,
+        "3" => keysyms::XK_3,
+        "4" => keysyms::XK_4,
+        "5" => keysyms::XK_5,
+        "6" => keysyms::XK_6,
+        "7" => keysyms::XK_7,
+        "8" => keysyms::XK_8,
+        "9" => keysyms::XK_9,
+        "Left" => keysyms::XK_LEFT,
+        "Right" => keysyms::XK_RIGHT,
+        "Up" => keysyms::XK_UP,
+        "Down" => keysyms::XK_DOWN,
+        "Home" => keysyms::XK_HOME,
+        "End" => keysyms::XK_END,
+        "PageUp" => keysyms::XK_PAGE_UP,
+        "PageDown" => keysyms::XK_PAGE_DOWN,
+        "Insert" => keysyms::XK_INSERT,
+        "Minus" => keysyms::XK_MINUS,
+        "Equal" => keysyms::XK_EQUAL,
+        "BracketLeft" => keysyms::XK_LEFT_BRACKET,
+        "BracketRight" => keysyms::XK_RIGHT_BRACKET,
+        "Semicolon" => keysyms::XK_SEMICOLON,
+        "Apostrophe" => keysyms::XK_APOSTROPHE,
+        "Grave" => keysyms::XK_GRAVE,
+        "Backslash" => keysyms::XK_BACKSLASH,
+        "Comma" => keysyms::XK_COMMA,
+        "Period" => keysyms::XK_PERIOD,
+        "Slash" => keysyms::XK_SLASH,
+        "AudioRaiseVolume" => keysyms::XF86_AUDIO_RAISE_VOLUME,
+        "AudioLowerVolume" => keysyms::XF86_AUDIO_LOWER_VOLUME,
+        "AudioMute" => keysyms::XF86_AUDIO_MUTE,
+        "MonBrightnessUp" => keysyms::XF86_MON_BRIGHTNESS_UP,
+        "MonBrightnessDown" => keysyms::XF86_MON_BRIGHTNESS_DOWN,
+        _ => return Err(ConfigError::UnknownKey(s.to_string())),
+    };
+
+    Ok(keysym)
+}
+
+fn parse_key_action(kb_table: &Table) -> Result<KeyAction, ConfigError> {
+    let action_str: String = get_table_field(kb_table, "action")?;
+    string_to_key_action(&action_str)
+}
+
+fn string_to_key_action(s: &str) -> Result<KeyAction, ConfigError> {
+    let action = match s {
+        "Spawn" => KeyAction::Spawn,
+        "KillClient" => KeyAction::KillClient,
+        "FocusStack" => KeyAction::FocusStack,
+        "FocusDirection" => KeyAction::FocusDirection,
+        "SwapDirection" => KeyAction::SwapDirection,
+        "Quit" => KeyAction::Quit,
+        "Restart" => KeyAction::Restart,
+        "Recompile" => KeyAction::Recompile,
+        "ViewTag" => KeyAction::ViewTag,
+        "ToggleGaps" => KeyAction::ToggleGaps,
+        "ToggleFullScreen" => KeyAction::ToggleFullScreen,
+        "ToggleFloating" => KeyAction::ToggleFloating,
+        "ChangeLayout" => KeyAction::ChangeLayout,
+        "CycleLayout" => KeyAction::CycleLayout,
+        "MoveToTag" => KeyAction::MoveToTag,
+        "FocusMonitor" => KeyAction::FocusMonitor,
+        "SmartMoveWin" => KeyAction::SmartMoveWin,
+        "ExchangeClient" => KeyAction::ExchangeClient,
+        "None" => KeyAction::None,
+        _ => return Err(ConfigError::UnknownAction(s.to_string())),
+    };
+
+    Ok(action)
+}
+
+fn parse_arg(kb_table: &Table) -> Result<Arg, ConfigError> {
+    let arg_result: Result<Value, _> = kb_table.get("arg");
+
+    match arg_result {
+        Ok(Value::Nil) | Err(_) => Ok(Arg::None),
+        Ok(Value::String(s)) => {
+            let s = s
+                .to_str()
+                .map_err(|e| ConfigError::LuaError(format!("Invalid UTF-8 in arg: {}", e)))?;
+            Ok(Arg::Str(s.to_string()))
+        }
+        Ok(Value::Integer(i)) => Ok(Arg::Int(i as i32)),
+        Ok(Value::Number(n)) => Ok(Arg::Int(n as i32)),
+        Ok(Value::Table(t)) => {
+            let mut arr = Vec::new();
+            for i in 1..=t
+                .len()
+                .map_err(|e| ConfigError::LuaError(format!("Failed to get arg array length: {}", e)))?
+            {
+                let item: String = t.get(i).map_err(|e| {
+                    ConfigError::LuaError(format!("Failed to get arg array item at index {}: {}", i, e))
+                })?;
+                arr.push(item);
+            }
+            Ok(Arg::Array(arr))
+        }
+        Ok(_) => Err(ConfigError::LuaError(
+            "Arg must be nil, string, number, or array".to_string(),
+        )),
+    }
+}
+
+fn parse_status_blocks(config: &Table) -> Result<Vec<BlockConfig>, ConfigError> {
+    let blocks_table: Table = get_table_field(config, "status_blocks")?;
+    let mut blocks = Vec::new();
+
+    for i in 1..=blocks_table.len().map_err(|e| {
+        ConfigError::LuaError(format!("Failed to get status_blocks length: {}", e))
+    })? {
+        let block_table: Table = blocks_table.get(i).map_err(|e| {
+            ConfigError::LuaError(format!("Failed to get status_block at index {}: {}", i, e))
+        })?;
+
+        let format: String = get_table_field(&block_table, "format")?;
+        let command_str: String = get_table_field(&block_table, "command")?;
+
+        // Parse interval_secs - handle both integer and number types
+        let interval_secs: u64 = {
+            let value: Value = block_table.get("interval_secs").map_err(|e| {
+                ConfigError::LuaError(format!("Failed to get interval_secs: {}", e))
+            })?;
+            match value {
+                Value::Integer(i) => i as u64,
+                Value::Number(n) => n as u64,
+                _ => return Err(ConfigError::LuaError("interval_secs must be a number".to_string())),
+            }
+        };
+
+        let color: u32 = parse_color(&block_table, "color")?;
+        let underline: bool = get_table_field(&block_table, "underline")?;
+
+        let command = match command_str.as_str() {
+            "DateTime" => {
+                let fmt: String = get_table_field(&block_table, "command_arg")?;
+                BlockCommand::DateTime(fmt)
+            }
+            "Shell" => {
+                let cmd: String = get_table_field(&block_table, "command_arg")?;
+                BlockCommand::Shell(cmd)
+            }
+            "Ram" => BlockCommand::Ram,
+            "Static" => {
+                let text_result: Result<String, _> = block_table.get("command_arg");
+                let text = text_result.unwrap_or_default();
+                BlockCommand::Static(text)
+            }
+            "Battery" => {
+                let formats_table: Table = get_table_field(&block_table, "battery_formats")?;
+                let format_charging: String = get_table_field(&formats_table, "charging")?;
+                let format_discharging: String = get_table_field(&formats_table, "discharging")?;
+                let format_full: String = get_table_field(&formats_table, "full")?;
+
+                BlockCommand::Battery {
+                    format_charging,
+                    format_discharging,
+                    format_full,
+                }
+            }
+            _ => return Err(ConfigError::UnknownBlockCommand(command_str)),
+        };
+
+        blocks.push(BlockConfig {
+            format,
+            command,
+            interval_secs,
+            color,
+            underline,
+        });
+    }
+
+    Ok(blocks)
+}
+
+fn parse_color_scheme(config: &Table, field: &str) -> Result<ColorScheme, ConfigError> {
+    let scheme_table: Table = get_table_field(config, field)?;
+
+    let foreground = parse_color(&scheme_table, "foreground")?;
+    let background = parse_color(&scheme_table, "background")?;
+    let underline = parse_color(&scheme_table, "underline")?;
+
+    Ok(ColorScheme {
+        foreground,
+        background,
+        underline,
+    })
+}
+
+fn parse_autostart(config: &Table) -> Result<Vec<String>, ConfigError> {
+    let autostart_result: Result<Table, _> = config.get("autostart");
+
+    match autostart_result {
+        Ok(autostart_table) => {
+            let mut autostart = Vec::new();
+
+            for i in 1..=autostart_table.len().map_err(|e| {
+                ConfigError::LuaError(format!("Failed to get autostart length: {}", e))
+            })? {
+                let cmd: String = autostart_table.get(i).map_err(|e| {
+                    ConfigError::LuaError(format!("Failed to get autostart command at index {}: {}", i, e))
+                })?;
+                autostart.push(cmd);
+            }
+
+            Ok(autostart)
+        }
+        Err(_) => Ok(Vec::new()), // autostart is optional
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_parse_minimal_lua_config() {
+        let config_str = r#"
+config = {
+    border_width = 2,
+    border_focused = 0x6dade3,
+    border_unfocused = 0xbbbbbb,
+    font = "monospace:style=Bold:size=10",
+
+    gaps_enabled = true,
+    gap_inner_horizontal = 5,
+    gap_inner_vertical = 5,
+    gap_outer_horizontal = 5,
+    gap_outer_vertical = 5,
+
+    modkey = "Mod4",
+    terminal = "st",
+
+    tags = {"1", "2", "3"},
+
+    keybindings = {
+        {modifiers = {"Mod4"}, key = "Return", action = "Spawn", arg = "st"},
+        {modifiers = {"Mod4"}, key = "Q", action = "KillClient"},
+    },
+
+    status_blocks = {
+        {format = "{}", command = "DateTime", command_arg = "%H:%M", interval_secs = 1, color = 0xffffff, underline = true},
+    },
+
+    scheme_normal = {foreground = 0xffffff, background = 0x000000, underline = 0x444444},
+    scheme_occupied = {foreground = 0xffffff, background = 0x000000, underline = 0x444444},
+    scheme_selected = {foreground = 0xffffff, background = 0x000000, underline = 0x444444},
+
+    autostart = {},
+}
+"#;
+
+        let config = parse_lua_config(config_str).expect("Failed to parse config");
+
+        assert_eq!(config.border_width, 2);
+        assert_eq!(config.border_focused, 0x6dade3);
+        assert_eq!(config.terminal, "st");
+        assert_eq!(config.tags.len(), 3);
+        assert_eq!(config.keybindings.len(), 2);
+        assert_eq!(config.status_blocks.len(), 1);
+        assert!(config.gaps_enabled);
+    }
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index d7c4f2c..02fbd8f 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -1,3 +1,5 @@
+mod lua;
+
 use crate::bar::{BlockCommand, BlockConfig};
 use crate::errors::ConfigError;
 use crate::keyboard::handlers::{KeyBinding, KeyPress};
@@ -8,6 +10,8 @@ use serde::Deserialize;
 use std::collections::HashMap;
 use x11rb::protocol::xproto::KeyButMask;
 
+pub use lua::parse_lua_config;
+
 #[derive(Debug, Deserialize)]
 pub enum ModKey {
     Mod,
diff --git a/src/errors.rs b/src/errors.rs
index a48010e..0ee19d4 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -23,6 +23,7 @@ pub enum X11Error {
 #[derive(Debug)]
 pub enum ConfigError {
     ParseError(ron::error::SpannedError),
+    LuaError(String),
     InvalidModkey(String),
     UnknownKey(String),
     UnknownAction(String),
@@ -68,6 +69,7 @@ impl std::fmt::Display for ConfigError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
             Self::ParseError(err) => write!(f, "Failed to parse RON config: {}", err),
+            Self::LuaError(msg) => write!(f, "Lua config error: {}", msg),
             Self::InvalidModkey(key) => write!(f, "Invalid modkey: {}", key),
             Self::UnknownKey(key) => write!(f, "Unknown key: {}", key),
             Self::UnknownAction(action) => write!(f, "Unknown action: {}", action),
diff --git a/templates/config.lua b/templates/config.lua
new file mode 100644
index 0000000..fd3eabb
--- /dev/null
+++ b/templates/config.lua
@@ -0,0 +1,309 @@
+-- OXWM Configuration File (Lua)
+-- 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)
+local colors = {
+    blue = 0x6dade3,
+    grey = 0xbbbbbb,
+    green = 0x9ece6a,
+    red = 0xf7768e,
+    cyan = 0x0db9d7,
+    purple = 0xad8ee6,
+    lavender = 0xa9b1d6,
+    bg = 0x1a1b26,
+    fg = 0xbbbbbb,
+    light_blue = 0x7aa2f7,
+}
+
+-- Main configuration table
+---@type Config
+config = {
+    -- Appearance
+    border_width = 2,
+    border_focused = colors.blue,
+    border_unfocused = colors.grey,
+    font = "monospace:style=Bold:size=10",
+
+    -- Window gaps
+    gaps_enabled = true,
+    gap_inner_horizontal = 5,
+    gap_inner_vertical = 5,
+    gap_outer_horizontal = 5,
+    gap_outer_vertical = 5,
+
+    -- Basics
+    modkey = modkey,
+    terminal = terminal,
+
+    -- Workspace tags
+    tags = {"1", "2", "3", "4", "5", "6", "7", "8", "9"},
+
+    -- Layout symbol overrides
+    layout_symbols = {
+        {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
+        {
+            keys = {
+                {modifiers = {"Mod4"}, key = "Space"},
+                {modifiers = {}, key = "T"}
+            },
+            action = "Spawn",
+            arg = terminal
+        },
+    },
+
+    -- 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",
+            command = "Ram",
+            interval_secs = 5,
+            color = colors.light_blue,
+            underline = true
+        },
+        {
+            format = " │  ",
+            command = "Static",
+            interval_secs = 999999999, -- Very large number for static blocks (never update)
+            color = colors.lavender,
+            underline = false
+        },
+        {
+            format = "Kernel: {}",
+            command = "Shell",
+            command_arg = "uname -r",
+            interval_secs = 999999999,
+            color = colors.red,
+            underline = true
+        },
+        {
+            format = " │  ",
+            command = "Static",
+            interval_secs = 999999999,
+            color = colors.lavender,
+            underline = false
+        },
+        {
+            format = "{}",
+            command = "DateTime",
+            command_arg = "%a, %b %d - %-I:%M %P",
+            interval_secs = 1,
+            color = colors.cyan,
+            underline = true
+        },
+    },
+
+    -- Color schemes for bar
+    scheme_normal = {
+        foreground = colors.fg,
+        background = colors.bg,
+        underline = 0x444444
+    },
+    scheme_occupied = {
+        foreground = colors.cyan,
+        background = colors.bg,
+        underline = colors.cyan
+    },
+    scheme_selected = {
+        foreground = colors.cyan,
+        background = colors.bg,
+        underline = colors.purple
+    },
+
+    -- 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 &",
+    },
+}
+
+-- 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