oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
33,783 bytes raw
1
use mlua::{Lua, Table, Value};
2
use std::cell::RefCell;
3
use std::rc::Rc;
4
5
use crate::ColorScheme;
6
use crate::bar::BlockConfig;
7
use crate::errors::ConfigError;
8
use crate::keyboard::handlers::{Arg, KeyAction, KeyBinding, KeyPress};
9
use crate::keyboard::keysyms::{self, Keysym};
10
use x11rb::protocol::xproto::KeyButMask;
11
12
#[derive(Clone)]
13
pub struct ConfigBuilder {
14
    pub border_width: u32,
15
    pub border_focused: u32,
16
    pub border_unfocused: u32,
17
    pub font: String,
18
    pub gaps_enabled: bool,
19
    pub smartgaps_enabled: bool,
20
    pub gap_inner_horizontal: u32,
21
    pub gap_inner_vertical: u32,
22
    pub gap_outer_horizontal: u32,
23
    pub gap_outer_vertical: u32,
24
    pub terminal: String,
25
    pub modkey: KeyButMask,
26
    pub tags: Vec<String>,
27
    pub layout_symbols: Vec<crate::LayoutSymbolOverride>,
28
    pub keybindings: Vec<KeyBinding>,
29
    pub window_rules: Vec<crate::WindowRule>,
30
    pub status_blocks: Vec<BlockConfig>,
31
    pub scheme_normal: ColorScheme,
32
    pub scheme_occupied: ColorScheme,
33
    pub scheme_selected: ColorScheme,
34
    pub autostart: Vec<String>,
35
    pub auto_tile: bool,
36
}
37
38
impl Default for ConfigBuilder {
39
    fn default() -> Self {
40
        Self {
41
            border_width: 2,
42
            border_focused: 0x6dade3,
43
            border_unfocused: 0xbbbbbb,
44
            font: "monospace:style=Bold:size=10".to_string(),
45
            gaps_enabled: true,
46
            smartgaps_enabled: true,
47
            gap_inner_horizontal: 5,
48
            gap_inner_vertical: 5,
49
            gap_outer_horizontal: 5,
50
            gap_outer_vertical: 5,
51
            terminal: "st".to_string(),
52
            modkey: KeyButMask::MOD4,
53
            tags: vec!["1".into(), "2".into(), "3".into()],
54
            layout_symbols: Vec::new(),
55
            keybindings: Vec::new(),
56
            window_rules: Vec::new(),
57
            status_blocks: Vec::new(),
58
            scheme_normal: ColorScheme {
59
                foreground: 0xffffff,
60
                background: 0x000000,
61
                underline: 0x444444,
62
            },
63
            scheme_occupied: ColorScheme {
64
                foreground: 0xffffff,
65
                background: 0x000000,
66
                underline: 0x444444,
67
            },
68
            scheme_selected: ColorScheme {
69
                foreground: 0xffffff,
70
                background: 0x000000,
71
                underline: 0x444444,
72
            },
73
            autostart: Vec::new(),
74
            auto_tile: false,
75
        }
76
    }
77
}
78
79
type SharedBuilder = Rc<RefCell<ConfigBuilder>>;
80
81
pub fn register_api(lua: &Lua) -> Result<SharedBuilder, ConfigError> {
82
    let builder = Rc::new(RefCell::new(ConfigBuilder::default()));
83
84
    let oxwm_table = lua.create_table()?;
85
86
    register_spawn(&lua, &oxwm_table, builder.clone())?;
87
    register_key_module(&lua, &oxwm_table, builder.clone())?;
88
    register_gaps_module(&lua, &oxwm_table, builder.clone())?;
89
    register_border_module(&lua, &oxwm_table, builder.clone())?;
90
    register_client_module(&lua, &oxwm_table)?;
91
    register_layout_module(&lua, &oxwm_table)?;
92
    register_tag_module(&lua, &oxwm_table)?;
93
    register_monitor_module(&lua, &oxwm_table)?;
94
    register_rule_module(&lua, &oxwm_table, builder.clone())?;
95
    register_bar_module(&lua, &oxwm_table, builder.clone())?;
96
    register_misc(&lua, &oxwm_table, builder.clone())?;
97
98
    lua.globals().set("oxwm", oxwm_table)?;
99
100
    Ok(builder)
101
}
102
103
fn register_spawn(lua: &Lua, parent: &Table, _builder: SharedBuilder) -> Result<(), ConfigError> {
104
    let spawn = lua.create_function(|lua, cmd: Value| create_action_table(lua, "Spawn", cmd))?;
105
    let spawn_terminal =
106
        lua.create_function(|lua, ()| create_action_table(lua, "SpawnTerminal", Value::Nil))?;
107
    parent.set("spawn", spawn)?;
108
    parent.set("spawn_terminal", spawn_terminal)?;
109
    Ok(())
110
}
111
112
fn register_key_module(
113
    lua: &Lua,
114
    parent: &Table,
115
    builder: SharedBuilder,
116
) -> Result<(), ConfigError> {
117
    let key_table = lua.create_table()?;
118
119
    let builder_clone = builder.clone();
120
    let bind = lua.create_function(move |lua, (mods, key, action): (Value, String, Value)| {
121
        let modifiers = parse_modifiers_value(lua, mods)?;
122
        let keysym = parse_keysym(&key)?;
123
        let (key_action, arg) = parse_action_value(lua, action)?;
124
125
        let binding = KeyBinding::single_key(modifiers, keysym, key_action, arg);
126
        builder_clone.borrow_mut().keybindings.push(binding);
127
128
        Ok(())
129
    })?;
130
131
    let builder_clone = builder.clone();
132
    let chord = lua.create_function(move |lua, (keys, action): (Table, Value)| {
133
        let mut key_presses = Vec::new();
134
135
        for i in 1..=keys.len()? {
136
            let key_spec: Table = keys.get(i)?;
137
            let mods: Value = key_spec.get(1)?;
138
            let key: String = key_spec.get(2)?;
139
140
            let modifiers = parse_modifiers_value(lua, mods)?;
141
            let keysym = parse_keysym(&key)?;
142
143
            key_presses.push(KeyPress { modifiers, keysym });
144
        }
145
146
        let (key_action, arg) = parse_action_value(lua, action)?;
147
        let binding = KeyBinding::new(key_presses, key_action, arg);
148
        builder_clone.borrow_mut().keybindings.push(binding);
149
150
        Ok(())
151
    })?;
152
153
    key_table.set("bind", bind)?;
154
    key_table.set("chord", chord)?;
155
    parent.set("key", key_table)?;
156
    Ok(())
157
}
158
159
fn register_gaps_module(
160
    lua: &Lua,
161
    parent: &Table,
162
    builder: SharedBuilder,
163
) -> Result<(), ConfigError> {
164
    let gaps_table = lua.create_table()?;
165
166
    let builder_clone = builder.clone();
167
    let set_enabled = lua.create_function(move |_, enabled: bool| {
168
        builder_clone.borrow_mut().gaps_enabled = enabled;
169
        Ok(())
170
    })?;
171
172
    let builder_clone = builder.clone();
173
    let enable = lua.create_function(move |_, ()| {
174
        builder_clone.borrow_mut().gaps_enabled = true;
175
        Ok(())
176
    })?;
177
178
    let builder_clone = builder.clone();
179
    let disable = lua.create_function(move |_, ()| {
180
        builder_clone.borrow_mut().gaps_enabled = false;
181
        Ok(())
182
    })?;
183
184
    let builder_clone = builder.clone();
185
    let set_inner = lua.create_function(move |_, (h, v): (u32, u32)| {
186
        let mut b = builder_clone.borrow_mut();
187
        b.gap_inner_horizontal = h;
188
        b.gap_inner_vertical = v;
189
        Ok(())
190
    })?;
191
192
    let builder_clone = builder.clone();
193
    let set_outer = lua.create_function(move |_, (h, v): (u32, u32)| {
194
        let mut b = builder_clone.borrow_mut();
195
        b.gap_outer_horizontal = h;
196
        b.gap_outer_vertical = v;
197
        Ok(())
198
    })?;
199
200
    let builder_clone = builder.clone();
201
    let set_smart = lua.create_function(move |_, enabled: bool| {
202
        builder_clone.borrow_mut().smartgaps_enabled = enabled;
203
        Ok(())
204
    })?;
205
206
    gaps_table.set("set_enabled", set_enabled)?;
207
    gaps_table.set("enable", enable)?;
208
    gaps_table.set("disable", disable)?;
209
    gaps_table.set("set_inner", set_inner)?;
210
    gaps_table.set("set_outer", set_outer)?;
211
    gaps_table.set("set_smart", set_smart)?;
212
    parent.set("gaps", gaps_table)?;
213
    Ok(())
214
}
215
216
fn register_border_module(
217
    lua: &Lua,
218
    parent: &Table,
219
    builder: SharedBuilder,
220
) -> Result<(), ConfigError> {
221
    let border_table = lua.create_table()?;
222
223
    let builder_clone = builder.clone();
224
    let set_width = lua.create_function(move |_, width: u32| {
225
        builder_clone.borrow_mut().border_width = width;
226
        Ok(())
227
    })?;
228
229
    let builder_clone = builder.clone();
230
    let set_focused_color = lua.create_function(move |_, color: Value| {
231
        let color_u32 = parse_color_value(color)?;
232
        builder_clone.borrow_mut().border_focused = color_u32;
233
        Ok(())
234
    })?;
235
236
    let builder_clone = builder.clone();
237
    let set_unfocused_color = lua.create_function(move |_, color: Value| {
238
        let color_u32 = parse_color_value(color)?;
239
        builder_clone.borrow_mut().border_unfocused = color_u32;
240
        Ok(())
241
    })?;
242
243
    border_table.set("set_width", set_width)?;
244
    border_table.set("set_focused_color", set_focused_color)?;
245
    border_table.set("set_unfocused_color", set_unfocused_color)?;
246
    parent.set("border", border_table)?;
247
    Ok(())
248
}
249
250
fn register_client_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
251
    let client_table = lua.create_table()?;
252
253
    let kill = lua.create_function(|lua, ()| create_action_table(lua, "KillClient", Value::Nil))?;
254
255
    let toggle_fullscreen =
256
        lua.create_function(|lua, ()| create_action_table(lua, "ToggleFullScreen", Value::Nil))?;
257
258
    let toggle_floating =
259
        lua.create_function(|lua, ()| create_action_table(lua, "ToggleFloating", Value::Nil))?;
260
261
    let focus_stack = lua.create_function(|lua, dir: i32| {
262
        create_action_table(lua, "FocusStack", Value::Integer(dir as i64))
263
    })?;
264
265
    let move_stack = lua.create_function(|lua, dir: i32| {
266
        create_action_table(lua, "MoveStack", Value::Integer(dir as i64))
267
    })?;
268
269
    client_table.set("kill", kill)?;
270
    client_table.set("toggle_fullscreen", toggle_fullscreen)?;
271
    client_table.set("toggle_floating", toggle_floating)?;
272
    client_table.set("focus_stack", focus_stack)?;
273
    client_table.set("move_stack", move_stack)?;
274
275
    parent.set("client", client_table)?;
276
    Ok(())
277
}
278
279
fn register_layout_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
280
    let layout_table = lua.create_table()?;
281
282
    let cycle =
283
        lua.create_function(|lua, ()| create_action_table(lua, "CycleLayout", Value::Nil))?;
284
285
    let set = lua.create_function(|lua, name: String| {
286
        create_action_table(
287
            lua,
288
            "ChangeLayout",
289
            Value::String(lua.create_string(&name)?),
290
        )
291
    })?;
292
293
    layout_table.set("cycle", cycle)?;
294
    layout_table.set("set", set)?;
295
    parent.set("layout", layout_table)?;
296
    Ok(())
297
}
298
299
fn register_tag_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
300
    let tag_table = lua.create_table()?;
301
302
    let view = lua.create_function(|lua, idx: i32| {
303
        create_action_table(lua, "ViewTag", Value::Integer(idx as i64))
304
    })?;
305
306
    let toggleview = lua.create_function(|lua, idx: i32| {
307
        create_action_table(lua, "ToggleView", Value::Integer(idx as i64))
308
    })?;
309
310
    let move_to = lua.create_function(|lua, idx: i32| {
311
        create_action_table(lua, "MoveToTag", Value::Integer(idx as i64))
312
    })?;
313
314
    let toggletag = lua.create_function(|lua, idx: i32| {
315
        create_action_table(lua, "ToggleTag", Value::Integer(idx as i64))
316
    })?;
317
318
    tag_table.set("view", view)?;
319
    tag_table.set("toggleview", toggleview)?;
320
    tag_table.set("move_to", move_to)?;
321
    tag_table.set("toggletag", toggletag)?;
322
    parent.set("tag", tag_table)?;
323
    Ok(())
324
}
325
326
fn register_monitor_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
327
    let monitor_table = lua.create_table()?;
328
329
    let focus = lua.create_function(|lua, direction: i64| {
330
        create_action_table(lua, "FocusMonitor", Value::Integer(direction))
331
    })?;
332
333
    let tag = lua.create_function(|lua, direction: i64| {
334
        create_action_table(lua, "TagMonitor", Value::Integer(direction))
335
    })?;
336
337
    monitor_table.set("focus", focus)?;
338
    monitor_table.set("tag", tag)?;
339
    parent.set("monitor", monitor_table)?;
340
    Ok(())
341
}
342
343
fn register_rule_module(
344
    lua: &Lua,
345
    parent: &Table,
346
    builder: SharedBuilder,
347
) -> Result<(), ConfigError> {
348
    let rule_table = lua.create_table()?;
349
350
    let builder_clone = builder.clone();
351
    let add = lua.create_function(move |_, config: Table| {
352
        let class: Option<String> = config.get("class").ok();
353
        let instance: Option<String> = config.get("instance").ok();
354
        let title: Option<String> = config.get("title").ok();
355
        let is_floating: Option<bool> = config.get("floating").ok();
356
        let monitor: Option<usize> = config.get("monitor").ok();
357
358
        let tags: Option<u32> = if let Ok(tag_index) = config.get::<i32>("tag") {
359
            if tag_index > 0 {
360
                Some(1 << (tag_index - 1))
361
            } else {
362
                None
363
            }
364
        } else {
365
            None
366
        };
367
368
        let rule = crate::WindowRule {
369
            class,
370
            instance,
371
            title,
372
            tags,
373
            is_floating,
374
            monitor,
375
        };
376
377
        builder_clone.borrow_mut().window_rules.push(rule);
378
        Ok(())
379
    })?;
380
381
    rule_table.set("add", add)?;
382
    parent.set("rule", rule_table)?;
383
    Ok(())
384
}
385
386
fn register_bar_module(
387
    lua: &Lua,
388
    parent: &Table,
389
    builder: SharedBuilder,
390
) -> Result<(), ConfigError> {
391
    let bar_table = lua.create_table()?;
392
393
    let builder_clone = builder.clone();
394
    let set_font = lua.create_function(move |_, font: String| {
395
        builder_clone.borrow_mut().font = font;
396
        Ok(())
397
    })?;
398
399
    let block_table = lua.create_table()?;
400
401
    let ram =
402
        lua.create_function(|lua, config: Table| create_block_config(lua, config, "Ram", None))?;
403
404
    let datetime = lua.create_function(|lua, config: Table| {
405
        let date_format: String = config.get("date_format").map_err(|_| {
406
            mlua::Error::RuntimeError(
407
                "oxwm.bar.block.datetime: 'date_format' field is required (e.g., '%H:%M')".into(),
408
            )
409
        })?;
410
        create_block_config(
411
            lua,
412
            config,
413
            "DateTime",
414
            Some(Value::String(lua.create_string(&date_format)?)),
415
        )
416
    })?;
417
418
    let shell = lua.create_function(|lua, config: Table| {
419
        let command: String = config.get("command").map_err(|_| {
420
            mlua::Error::RuntimeError("oxwm.bar.block.shell: 'command' field is required".into())
421
        })?;
422
        create_block_config(
423
            lua,
424
            config,
425
            "Shell",
426
            Some(Value::String(lua.create_string(&command)?)),
427
        )
428
    })?;
429
430
    let static_block = lua.create_function(|lua, config: Table| {
431
        let text: String = config.get("text").map_err(|_| {
432
            mlua::Error::RuntimeError("oxwm.bar.block.static: 'text' field is required".into())
433
        })?;
434
        create_block_config(
435
            lua,
436
            config,
437
            "Static",
438
            Some(Value::String(lua.create_string(&text)?)),
439
        )
440
    })?;
441
442
    let battery = lua.create_function(|lua, config: Table| {
443
        let charging: String = config.get("charging").map_err(|_| {
444
            mlua::Error::RuntimeError("oxwm.bar.block.battery: 'charging' field is required".into())
445
        })?;
446
        let discharging: String = config.get("discharging").map_err(|_| {
447
            mlua::Error::RuntimeError(
448
                "oxwm.bar.block.battery: 'discharging' field is required".into(),
449
            )
450
        })?;
451
        let full: String = config.get("full").map_err(|_| {
452
            mlua::Error::RuntimeError("oxwm.bar.block.battery: 'full' field is required".into())
453
        })?;
454
455
        let formats_table = lua.create_table()?;
456
        formats_table.set("charging", charging)?;
457
        formats_table.set("discharging", discharging)?;
458
        formats_table.set("full", full)?;
459
460
        create_block_config(lua, config, "Battery", Some(Value::Table(formats_table)))
461
    })?;
462
463
    block_table.set("ram", ram)?;
464
    block_table.set("datetime", datetime)?;
465
    block_table.set("shell", shell)?;
466
    block_table.set("static", static_block)?;
467
    block_table.set("battery", battery)?;
468
469
    // Deprecated add_block() function for backwards compatibility
470
    // This allows old configs to still work, but users should migrate to set_blocks()
471
    let builder_clone = builder.clone();
472
    let add_block = lua.create_function(move |_, (format, block_type, arg, interval, color, underline): (String, String, Value, u64, Value, Option<bool>)| -> mlua::Result<()> {
473
        eprintln!("WARNING: oxwm.bar.add_block() is deprecated. Please migrate to oxwm.bar.set_blocks() with block constructors.");
474
        eprintln!("See the migration guide for details.");
475
476
        let cmd = match block_type.as_str() {
477
            "DateTime" => {
478
                let fmt = if let Value::String(s) = arg {
479
                    s.to_str()?.to_string()
480
                } else {
481
                    return Err(mlua::Error::RuntimeError("DateTime block requires format string as third argument".into()));
482
                };
483
                crate::bar::BlockCommand::DateTime(fmt)
484
            }
485
            "Shell" => {
486
                let cmd_str = if let Value::String(s) = arg {
487
                    s.to_str()?.to_string()
488
                } else {
489
                    return Err(mlua::Error::RuntimeError("Shell block requires command string as third argument".into()));
490
                };
491
                crate::bar::BlockCommand::Shell(cmd_str)
492
            }
493
            "Ram" => crate::bar::BlockCommand::Ram,
494
            "Static" => {
495
                let text = if let Value::String(s) = arg {
496
                    s.to_str()?.to_string()
497
                } else {
498
                    String::new()
499
                };
500
                crate::bar::BlockCommand::Static(text)
501
            }
502
            "Battery" => {
503
                return Err(mlua::Error::RuntimeError(
504
                    "Battery block is not supported with add_block(). Please use oxwm.bar.set_blocks() with oxwm.bar.block.battery()".into()
505
                ));
506
            }
507
            _ => return Err(mlua::Error::RuntimeError(format!("Unknown block type '{}'", block_type))),
508
        };
509
510
        let color_u32 = parse_color_value(color)?;
511
512
        let block = crate::bar::BlockConfig {
513
            format,
514
            command: cmd,
515
            interval_secs: interval,
516
            color: color_u32,
517
            underline: underline.unwrap_or(false),
518
        };
519
520
        builder_clone.borrow_mut().status_blocks.push(block);
521
        Ok(())
522
    })?;
523
524
    let builder_clone = builder.clone();
525
    let set_blocks = lua.create_function(move |_, blocks: Table| {
526
        use crate::bar::BlockCommand;
527
528
        let mut block_configs = Vec::new();
529
530
        for i in 1..=blocks.len()? {
531
            let block_table: Table = blocks.get(i)?;
532
            let block_type: String = block_table.get("__block_type")?;
533
            let format: String = block_table.get("format").unwrap_or_default();
534
            let interval: u64 = block_table.get("interval")?;
535
            let color_val: Value = block_table.get("color")?;
536
            let underline: bool = block_table.get("underline").unwrap_or(false);
537
            let arg: Option<Value> = block_table.get("__arg").ok();
538
539
            let cmd = match block_type.as_str() {
540
                "DateTime" => {
541
                    let fmt = arg
542
                        .and_then(|v| {
543
                            if let Value::String(s) = v {
544
                                s.to_str().ok().map(|s| s.to_string())
545
                            } else {
546
                                None
547
                            }
548
                        })
549
                        .ok_or_else(|| {
550
                            mlua::Error::RuntimeError("DateTime block missing format".into())
551
                        })?;
552
                    BlockCommand::DateTime(fmt)
553
                }
554
                "Shell" => {
555
                    let cmd_str = arg
556
                        .and_then(|v| {
557
                            if let Value::String(s) = v {
558
                                s.to_str().ok().map(|s| s.to_string())
559
                            } else {
560
                                None
561
                            }
562
                        })
563
                        .ok_or_else(|| {
564
                            mlua::Error::RuntimeError("Shell block missing command".into())
565
                        })?;
566
                    BlockCommand::Shell(cmd_str)
567
                }
568
                "Ram" => BlockCommand::Ram,
569
                "Static" => {
570
                    let text = arg
571
                        .and_then(|v| {
572
                            if let Value::String(s) = v {
573
                                s.to_str().ok().map(|s| s.to_string())
574
                            } else {
575
                                None
576
                            }
577
                        })
578
                        .unwrap_or_default();
579
                    BlockCommand::Static(text)
580
                }
581
                "Battery" => {
582
                    let formats = arg
583
                        .and_then(|v| {
584
                            if let Value::Table(t) = v {
585
                                Some(t)
586
                            } else {
587
                                None
588
                            }
589
                        })
590
                        .ok_or_else(|| {
591
                            mlua::Error::RuntimeError("Battery block missing formats".into())
592
                        })?;
593
594
                    let charging: String = formats.get("charging")?;
595
                    let discharging: String = formats.get("discharging")?;
596
                    let full: String = formats.get("full")?;
597
598
                    BlockCommand::Battery {
599
                        format_charging: charging,
600
                        format_discharging: discharging,
601
                        format_full: full,
602
                    }
603
                }
604
                _ => {
605
                    return Err(mlua::Error::RuntimeError(format!(
606
                        "Unknown block type '{}'",
607
                        block_type
608
                    )));
609
                }
610
            };
611
612
            let color_u32 = parse_color_value(color_val)?;
613
614
            let block = crate::bar::BlockConfig {
615
                format,
616
                command: cmd,
617
                interval_secs: interval,
618
                color: color_u32,
619
                underline,
620
            };
621
622
            block_configs.push(block);
623
        }
624
625
        builder_clone.borrow_mut().status_blocks = block_configs;
626
        Ok(())
627
    })?;
628
629
    let builder_clone = builder.clone();
630
    let set_scheme_normal =
631
        lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
632
            let foreground = parse_color_value(fg)?;
633
            let background = parse_color_value(bg)?;
634
            let underline = parse_color_value(ul)?;
635
636
            builder_clone.borrow_mut().scheme_normal = ColorScheme {
637
                foreground,
638
                background,
639
                underline,
640
            };
641
            Ok(())
642
        })?;
643
644
    let builder_clone = builder.clone();
645
    let set_scheme_occupied =
646
        lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
647
            let foreground = parse_color_value(fg)?;
648
            let background = parse_color_value(bg)?;
649
            let underline = parse_color_value(ul)?;
650
651
            builder_clone.borrow_mut().scheme_occupied = ColorScheme {
652
                foreground,
653
                background,
654
                underline,
655
            };
656
            Ok(())
657
        })?;
658
659
    let builder_clone = builder.clone();
660
    let set_scheme_selected =
661
        lua.create_function(move |_, (fg, bg, ul): (Value, Value, Value)| {
662
            let foreground = parse_color_value(fg)?;
663
            let background = parse_color_value(bg)?;
664
            let underline = parse_color_value(ul)?;
665
666
            builder_clone.borrow_mut().scheme_selected = ColorScheme {
667
                foreground,
668
                background,
669
                underline,
670
            };
671
            Ok(())
672
        })?;
673
674
    bar_table.set("set_font", set_font)?;
675
    bar_table.set("block", block_table)?;
676
    bar_table.set("add_block", add_block)?; // Deprecated, for backwards compatibility
677
    bar_table.set("set_blocks", set_blocks)?;
678
    bar_table.set("set_scheme_normal", set_scheme_normal)?;
679
    bar_table.set("set_scheme_occupied", set_scheme_occupied)?;
680
    bar_table.set("set_scheme_selected", set_scheme_selected)?;
681
    parent.set("bar", bar_table)?;
682
    Ok(())
683
}
684
685
fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<(), ConfigError> {
686
    let builder_clone = builder.clone();
687
    let set_terminal = lua.create_function(move |_, term: String| {
688
        builder_clone.borrow_mut().terminal = term;
689
        Ok(())
690
    })?;
691
692
    let builder_clone = builder.clone();
693
    let set_modkey = lua.create_function(move |_, modkey_str: String| {
694
        let modkey = parse_modkey_string(&modkey_str)
695
            .map_err(|e| mlua::Error::RuntimeError(format!("{}", e)))?;
696
        builder_clone.borrow_mut().modkey = modkey;
697
        Ok(())
698
    })?;
699
700
    let builder_clone = builder.clone();
701
    let set_tags = lua.create_function(move |_, tags: Vec<String>| {
702
        builder_clone.borrow_mut().tags = tags;
703
        Ok(())
704
    })?;
705
706
    let quit = lua.create_function(|lua, ()| create_action_table(lua, "Quit", Value::Nil))?;
707
708
    let restart = lua.create_function(|lua, ()| create_action_table(lua, "Restart", Value::Nil))?;
709
710
    let toggle_gaps =
711
        lua.create_function(|lua, ()| create_action_table(lua, "ToggleGaps", Value::Nil))?;
712
713
    let set_master_factor = lua.create_function(|lua, delta: i32| {
714
        create_action_table(lua, "SetMasterFactor", Value::Integer(delta as i64))
715
    })?;
716
717
    let inc_num_master = lua.create_function(|lua, delta: i32| {
718
        create_action_table(lua, "IncNumMaster", Value::Integer(delta as i64))
719
    })?;
720
721
    let show_keybinds =
722
        lua.create_function(|lua, ()| create_action_table(lua, "ShowKeybindOverlay", Value::Nil))?;
723
724
    let focus_monitor = lua.create_function(|lua, idx: i32| {
725
        create_action_table(lua, "FocusMonitor", Value::Integer(idx as i64))
726
    })?;
727
728
    let builder_clone = builder.clone();
729
    let set_layout_symbol = lua.create_function(move |_, (name, symbol): (String, String)| {
730
        builder_clone
731
            .borrow_mut()
732
            .layout_symbols
733
            .push(crate::LayoutSymbolOverride { name, symbol });
734
        Ok(())
735
    })?;
736
737
    let builder_clone = builder.clone();
738
    let autostart = lua.create_function(move |_, cmd: String| {
739
        builder_clone.borrow_mut().autostart.push(cmd);
740
        Ok(())
741
    })?;
742
743
    let builder_clone = builder.clone();
744
    let auto_tile = lua.create_function(move |_, enabled: bool| {
745
        builder_clone.borrow_mut().auto_tile = enabled;
746
        Ok(())
747
    })?;
748
749
    parent.set("set_terminal", set_terminal)?;
750
    parent.set("set_modkey", set_modkey)?;
751
    parent.set("set_tags", set_tags)?;
752
    parent.set("set_layout_symbol", set_layout_symbol)?;
753
    parent.set("autostart", autostart)?;
754
    parent.set("quit", quit)?;
755
    parent.set("restart", restart)?;
756
    parent.set("toggle_gaps", toggle_gaps)?;
757
    parent.set("set_master_factor", set_master_factor)?;
758
    parent.set("inc_num_master", inc_num_master)?;
759
    parent.set("show_keybinds", show_keybinds)?;
760
    parent.set("focus_monitor", focus_monitor)?;
761
    parent.set("auto_tile", auto_tile)?;
762
    Ok(())
763
}
764
765
fn parse_modifiers_value(_lua: &Lua, value: Value) -> mlua::Result<Vec<KeyButMask>> {
766
    match value {
767
        Value::Table(t) => {
768
            let mut mods = Vec::new();
769
            for i in 1..=t.len()? {
770
                let mod_str: String = t.get(i)?;
771
                let mask = parse_modkey_string(&mod_str)
772
                    .map_err(|e| mlua::Error::RuntimeError(format!("oxwm.key.bind: invalid modifier - {}", e)))?;
773
                mods.push(mask);
774
            }
775
            Ok(mods)
776
        }
777
        Value::String(s) => {
778
            let s_str = s.to_str()?;
779
            let mask = parse_modkey_string(&s_str)
780
                .map_err(|e| mlua::Error::RuntimeError(format!("oxwm.key.bind: invalid modifier - {}", e)))?;
781
            Ok(vec![mask])
782
        }
783
        _ => Err(mlua::Error::RuntimeError(
784
            "oxwm.key.bind: first argument must be a table of modifiers like {\"Mod4\"} or {\"Mod4\", \"Shift\"}".into(),
785
        )),
786
    }
787
}
788
789
fn parse_modkey_string(s: &str) -> Result<KeyButMask, ConfigError> {
790
    match s {
791
        "Mod1" => Ok(KeyButMask::MOD1),
792
        "Mod2" => Ok(KeyButMask::MOD2),
793
        "Mod3" => Ok(KeyButMask::MOD3),
794
        "Mod4" => Ok(KeyButMask::MOD4),
795
        "Mod5" => Ok(KeyButMask::MOD5),
796
        "Shift" => Ok(KeyButMask::SHIFT),
797
        "Control" => Ok(KeyButMask::CONTROL),
798
        _ => Err(ConfigError::InvalidModkey(format!(
799
            "'{}' is not a valid modifier. Use one of: Mod1, Mod4, Shift, Control",
800
            s
801
        ))),
802
    }
803
}
804
805
fn parse_keysym(key: &str) -> mlua::Result<Keysym> {
806
    keysyms::keysym_from_str(key)
807
        .ok_or_else(|| mlua::Error::RuntimeError(format!("unknown key '{}'. valid keys include: Return, Space, A-Z, 0-9, F1-F12, Left, Right, Up, Down, etc. check oxwm.lua type definitions for the complete list", key)))
808
}
809
810
fn parse_action_value(_lua: &Lua, value: Value) -> mlua::Result<(KeyAction, Arg)> {
811
    match value {
812
        Value::Function(_) => {
813
            Err(mlua::Error::RuntimeError(
814
                "action must be a function call, not a function reference. did you forget ()? example: oxwm.spawn('st') not oxwm.spawn".into()
815
            ))
816
        }
817
        Value::Table(t) => {
818
            if let Ok(action_name) = t.get::<String>("__action") {
819
                let action = string_to_action(&action_name)?;
820
                let arg = if let Ok(arg_val) = t.get::<Value>("__arg") {
821
                    value_to_arg(arg_val)?
822
                } else {
823
                    Arg::None
824
                };
825
                return Ok((action, arg));
826
            }
827
828
            Err(mlua::Error::RuntimeError(
829
                "action must be a table returned by oxwm functions like oxwm.spawn(), oxwm.client.kill(), oxwm.quit(), etc.".into(),
830
            ))
831
        }
832
        _ => Err(mlua::Error::RuntimeError(
833
            "action must be a table returned by oxwm functions like oxwm.spawn(), oxwm.client.kill(), oxwm.quit(), etc.".into(),
834
        )),
835
    }
836
}
837
838
fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
839
    match s {
840
        "Spawn" => Ok(KeyAction::Spawn),
841
        "SpawnTerminal" => Ok(KeyAction::SpawnTerminal),
842
        "KillClient" => Ok(KeyAction::KillClient),
843
        "FocusStack" => Ok(KeyAction::FocusStack),
844
        "MoveStack" => Ok(KeyAction::MoveStack),
845
        "Quit" => Ok(KeyAction::Quit),
846
        "Restart" => Ok(KeyAction::Restart),
847
        "ViewTag" => Ok(KeyAction::ViewTag),
848
        "ToggleView" => Ok(KeyAction::ToggleView),
849
        "MoveToTag" => Ok(KeyAction::MoveToTag),
850
        "ToggleTag" => Ok(KeyAction::ToggleTag),
851
        "ToggleGaps" => Ok(KeyAction::ToggleGaps),
852
        "SetMasterFactor" => Ok(KeyAction::SetMasterFactor),
853
        "IncNumMaster" => Ok(KeyAction::IncNumMaster),
854
        "ToggleFullScreen" => Ok(KeyAction::ToggleFullScreen),
855
        "ToggleFloating" => Ok(KeyAction::ToggleFloating),
856
        "ChangeLayout" => Ok(KeyAction::ChangeLayout),
857
        "CycleLayout" => Ok(KeyAction::CycleLayout),
858
        "FocusMonitor" => Ok(KeyAction::FocusMonitor),
859
        "TagMonitor" => Ok(KeyAction::TagMonitor),
860
        "ShowKeybindOverlay" => Ok(KeyAction::ShowKeybindOverlay),
861
        _ => Err(mlua::Error::RuntimeError(format!(
862
            "unknown action '{}'. this is an internal error, please report it",
863
            s
864
        ))),
865
    }
866
}
867
868
fn value_to_arg(value: Value) -> mlua::Result<Arg> {
869
    match value {
870
        Value::Nil => Ok(Arg::None),
871
        Value::String(s) => Ok(Arg::Str(s.to_str()?.to_string())),
872
        Value::Integer(i) => Ok(Arg::Int(i as i32)),
873
        Value::Number(n) => Ok(Arg::Int(n as i32)),
874
        Value::Table(t) => {
875
            let mut arr = Vec::new();
876
            for i in 1..=t.len()? {
877
                let item: String = t.get(i)?;
878
                arr.push(item);
879
            }
880
            Ok(Arg::Array(arr))
881
        }
882
        _ => Ok(Arg::None),
883
    }
884
}
885
886
fn create_action_table(lua: &Lua, action_name: &str, arg: Value) -> mlua::Result<Table> {
887
    let table = lua.create_table()?;
888
    table.set("__action", action_name)?;
889
    table.set("__arg", arg)?;
890
    Ok(table)
891
}
892
893
fn parse_color_value(value: Value) -> mlua::Result<u32> {
894
    match value {
895
        Value::Integer(i) => Ok(i as u32),
896
        Value::Number(n) => Ok(n as u32),
897
        Value::String(s) => {
898
            let s = s.to_str()?;
899
            if s.starts_with('#') {
900
                u32::from_str_radix(&s[1..], 16).map_err(|e| {
901
                    mlua::Error::RuntimeError(format!(
902
                        "invalid hex color '{}': {}. use format like #ff0000 or 0xff0000",
903
                        s, e
904
                    ))
905
                })
906
            } else if s.starts_with("0x") {
907
                u32::from_str_radix(&s[2..], 16).map_err(|e| {
908
                    mlua::Error::RuntimeError(format!(
909
                        "invalid hex color '{}': {}. use format like 0xff0000 or #ff0000",
910
                        s, e
911
                    ))
912
                })
913
            } else {
914
                s.parse::<u32>().map_err(|e| {
915
                    mlua::Error::RuntimeError(format!(
916
                        "invalid color '{}': {}. use hex format like 0xff0000 or #ff0000",
917
                        s, e
918
                    ))
919
                })
920
            }
921
        }
922
        _ => Err(mlua::Error::RuntimeError(
923
            "color must be a number (0xff0000) or string ('#ff0000' or '0xff0000')".into(),
924
        )),
925
    }
926
}
927
928
fn create_block_config(
929
    lua: &Lua,
930
    config: Table,
931
    block_type: &str,
932
    arg: Option<Value>,
933
) -> mlua::Result<Table> {
934
    let table = lua.create_table()?;
935
    table.set("__block_type", block_type)?;
936
937
    let format: String = config.get("format").unwrap_or_default();
938
    let interval: u64 = config.get("interval")?;
939
    let color: Value = config.get("color")?;
940
    let underline: bool = config.get("underline").unwrap_or(false);
941
942
    table.set("format", format)?;
943
    table.set("interval", interval)?;
944
    table.set("color", color)?;
945
    table.set("underline", underline)?;
946
947
    if let Some(arg_val) = arg {
948
        table.set("__arg", arg_val)?;
949
    }
950
951
    Ok(table)
952
}