oxwm

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