Skip to main content

hyprshell_exec_lib/
collect.rs

1use crate::util::to_client_id;
2use anyhow::Context;
3use core_lib::{ByFirst, ClientData, ClientId, MonitorData, MonitorId, WorkspaceData, WorkspaceId};
4use hyprland::data::{Client, Clients, Monitor, Monitors, Workspace, Workspaces};
5use hyprland::prelude::*;
6use regex::Regex;
7use tracing::{debug_span, instrument, trace, warn};
8
9fn get_hypr_data() -> anyhow::Result<(Vec<Monitor>, Vec<Workspace>, Vec<Client>)> {
10    let _span = debug_span!("get_hypr_data").entered();
11    let monitors = Monitors::get().context("monitors failed")?.to_vec();
12    // sort and filter all workspaces sorted by ID
13    let workspaces = {
14        let mut workspaces = Workspaces::get()
15            .context("workspaces failed")?
16            .into_iter()
17            .filter(|w| w.id != -1) // TODO: check if still needed: ignore clients on invalid workspaces
18            .collect::<Vec<_>>();
19
20        workspaces.sort_by(|a, b| a.id.cmp(&b.id));
21        workspaces
22    };
23    let clients = Clients::get()
24        .context("clients failed")?
25        .into_iter()
26        .filter(|c| c.workspace.id != -1) // TODO: check if still needed: ignore clients on invalid workspaces
27        .collect::<Vec<_>>();
28
29    Ok((monitors, workspaces, clients))
30}
31
32#[allow(clippy::type_complexity)]
33#[instrument(level = "debug")]
34pub fn collect_hypr_data(
35    exclude_workspaces: Option<Regex>,
36) -> anyhow::Result<(
37    Vec<(ClientId, ClientData)>,
38    Vec<(WorkspaceId, WorkspaceData)>,
39    Vec<(MonitorId, MonitorData)>,
40    Option<(String, ClientId)>,
41    WorkspaceId,
42    MonitorId,
43)> {
44    let (monitors, workspaces, clients) =
45        get_hypr_data().context("loading hyprland data failed")?;
46
47    // all monitors with their data, x and y are the offset of the monitor, width and height are the size of the monitor.
48    // combined_width and combined_height are the combined size of all workspaces on the monitor and workspaces_on_monitor is the number of workspaces on the monitor
49    let mut monitor_data = {
50        let mut md: Vec<(MonitorId, MonitorData)> = Vec::with_capacity(monitors.iter().len());
51
52        for monitor in &monitors {
53            #[allow(clippy::cast_sign_loss)]
54            md.push((
55                monitor.id,
56                MonitorData {
57                    id: monitor.id,
58                    x: monitor.x,
59                    y: monitor.y,
60                    width: (f32::from(monitor.width) / monitor.scale) as u16,
61                    height: (f32::from(monitor.height) / monitor.scale) as u16,
62                    connector: monitor.name.clone(),
63                    scale: monitor.scale,
64                },
65            ));
66        }
67        md
68    };
69
70    // all workspaces with their data, x and y are the offset of the workspace
71    let mut workspace_data = {
72        let mut wd: Vec<(WorkspaceId, WorkspaceData)> = Vec::with_capacity(workspaces.len());
73
74        for (monitor_id, monitor_data) in &monitor_data {
75            workspaces
76                .iter()
77                .filter(|ws| ws.monitor_id == Some(*monitor_id))
78                .for_each(|workspace| {
79                    wd.push((
80                        workspace.id,
81                        WorkspaceData {
82                            name: workspace.name.clone(),
83                            monitor: *monitor_id,
84                            height: monitor_data.height,
85                            width: monitor_data.width,
86                            any_client_enabled: true, // gets updated later
87                        },
88                    ));
89                });
90        }
91        wd
92    };
93
94    let mut client_data = {
95        let mut cd: Vec<(ClientId, ClientData)> = Vec::with_capacity(clients.len());
96
97        for client in clients {
98            let Some(monitor) = client.monitor else {
99                continue;
100            };
101            if workspace_data.find_by_first(&client.workspace.id).is_some() {
102                cd.push((
103                    to_client_id(&client.address),
104                    ClientData {
105                        x: client.at.0,
106                        y: client.at.1,
107                        width: client.size.0,
108                        height: client.size.1,
109                        class: client.class.clone(),
110                        workspace: client.workspace.id,
111                        monitor,
112                        focus_history_id: client.focus_history_id,
113                        title: client.title.clone(),
114                        floating: client.floating,
115                        pid: client.pid,
116                        enabled: true, // gets updated later
117                    },
118                ));
119            } else {
120                warn!(
121                    "workspace {:?} not found for client {client:?}",
122                    client.workspace
123                );
124            }
125        }
126        cd
127    };
128
129    // we do this after the initial collection because then clients would complain
130    // about missing workspace
131    trace!(
132        "workspaces bevore filter by regex: {}",
133        workspace_data.len()
134    );
135    workspace_data = workspace_data
136        .into_iter()
137        .filter(|(_, ws)| {
138            exclude_workspaces
139                .as_ref()
140                .map(|reg| !reg.is_match(&ws.name))
141                .unwrap_or(true)
142        })
143        .collect();
144    trace!("workspaces after filter by regex: {}", workspace_data.len());
145    client_data = client_data
146        .into_iter()
147        .filter(|(_id, cl)| workspace_data.find_by_first(&cl.workspace).is_some())
148        .collect();
149
150    workspace_data.sort_by(|a, b| a.0.cmp(&b.0));
151    monitor_data.sort_by(|a, b| a.0.cmp(&b.0));
152
153    // is broken, reports the "normal" workspace as active when a client in special workspace is selected
154    // let active_ws = Workspace::get_active()?.id;
155    let active_ws = Workspace::get_active()
156        .map(|w| w.id)
157        .context("active workspace failed")?;
158    let active_ws = Client::get_active()
159        .context("active client failed")?
160        .map_or(active_ws, |a| a.workspace.id);
161    let active_monitor = Monitor::get_active().context("active monitor failed")?.id;
162    let active_client = Client::get_active()
163        .context("active client failed")?
164        .map(|a| (a.class.clone(), to_client_id(&a.address)));
165
166    Ok((
167        client_data,
168        workspace_data,
169        monitor_data,
170        active_client,
171        active_ws,
172        active_monitor,
173    ))
174}
175
176pub fn get_monitors() -> Vec<MonitorData> {
177    Monitors::get()
178        .map_or(vec![], HyprDataVec::to_vec)
179        .iter()
180        .map(|m| MonitorData {
181            id: m.id,
182            x: m.x,
183            y: m.y,
184            width: m.width,
185            height: m.height,
186            connector: m.name.clone(),
187            scale: m.scale,
188        })
189        .collect()
190}
191
192#[must_use]
193pub fn get_current_monitor() -> Option<MonitorData> {
194    Monitor::get_active().ok().map(|m| MonitorData {
195        id: m.id,
196        x: m.x,
197        y: m.y,
198        width: m.width,
199        height: m.height,
200        connector: m.name.clone(),
201        scale: m.scale,
202    })
203}
204
205pub fn get_client_classes() -> Vec<String> {
206    Clients::get()
207        .map_or(vec![], HyprDataVec::to_vec)
208        .iter()
209        .map(|client| client.class.clone())
210        .collect()
211}