hyprshell_windows_lib/switch/
open.rs1use crate::data::{SortConfig, collect_data};
2use crate::global::WindowsSwitchData;
3use crate::icon::set_icon;
4use crate::next::{find_next_client, find_next_workspace};
5use adw::gtk::gdk::Cursor;
6use adw::gtk::prelude::*;
7use adw::gtk::{Button, Fixed, Frame, Image, Label, Overflow, Overlay, pango};
8use anyhow::Context;
9use core_lib::transfer::{Direction, OpenSwitch};
10use core_lib::{ClientData, ClientId, WarnWithDetails};
11use exec_lib::{get_current_monitor, set_no_follow_mouse};
12use std::borrow::Cow;
13use tracing::{debug_span, trace};
14
15fn scale<T: Into<f64>>(value: T, scale: f64) -> i32 {
16 (value.into() / (15f64 - scale)) as i32
17}
18
19#[must_use]
20pub fn switch_already_open(data: &WindowsSwitchData) -> bool {
21 data.window.get_visible()
22}
23
24#[allow(clippy::too_many_lines)]
25pub fn open_switch(data: &mut WindowsSwitchData, config: &OpenSwitch) -> anyhow::Result<()> {
26 let _span = debug_span!("open_switch").entered();
27 set_no_follow_mouse().warn_details("Failed to set set_remain_focused");
28
29 let (clients_data, active_prev) = collect_data(&SortConfig {
30 filter_current_monitor: data.config.filter_current_monitor,
31 filter_current_workspace: data.config.filter_current_workspace,
32 filter_same_class: data.config.filter_same_class,
33 sort_recent: true,
34 })
35 .context("Failed to collect data")?;
36 let dir = if config.reverse {
37 Direction::Left
38 } else {
39 Direction::Right
40 };
41 let active = if data.config.switch_workspaces {
42 find_next_workspace(
43 &dir,
44 true,
45 &clients_data,
46 active_prev,
47 data.config.items_per_row,
48 )
49 } else {
50 find_next_client(
51 &dir,
52 true,
53 &clients_data,
54 active_prev,
55 data.config.items_per_row,
56 )
57 };
58
59 let remove_html = regex::Regex::new(r"<[^>]*>").context("Invalid regex")?;
60
61 trace!("Showing window {:?}", data.window.id());
62 data.window.set_visible(true);
63
64 let current_monitor = get_current_monitor().context("Failed to get current monitor")?;
65
66 if data.config.switch_workspaces {
67 for (wid, workspace) in &clients_data.workspaces {
68 let clients: Vec<&(ClientId, ClientData)> = {
69 let mut clients = clients_data
70 .clients
71 .iter()
72 .filter(|(_, client)| client.workspace == *wid && client.enabled)
73 .collect::<Vec<_>>();
74 clients.sort_by(|(_, a), (_, b)| {
75 if a.floating && b.floating {
77 (b.width * b.height).cmp(&(a.width * a.height))
78 } else {
79 a.floating.cmp(&b.floating)
80 }
81 });
82 clients
83 };
84 if clients.is_empty() {
85 continue;
86 }
87 let workspace_fixed = Fixed::builder()
88 .width_request(scale(workspace.width, data.config.scale))
89 .height_request(scale(workspace.height, data.config.scale))
90 .build();
91 let id_string = wid.to_string();
92 let title = if workspace.name.trim().is_empty() {
93 Cow::from(&id_string)
94 } else {
95 remove_html.replace_all(&workspace.name, "")
96 };
97 let workspace_frame = Frame::builder()
98 .label(title)
99 .label_xalign(0.5)
100 .child(&workspace_fixed)
101 .build();
102
103 let workspace_button = {
104 let workspace_overlay = Overlay::builder().child(&workspace_frame).build();
105 let button = Button::builder()
106 .child(&workspace_overlay)
107 .css_classes(["workspace", "no-hover"])
108 .build();
109 button.set_cursor(Cursor::from_name("pointer", None).as_ref());
110 if active.workspace == *wid {
111 button.add_css_class("active");
112 }
113 button
114 };
115 if workspace.name.starts_with("special:") {
116 workspace_button.add_css_class("special");
117 }
118 data.main_flow.insert(&workspace_button, -1);
119 data.workspaces.insert(*wid, workspace_button);
120
121 for (address, client) in clients {
122 if !client.enabled {
123 continue;
124 }
125
126 let client_button = {
127 let title = if client.title.trim().is_empty() {
128 &client.class
129 } else {
130 &client.title
131 };
132 let client_label = Label::builder()
133 .label(title)
134 .overflow(Overflow::Visible)
135 .margin_start(6)
136 .ellipsize(pango::EllipsizeMode::End)
137 .build();
138 let client_frame = Frame::builder()
139 .label_xalign(0.5)
140 .label_widget(&client_label)
141 .build();
142
143 let client_h_w = scale(client.height, data.config.scale)
145 .min(scale(client.width, data.config.scale));
146 if client_h_w > 70 {
147 let image = Image::builder()
148 .css_classes(["client-image"])
149 .pixel_size((f64::from(client_h_w.clamp(50, 600)) / 1.6) as i32 - 20)
150 .build();
151 if !client.enabled {
152 image.add_css_class("monochrome");
153 }
154 set_icon(&client.class, client.pid, &image);
155 client_frame.set_child(Some(&image));
156 }
157
158 let client_overlay = Overlay::builder()
159 .overflow(Overflow::Hidden)
160 .child(&client_frame)
161 .build();
162 let button = Button::builder()
163 .child(&client_overlay)
164 .css_classes(["client", "no-hover"])
165 .width_request(scale(client.width, data.config.scale))
166 .height_request(scale(client.height, data.config.scale))
167 .build();
168 button.set_cursor(Cursor::from_name("pointer", None).as_ref());
169
170 if active.client == Some(*address) {
172 button.add_css_class("active");
173 }
174 button
175 };
176 workspace_fixed.put(
177 &client_button,
178 f64::from(scale(client.x, data.config.scale)),
179 f64::from(scale(client.y, data.config.scale)),
180 );
181 data.clients.insert(*address, client_button);
182 }
183 }
184 } else {
185 for (address, client) in &clients_data.clients {
186 if !client.enabled {
187 continue;
188 }
189 let client_button = {
190 let title = if client.title.trim().is_empty() {
191 &client.class
192 } else {
193 &client.title
194 };
195 let client_label = Label::builder()
196 .label(title)
197 .overflow(Overflow::Visible)
198 .margin_start(6)
199 .ellipsize(pango::EllipsizeMode::End)
200 .build();
201 let client_frame = Frame::builder()
202 .label_xalign(0.5)
203 .label_widget(&client_label)
204 .build();
205
206 let client_h_w = scale(i16::try_from(current_monitor.height)?, data.config.scale)
208 .min(scale(
209 (f32::from(current_monitor.height) / current_monitor.scale) as i16,
210 data.config.scale,
211 ));
212 if client_h_w > 70 {
213 let image = Image::builder()
214 .css_classes(["client-image"])
215 .pixel_size((f64::from(client_h_w.clamp(50, 600)) / 1.5) as i32 - 20)
216 .build();
217 if !client.enabled {
218 image.add_css_class("monochrome");
219 }
220 set_icon(&client.class, client.pid, &image);
221 client_frame.set_child(Some(&image));
222 }
223
224 let client_overlay = Overlay::builder()
225 .overflow(Overflow::Hidden)
226 .child(&client_frame)
227 .build();
228 let button = Button::builder()
229 .child(&client_overlay)
230 .css_classes(["client", "no-hover"])
231 .width_request(scale(
232 (f32::from(current_monitor.width) / current_monitor.scale) as i16,
233 data.config.scale,
234 ))
235 .height_request(scale(
236 (f32::from(current_monitor.height) / current_monitor.scale) as i16,
237 data.config.scale,
238 ))
239 .build();
240 button.set_cursor(Cursor::from_name("pointer", None).as_ref());
241
242 if active.client == Some(*address) {
244 button.add_css_class("active");
245 }
246 button
247 };
248 data.main_flow.insert(&client_button, -1);
249 data.clients.insert(*address, client_button);
250 }
251 }
252
253 data.active = active;
254 data.hypr_data = clients_data;
255 Ok(())
256}