| 1 |
use crate::bar::{BlockCommand, BlockConfig};
|
| 2 |
use crate::errors::ConfigError;
|
| 3 |
use crate::keyboard::handlers::{KeyBinding, KeyPress};
|
| 4 |
use crate::keyboard::keysyms;
|
| 5 |
use crate::keyboard::{Arg, KeyAction};
|
| 6 |
use crate::keyboard::keysyms::Keysym;
|
| 7 |
use serde::Deserialize;
|
| 8 |
use std::collections::HashMap;
|
| 9 |
use x11rb::protocol::xproto::KeyButMask;
|
| 10 |
|
| 11 |
#[derive(Debug, Deserialize)]
|
| 12 |
pub enum ModKey {
|
| 13 |
Mod,
|
| 14 |
Mod1,
|
| 15 |
Mod2,
|
| 16 |
Mod3,
|
| 17 |
Mod4,
|
| 18 |
Mod5,
|
| 19 |
Shift,
|
| 20 |
Control,
|
| 21 |
}
|
| 22 |
|
| 23 |
impl ModKey {
|
| 24 |
fn to_keybut_mask(&self) -> KeyButMask {
|
| 25 |
match self {
|
| 26 |
ModKey::Mod => panic!("ModKey::Mod should be replaced during config parsing"),
|
| 27 |
ModKey::Mod1 => KeyButMask::MOD1,
|
| 28 |
ModKey::Mod2 => KeyButMask::MOD2,
|
| 29 |
ModKey::Mod3 => KeyButMask::MOD3,
|
| 30 |
ModKey::Mod4 => KeyButMask::MOD4,
|
| 31 |
ModKey::Mod5 => KeyButMask::MOD5,
|
| 32 |
ModKey::Shift => KeyButMask::SHIFT,
|
| 33 |
ModKey::Control => KeyButMask::CONTROL,
|
| 34 |
}
|
| 35 |
}
|
| 36 |
}
|
| 37 |
|
| 38 |
#[rustfmt::skip]
|
| 39 |
#[derive(Debug, Deserialize)]
|
| 40 |
pub enum KeyData {
|
| 41 |
Return,
|
| 42 |
Q,
|
| 43 |
Escape,
|
| 44 |
Space,
|
| 45 |
Tab,
|
| 46 |
Backspace,
|
| 47 |
Delete,
|
| 48 |
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
|
| 49 |
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, R, S, T, U, V, W, X, Y, Z,
|
| 50 |
Key0,
|
| 51 |
Key1,
|
| 52 |
Key2,
|
| 53 |
Key3,
|
| 54 |
Key4,
|
| 55 |
Key5,
|
| 56 |
Key6,
|
| 57 |
Key7,
|
| 58 |
Key8,
|
| 59 |
Key9,
|
| 60 |
Left,
|
| 61 |
Right,
|
| 62 |
Up,
|
| 63 |
Down,
|
| 64 |
Home,
|
| 65 |
End,
|
| 66 |
PageUp,
|
| 67 |
PageDown,
|
| 68 |
Insert,
|
| 69 |
Minus,
|
| 70 |
Equal,
|
| 71 |
BracketLeft,
|
| 72 |
BracketRight,
|
| 73 |
Semicolon,
|
| 74 |
Apostrophe,
|
| 75 |
Grave,
|
| 76 |
Backslash,
|
| 77 |
Comma,
|
| 78 |
Period,
|
| 79 |
Slash,
|
| 80 |
AudioRaiseVolume,
|
| 81 |
AudioLowerVolume,
|
| 82 |
AudioMute,
|
| 83 |
MonBrightnessUp,
|
| 84 |
MonBrightnessDown,
|
| 85 |
}
|
| 86 |
|
| 87 |
impl KeyData {
|
| 88 |
fn to_keysym(&self) -> Keysym {
|
| 89 |
match self {
|
| 90 |
KeyData::Return => keysyms::XK_RETURN,
|
| 91 |
KeyData::Q => keysyms::XK_Q,
|
| 92 |
KeyData::Escape => keysyms::XK_ESCAPE,
|
| 93 |
KeyData::Space => keysyms::XK_SPACE,
|
| 94 |
KeyData::Tab => keysyms::XK_TAB,
|
| 95 |
KeyData::Backspace => keysyms::XK_BACKSPACE,
|
| 96 |
KeyData::Delete => keysyms::XK_DELETE,
|
| 97 |
KeyData::F1 => keysyms::XK_F1,
|
| 98 |
KeyData::F2 => keysyms::XK_F2,
|
| 99 |
KeyData::F3 => keysyms::XK_F3,
|
| 100 |
KeyData::F4 => keysyms::XK_F4,
|
| 101 |
KeyData::F5 => keysyms::XK_F5,
|
| 102 |
KeyData::F6 => keysyms::XK_F6,
|
| 103 |
KeyData::F7 => keysyms::XK_F7,
|
| 104 |
KeyData::F8 => keysyms::XK_F8,
|
| 105 |
KeyData::F9 => keysyms::XK_F9,
|
| 106 |
KeyData::F10 => keysyms::XK_F10,
|
| 107 |
KeyData::F11 => keysyms::XK_F11,
|
| 108 |
KeyData::F12 => keysyms::XK_F12,
|
| 109 |
KeyData::A => keysyms::XK_A,
|
| 110 |
KeyData::B => keysyms::XK_B,
|
| 111 |
KeyData::C => keysyms::XK_C,
|
| 112 |
KeyData::D => keysyms::XK_D,
|
| 113 |
KeyData::E => keysyms::XK_E,
|
| 114 |
KeyData::F => keysyms::XK_F,
|
| 115 |
KeyData::G => keysyms::XK_G,
|
| 116 |
KeyData::H => keysyms::XK_H,
|
| 117 |
KeyData::I => keysyms::XK_I,
|
| 118 |
KeyData::J => keysyms::XK_J,
|
| 119 |
KeyData::K => keysyms::XK_K,
|
| 120 |
KeyData::L => keysyms::XK_L,
|
| 121 |
KeyData::M => keysyms::XK_M,
|
| 122 |
KeyData::N => keysyms::XK_N,
|
| 123 |
KeyData::O => keysyms::XK_O,
|
| 124 |
KeyData::P => keysyms::XK_P,
|
| 125 |
KeyData::R => keysyms::XK_R,
|
| 126 |
KeyData::S => keysyms::XK_S,
|
| 127 |
KeyData::T => keysyms::XK_T,
|
| 128 |
KeyData::U => keysyms::XK_U,
|
| 129 |
KeyData::V => keysyms::XK_V,
|
| 130 |
KeyData::W => keysyms::XK_W,
|
| 131 |
KeyData::X => keysyms::XK_X,
|
| 132 |
KeyData::Y => keysyms::XK_Y,
|
| 133 |
KeyData::Z => keysyms::XK_Z,
|
| 134 |
KeyData::Key0 => keysyms::XK_0,
|
| 135 |
KeyData::Key1 => keysyms::XK_1,
|
| 136 |
KeyData::Key2 => keysyms::XK_2,
|
| 137 |
KeyData::Key3 => keysyms::XK_3,
|
| 138 |
KeyData::Key4 => keysyms::XK_4,
|
| 139 |
KeyData::Key5 => keysyms::XK_5,
|
| 140 |
KeyData::Key6 => keysyms::XK_6,
|
| 141 |
KeyData::Key7 => keysyms::XK_7,
|
| 142 |
KeyData::Key8 => keysyms::XK_8,
|
| 143 |
KeyData::Key9 => keysyms::XK_9,
|
| 144 |
KeyData::Left => keysyms::XK_LEFT,
|
| 145 |
KeyData::Right => keysyms::XK_RIGHT,
|
| 146 |
KeyData::Up => keysyms::XK_UP,
|
| 147 |
KeyData::Down => keysyms::XK_DOWN,
|
| 148 |
KeyData::Home => keysyms::XK_HOME,
|
| 149 |
KeyData::End => keysyms::XK_END,
|
| 150 |
KeyData::PageUp => keysyms::XK_PAGE_UP,
|
| 151 |
KeyData::PageDown => keysyms::XK_PAGE_DOWN,
|
| 152 |
KeyData::Insert => keysyms::XK_INSERT,
|
| 153 |
KeyData::Minus => keysyms::XK_MINUS,
|
| 154 |
KeyData::Equal => keysyms::XK_EQUAL,
|
| 155 |
KeyData::BracketLeft => keysyms::XK_LEFT_BRACKET,
|
| 156 |
KeyData::BracketRight => keysyms::XK_RIGHT_BRACKET,
|
| 157 |
KeyData::Semicolon => keysyms::XK_SEMICOLON,
|
| 158 |
KeyData::Apostrophe => keysyms::XK_APOSTROPHE,
|
| 159 |
KeyData::Grave => keysyms::XK_GRAVE,
|
| 160 |
KeyData::Backslash => keysyms::XK_BACKSLASH,
|
| 161 |
KeyData::Comma => keysyms::XK_COMMA,
|
| 162 |
KeyData::Period => keysyms::XK_PERIOD,
|
| 163 |
KeyData::Slash => keysyms::XK_SLASH,
|
| 164 |
KeyData::AudioRaiseVolume => keysyms::XF86_AUDIO_RAISE_VOLUME,
|
| 165 |
KeyData::AudioLowerVolume => keysyms::XF86_AUDIO_LOWER_VOLUME,
|
| 166 |
KeyData::AudioMute => keysyms::XF86_AUDIO_MUTE,
|
| 167 |
KeyData::MonBrightnessUp => keysyms::XF86_MON_BRIGHTNESS_UP,
|
| 168 |
KeyData::MonBrightnessDown => keysyms::XF86_MON_BRIGHTNESS_DOWN,
|
| 169 |
}
|
| 170 |
}
|
| 171 |
|
| 172 |
}
|
| 173 |
|
| 174 |
fn preprocess_variables(input: &str) -> Result<String, ConfigError> {
|
| 175 |
let mut variables: HashMap<String, String> = HashMap::new();
|
| 176 |
let mut result = String::new();
|
| 177 |
|
| 178 |
for line in input.lines() {
|
| 179 |
let trimmed = line.trim();
|
| 180 |
|
| 181 |
if trimmed.starts_with("#DEFINE") {
|
| 182 |
let rest = trimmed.strip_prefix("#DEFINE").unwrap().trim();
|
| 183 |
|
| 184 |
if let Some(eq_pos) = rest.find('=') {
|
| 185 |
let var_name = rest[..eq_pos].trim();
|
| 186 |
let value = rest[eq_pos + 1..].trim().trim_end_matches(',');
|
| 187 |
|
| 188 |
if !var_name.starts_with('$') {
|
| 189 |
return Err(ConfigError::InvalidVariableName(var_name.to_string()));
|
| 190 |
}
|
| 191 |
|
| 192 |
variables.insert(var_name.to_string(), value.to_string());
|
| 193 |
} else {
|
| 194 |
return Err(ConfigError::InvalidDefine(trimmed.to_string()));
|
| 195 |
}
|
| 196 |
|
| 197 |
result.push('\n');
|
| 198 |
} else {
|
| 199 |
let mut processed_line = line.to_string();
|
| 200 |
for (var_name, value) in &variables {
|
| 201 |
processed_line = processed_line.replace(var_name, value);
|
| 202 |
}
|
| 203 |
result.push_str(&processed_line);
|
| 204 |
result.push('\n');
|
| 205 |
}
|
| 206 |
}
|
| 207 |
|
| 208 |
for line in result.lines() {
|
| 209 |
if let Some(var_start) = line.find('$') {
|
| 210 |
let rest = &line[var_start..];
|
| 211 |
let var_end = rest[1..]
|
| 212 |
.find(|c: char| !c.is_alphanumeric() && c != '_')
|
| 213 |
.unwrap_or(rest.len() - 1)
|
| 214 |
+ 1;
|
| 215 |
let undefined_var = &rest[..var_end];
|
| 216 |
return Err(ConfigError::UndefinedVariable(undefined_var.to_string()));
|
| 217 |
}
|
| 218 |
}
|
| 219 |
Ok(result)
|
| 220 |
}
|
| 221 |
|
| 222 |
pub fn parse_config(input: &str) -> Result<crate::Config, ConfigError> {
|
| 223 |
let preprocessed = preprocess_variables(input)?;
|
| 224 |
let config_data: ConfigData = ron::from_str(&preprocessed)?;
|
| 225 |
config_data_to_config(config_data)
|
| 226 |
}
|
| 227 |
|
| 228 |
#[derive(Debug, Deserialize)]
|
| 229 |
struct LayoutSymbolOverrideData {
|
| 230 |
name: String,
|
| 231 |
symbol: String,
|
| 232 |
}
|
| 233 |
|
| 234 |
#[derive(Debug, Deserialize)]
|
| 235 |
struct ConfigData {
|
| 236 |
border_width: u32,
|
| 237 |
border_focused: u32,
|
| 238 |
border_unfocused: u32,
|
| 239 |
font: String,
|
| 240 |
|
| 241 |
gaps_enabled: bool,
|
| 242 |
gap_inner_horizontal: u32,
|
| 243 |
gap_inner_vertical: u32,
|
| 244 |
gap_outer_horizontal: u32,
|
| 245 |
gap_outer_vertical: u32,
|
| 246 |
|
| 247 |
terminal: String,
|
| 248 |
modkey: ModKey,
|
| 249 |
|
| 250 |
tags: Vec<String>,
|
| 251 |
#[serde(default)]
|
| 252 |
layout_symbols: Vec<LayoutSymbolOverrideData>,
|
| 253 |
keybindings: Vec<KeybindingData>,
|
| 254 |
status_blocks: Vec<StatusBlockData>,
|
| 255 |
|
| 256 |
scheme_normal: ColorSchemeData,
|
| 257 |
scheme_occupied: ColorSchemeData,
|
| 258 |
scheme_selected: ColorSchemeData,
|
| 259 |
|
| 260 |
#[serde(default)]
|
| 261 |
autostart: Vec<String>,
|
| 262 |
}
|
| 263 |
|
| 264 |
#[derive(Debug, Deserialize)]
|
| 265 |
struct KeybindingData {
|
| 266 |
#[serde(default)]
|
| 267 |
keys: Option<Vec<KeyPressData>>,
|
| 268 |
#[serde(default)]
|
| 269 |
modifiers: Option<Vec<ModKey>>,
|
| 270 |
#[serde(default)]
|
| 271 |
key: Option<KeyData>,
|
| 272 |
action: KeyAction,
|
| 273 |
#[serde(default)]
|
| 274 |
arg: ArgData,
|
| 275 |
}
|
| 276 |
|
| 277 |
#[derive(Debug, Deserialize)]
|
| 278 |
struct KeyPressData {
|
| 279 |
modifiers: Vec<ModKey>,
|
| 280 |
key: KeyData,
|
| 281 |
}
|
| 282 |
|
| 283 |
#[derive(Debug, Deserialize)]
|
| 284 |
#[serde(untagged)]
|
| 285 |
enum ArgData {
|
| 286 |
None,
|
| 287 |
String(String),
|
| 288 |
Int(i32),
|
| 289 |
Array(Vec<String>),
|
| 290 |
}
|
| 291 |
|
| 292 |
impl Default for ArgData {
|
| 293 |
fn default() -> Self {
|
| 294 |
ArgData::None
|
| 295 |
}
|
| 296 |
}
|
| 297 |
|
| 298 |
#[derive(Debug, Deserialize)]
|
| 299 |
struct StatusBlockData {
|
| 300 |
format: String,
|
| 301 |
command: String,
|
| 302 |
#[serde(default)]
|
| 303 |
command_arg: Option<String>,
|
| 304 |
#[serde(default)]
|
| 305 |
battery_formats: Option<BatteryFormats>,
|
| 306 |
interval_secs: u64,
|
| 307 |
color: u32,
|
| 308 |
underline: bool,
|
| 309 |
}
|
| 310 |
|
| 311 |
#[derive(Debug, Deserialize)]
|
| 312 |
struct BatteryFormats {
|
| 313 |
charging: String,
|
| 314 |
discharging: String,
|
| 315 |
full: String,
|
| 316 |
}
|
| 317 |
|
| 318 |
#[derive(Debug, Deserialize)]
|
| 319 |
struct ColorSchemeData {
|
| 320 |
foreground: u32,
|
| 321 |
background: u32,
|
| 322 |
underline: u32,
|
| 323 |
}
|
| 324 |
|
| 325 |
fn config_data_to_config(data: ConfigData) -> Result<crate::Config, ConfigError> {
|
| 326 |
let modkey = data.modkey.to_keybut_mask();
|
| 327 |
|
| 328 |
let mut keybindings = Vec::new();
|
| 329 |
for kb_data in data.keybindings {
|
| 330 |
let keys = if let Some(keys_data) = kb_data.keys {
|
| 331 |
keys_data
|
| 332 |
.into_iter()
|
| 333 |
.map(|kp| {
|
| 334 |
let modifiers = kp
|
| 335 |
.modifiers
|
| 336 |
.iter()
|
| 337 |
.map(|m| match m {
|
| 338 |
ModKey::Mod => modkey,
|
| 339 |
_ => m.to_keybut_mask(),
|
| 340 |
})
|
| 341 |
.collect();
|
| 342 |
|
| 343 |
KeyPress {
|
| 344 |
modifiers,
|
| 345 |
keysym: kp.key.to_keysym(),
|
| 346 |
}
|
| 347 |
})
|
| 348 |
.collect()
|
| 349 |
} else if let (Some(modifiers), Some(key)) = (kb_data.modifiers, kb_data.key) {
|
| 350 |
vec![KeyPress {
|
| 351 |
modifiers: modifiers
|
| 352 |
.iter()
|
| 353 |
.map(|m| match m {
|
| 354 |
ModKey::Mod => modkey,
|
| 355 |
_ => m.to_keybut_mask(),
|
| 356 |
})
|
| 357 |
.collect(),
|
| 358 |
keysym: key.to_keysym(),
|
| 359 |
}]
|
| 360 |
} else {
|
| 361 |
return Err(ConfigError::ValidationError(
|
| 362 |
"Keybinding must have either 'keys' or 'modifiers'+'key'".to_string(),
|
| 363 |
));
|
| 364 |
};
|
| 365 |
|
| 366 |
let action = kb_data.action;
|
| 367 |
let arg = arg_data_to_arg(kb_data.arg)?;
|
| 368 |
|
| 369 |
keybindings.push(KeyBinding::new(keys, action, arg));
|
| 370 |
}
|
| 371 |
|
| 372 |
let mut status_blocks = Vec::new();
|
| 373 |
for block_data in data.status_blocks {
|
| 374 |
let command = match block_data.command.as_str() {
|
| 375 |
"DateTime" => {
|
| 376 |
let fmt = block_data
|
| 377 |
.command_arg
|
| 378 |
.ok_or_else(|| ConfigError::MissingCommandArg {
|
| 379 |
command: "DateTime".to_string(),
|
| 380 |
field: "command_arg".to_string(),
|
| 381 |
})?;
|
| 382 |
BlockCommand::DateTime(fmt)
|
| 383 |
}
|
| 384 |
"Shell" => {
|
| 385 |
let cmd = block_data
|
| 386 |
.command_arg
|
| 387 |
.ok_or_else(|| ConfigError::MissingCommandArg {
|
| 388 |
command: "Shell".to_string(),
|
| 389 |
field: "command_arg".to_string(),
|
| 390 |
})?;
|
| 391 |
BlockCommand::Shell(cmd)
|
| 392 |
}
|
| 393 |
"Ram" => BlockCommand::Ram,
|
| 394 |
"Static" => {
|
| 395 |
let text = block_data.command_arg.unwrap_or_default();
|
| 396 |
BlockCommand::Static(text)
|
| 397 |
}
|
| 398 |
"Battery" => {
|
| 399 |
let formats =
|
| 400 |
block_data
|
| 401 |
.battery_formats
|
| 402 |
.ok_or_else(|| ConfigError::MissingCommandArg {
|
| 403 |
command: "Battery".to_string(),
|
| 404 |
field: "battery_formats".to_string(),
|
| 405 |
})?;
|
| 406 |
BlockCommand::Battery {
|
| 407 |
format_charging: formats.charging,
|
| 408 |
format_discharging: formats.discharging,
|
| 409 |
format_full: formats.full,
|
| 410 |
}
|
| 411 |
}
|
| 412 |
_ => return Err(ConfigError::UnknownBlockCommand(block_data.command)),
|
| 413 |
};
|
| 414 |
|
| 415 |
status_blocks.push(BlockConfig {
|
| 416 |
format: block_data.format,
|
| 417 |
command,
|
| 418 |
interval_secs: block_data.interval_secs,
|
| 419 |
color: block_data.color,
|
| 420 |
underline: block_data.underline,
|
| 421 |
});
|
| 422 |
}
|
| 423 |
|
| 424 |
let layout_symbols = data
|
| 425 |
.layout_symbols
|
| 426 |
.into_iter()
|
| 427 |
.map(|l| crate::LayoutSymbolOverride {
|
| 428 |
name: l.name,
|
| 429 |
symbol: l.symbol,
|
| 430 |
})
|
| 431 |
.collect();
|
| 432 |
|
| 433 |
Ok(crate::Config {
|
| 434 |
border_width: data.border_width,
|
| 435 |
border_focused: data.border_focused,
|
| 436 |
border_unfocused: data.border_unfocused,
|
| 437 |
font: data.font,
|
| 438 |
gaps_enabled: data.gaps_enabled,
|
| 439 |
gap_inner_horizontal: data.gap_inner_horizontal,
|
| 440 |
gap_inner_vertical: data.gap_inner_vertical,
|
| 441 |
gap_outer_horizontal: data.gap_outer_horizontal,
|
| 442 |
gap_outer_vertical: data.gap_outer_vertical,
|
| 443 |
terminal: data.terminal,
|
| 444 |
modkey,
|
| 445 |
tags: data.tags,
|
| 446 |
layout_symbols,
|
| 447 |
keybindings,
|
| 448 |
status_blocks,
|
| 449 |
scheme_normal: crate::ColorScheme {
|
| 450 |
foreground: data.scheme_normal.foreground,
|
| 451 |
background: data.scheme_normal.background,
|
| 452 |
underline: data.scheme_normal.underline,
|
| 453 |
},
|
| 454 |
scheme_occupied: crate::ColorScheme {
|
| 455 |
foreground: data.scheme_occupied.foreground,
|
| 456 |
background: data.scheme_occupied.background,
|
| 457 |
underline: data.scheme_occupied.underline,
|
| 458 |
},
|
| 459 |
scheme_selected: crate::ColorScheme {
|
| 460 |
foreground: data.scheme_selected.foreground,
|
| 461 |
background: data.scheme_selected.background,
|
| 462 |
underline: data.scheme_selected.underline,
|
| 463 |
},
|
| 464 |
autostart: data.autostart,
|
| 465 |
})
|
| 466 |
}
|
| 467 |
|
| 468 |
fn arg_data_to_arg(data: ArgData) -> Result<Arg, ConfigError> {
|
| 469 |
match data {
|
| 470 |
ArgData::None => Ok(Arg::None),
|
| 471 |
ArgData::String(s) => Ok(Arg::Str(s)),
|
| 472 |
ArgData::Int(n) => Ok(Arg::Int(n)),
|
| 473 |
ArgData::Array(arr) => Ok(Arg::Array(arr)),
|
| 474 |
}
|
| 475 |
}
|