|
| 1 | +//! Sway colors |
| 2 | +//! |
| 3 | +//! Sway colors are picked up using sway IPC for the current bar. |
| 4 | +//! |
| 5 | +//! These are the available sway colors: |
| 6 | +//! |
| 7 | +//! * `active_workspace_bg` |
| 8 | +//! * `active_workspace_border` |
| 9 | +//! * `active_workspace_text` |
| 10 | +//! * `background` |
| 11 | +//! * `binding_mode_bg` |
| 12 | +//! * `binding_mode_border` |
| 13 | +//! * `binding_mode_text` |
| 14 | +//! * `focused_background` |
| 15 | +//! * `focused_separator` |
| 16 | +//! * `focused_statusline` |
| 17 | +//! * `focused_workspace_bg` |
| 18 | +//! * `focused_workspace_border` |
| 19 | +//! * `focused_workspace_text` |
| 20 | +//! * `inactive_workspace_bg` |
| 21 | +//! * `inactive_workspace_border` |
| 22 | +//! * `inactive_workspace_text` |
| 23 | +//! * `separator` |
| 24 | +//! * `statusline` |
| 25 | +//! * `urgent_workspace_bg` |
| 26 | +//! * `urgent_workspace_border` |
| 27 | +//! * `urgent_workspace_text` |
| 28 | +//! |
| 29 | +//! You can see the current values for your bar with `swaymsg -t get_bar_config <bar-name>`. |
| 30 | +
|
| 31 | +use swayipc::ColorableBarPart; |
| 32 | + |
| 33 | +use std::sync::LazyLock; |
| 34 | + |
| 35 | +use crate::errors::*; |
| 36 | + |
| 37 | +#[cfg(not(test))] |
| 38 | +use std::{fs::read_to_string, os::unix::process::parent_id, path::Path}; |
| 39 | + |
| 40 | +#[cfg(not(test))] |
| 41 | +static COLORS: LazyLock<Result<ColorableBarPart>> = |
| 42 | + LazyLock::new(|| { |
| 43 | + let mut swayipc_connection = |
| 44 | + swayipc::Connection::new().error("Failed to open swayipc connection")?; |
| 45 | + |
| 46 | + let mut current_pid = parent_id().to_string(); |
| 47 | + while current_pid != "1" { |
| 48 | + let cmdline = read_to_string(Path::new("/proc").join(¤t_pid).join("cmdline")) |
| 49 | + .or_error(|| format!("Failed to read /proc/{current_pid}/cmdline"))?; |
| 50 | + let cmdline_parts: Vec<_> = cmdline.split('\0').collect(); |
| 51 | + if let Some(bar_id) = cmdline_parts |
| 52 | + .iter() |
| 53 | + .position(|&s| s == "-b" || s == "--bar_id") |
| 54 | + .and_then(|pos| cmdline_parts.get(pos + 1)) |
| 55 | + { |
| 56 | + return Ok(swayipc_connection |
| 57 | + .get_bar_config(bar_id) |
| 58 | + .error("Failed to get swaybar config")? |
| 59 | + .colors); |
| 60 | + } |
| 61 | + let stat = read_to_string(Path::new("/proc").join(¤t_pid).join("stat")) |
| 62 | + .or_error(|| format!("Failed to read /proc/{current_pid}/stat"))?; |
| 63 | + |
| 64 | + current_pid = |
| 65 | + stat.split(' ').nth(3).unwrap().parse().or_error(|| { |
| 66 | + format!("Failed to parse parent PID from /proc/{current_pid}/stat") |
| 67 | + })?; |
| 68 | + } |
| 69 | + Err(Error::new( |
| 70 | + "Unable to find swaybar process in parent process tree", |
| 71 | + )) |
| 72 | + }); |
| 73 | + |
| 74 | +#[cfg(test)] |
| 75 | +use tests::COLORS; |
| 76 | + |
| 77 | +pub fn get_color(name: &str) -> Result<String> { |
| 78 | + COLORS.as_ref().map_err(Clone::clone).and_then(|colors| { |
| 79 | + Ok(match name { |
| 80 | + "background" => colors.background.clone(), |
| 81 | + "statusline" => colors.statusline.clone(), |
| 82 | + "separator" => colors.separator.clone(), |
| 83 | + "focused_background" => colors.focused_background.clone(), |
| 84 | + "focused_statusline" => colors.focused_statusline.clone(), |
| 85 | + "focused_separator" => colors.focused_separator.clone(), |
| 86 | + "focused_workspace_border" => colors.focused_workspace_border.clone(), |
| 87 | + "focused_workspace_bg" => colors.focused_workspace_bg.clone(), |
| 88 | + "focused_workspace_text" => colors.focused_workspace_text.clone(), |
| 89 | + "inactive_workspace_border" => colors.inactive_workspace_border.clone(), |
| 90 | + "inactive_workspace_bg" => colors.inactive_workspace_bg.clone(), |
| 91 | + "inactive_workspace_text" => colors.inactive_workspace_text.clone(), |
| 92 | + "active_workspace_border" => colors.active_workspace_border.clone(), |
| 93 | + "active_workspace_bg" => colors.active_workspace_bg.clone(), |
| 94 | + "active_workspace_text" => colors.active_workspace_text.clone(), |
| 95 | + "urgent_workspace_border" => colors.urgent_workspace_border.clone(), |
| 96 | + "urgent_workspace_bg" => colors.urgent_workspace_bg.clone(), |
| 97 | + "urgent_workspace_text" => colors.urgent_workspace_text.clone(), |
| 98 | + "binding_mode_border" => colors.binding_mode_border.clone(), |
| 99 | + "binding_mode_bg" => colors.binding_mode_bg.clone(), |
| 100 | + "binding_mode_text" => colors.binding_mode_text.clone(), |
| 101 | + _ => { |
| 102 | + return Err(Error::new(format!( |
| 103 | + "color '{}' is not a valid sway color name", |
| 104 | + name |
| 105 | + ))); |
| 106 | + } |
| 107 | + }) |
| 108 | + }) |
| 109 | +} |
| 110 | + |
| 111 | +#[cfg(test)] |
| 112 | +mod tests { |
| 113 | + |
| 114 | + use super::*; |
| 115 | + |
| 116 | + pub(crate) static COLORS: LazyLock<Result<ColorableBarPart>> = LazyLock::new(|| { |
| 117 | + let json_str = include_str!("../../../testdata/sway_colors.json"); |
| 118 | + serde_json::from_str(json_str).error("Failed to parse sway colors JSON") |
| 119 | + }); |
| 120 | + |
| 121 | + #[test] |
| 122 | + fn test_deserializing_xcolors() { |
| 123 | + use crate::themes::color::*; |
| 124 | + let mut parsed_color = "sway:separator".parse::<Color>().unwrap(); |
| 125 | + assert_eq!( |
| 126 | + parsed_color, |
| 127 | + Color::Rgba(Rgba { |
| 128 | + r: 0x93, |
| 129 | + g: 0xa1, |
| 130 | + b: 0xa1, |
| 131 | + a: 0xff |
| 132 | + }) |
| 133 | + ); |
| 134 | + parsed_color = "sway:focused_workspace_border".parse::<Color>().unwrap(); |
| 135 | + assert_eq!( |
| 136 | + parsed_color, |
| 137 | + Color::Rgba(Rgba { |
| 138 | + r: 0x00, |
| 139 | + g: 0x88, |
| 140 | + b: 0xcc, |
| 141 | + a: 0xff, |
| 142 | + }) |
| 143 | + ); |
| 144 | + } |
| 145 | +} |
0 commit comments