Skip to main content

i3status_rs/themes/color/
sway.rs

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
31use swayipc::ColorableBarPart;
32
33use std::sync::LazyLock;
34
35use crate::errors::*;
36
37#[cfg(not(test))]
38use std::{fs::read_to_string, os::unix::process::parent_id, path::Path};
39
40#[cfg(not(test))]
41static 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(&current_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(&current_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)]
75use tests::COLORS;
76
77pub 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)]
112mod 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}