oxwm

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