Skip to main content

hyprshell_windows_lib/switch/
root.rs

1use crate::data::{SortConfig, collect_data};
2use crate::next::{find_next_client, find_next_workspace};
3#[cfg(feature = "live_windows")]
4use crate::shared::refresh_captures;
5use crate::shared::{Workspaces, WorkspacesInit, WorkspacesInput};
6use core_lib::{Active, ByFirst, Direction, HyprlandData, SWITCH_NAMESPACE};
7use exec_lib::switch::{switch_client, switch_workspace};
8#[cfg(feature = "live_windows")]
9use exec_lib::wayland_capture::CaptureManager;
10use gtk4_layer_shell::{KeyboardMode, Layer, LayerShell};
11use regex::Regex;
12use relm4::adw::glib::ControlFlow;
13use relm4::adw::gtk;
14use relm4::adw::gtk::glib;
15use relm4::adw::prelude::*;
16use relm4::gtk::gdk::Key;
17use relm4::gtk::{EventControllerKey, Orientation, SelectionMode};
18use relm4::prelude::*;
19use std::time::Duration;
20use tracing::{debug, error, trace, warn};
21
22const KILL_TIMEOUT: Duration = Duration::from_millis(200);
23#[cfg(feature = "live_windows")]
24const THUMBNAIL_BURST_MS: u64 = 8;
25
26#[derive(Debug)]
27pub struct SwitchRoot {
28    general: config_lib::WindowsGeneral,
29    switch: config_lib::Switch,
30    open: bool,
31    data: SwitchData,
32    // gtk
33    window: gtk::ApplicationWindow,
34    controller: Option<gtk::EventController>,
35    /// Regex for removing HTML tags from strings
36    remove_html: Regex,
37    /// Factory for workspace mode (workspaces)
38    items: FactoryVecDeque<Workspaces>,
39    /// Factory for non-workspace mode (clients)
40    clients_only: FactoryVecDeque<crate::switch::clients::Clients>,
41
42    #[cfg(feature = "live_windows")]
43    live_thumbnails: bool,
44    #[cfg(feature = "live_windows")]
45    live_thumbnails_icons: bool,
46
47    #[cfg(feature = "live_windows")]
48    capture_manager: Option<CaptureManager>,
49    #[cfg(feature = "live_windows")]
50    timer_handle: Option<glib::SourceId>,
51    #[cfg(feature = "live_windows")]
52    thumbnail_refresh_ms: u64,
53    #[cfg(feature = "live_windows")]
54    thumbnail_burst: bool,
55}
56
57#[derive(Debug)]
58pub enum SwitchRootInput {
59    SetSwitch(config_lib::Switch),
60    SetGeneral(config_lib::WindowsGeneral),
61    OpenSwitch(Direction),
62    Switch(Direction),
63    CloseSwitch(bool),
64    CloseCurrentItem,
65    ReloadSwitch,
66    #[cfg(feature = "live_windows")]
67    RefreshThumbnails,
68}
69
70#[derive(Debug)]
71pub struct SwitchRootInit {
72    pub general: config_lib::WindowsGeneral,
73    pub switch: config_lib::Switch,
74    pub thumbnail_refresh_ms: u64,
75}
76
77#[derive(Debug)]
78pub enum SwitchRootOutput {}
79
80#[relm4::component(pub)]
81impl SimpleComponent for SwitchRoot {
82    type Init = SwitchRootInit;
83    type Input = SwitchRootInput;
84    type Output = SwitchRootOutput;
85
86    view! {
87        #[root]
88        gtk::ApplicationWindow {
89            set_css_classes: &["window"],
90            set_default_size: (100, 100),
91            match model.switch.switch_workspaces {
92                true => {
93                    #[local_ref]
94                    itemsw -> gtk::FlowBox {
95                        set_css_classes: &["monitor"],
96                        set_selection_mode: SelectionMode::None,
97                        set_orientation: Orientation::Horizontal,
98                        #[watch]
99                        set_max_children_per_line: u32::from(model.general.items_per_row),
100                        #[watch]
101                        set_min_children_per_line: u32::from(model.general.items_per_row),
102                    }
103                }
104                false => {
105                    #[local_ref]
106                    clients_only_w -> gtk::FlowBox {
107                        set_css_classes: &["monitor"],
108                        set_selection_mode: SelectionMode::None,
109                        set_orientation: Orientation::Horizontal,
110                        #[watch]
111                        set_max_children_per_line: u32::from(model.general.items_per_row),
112                        #[watch]
113                        set_min_children_per_line: u32::from(model.general.items_per_row),
114                    }
115                }
116            }
117        }
118    }
119
120    fn init(
121        init: Self::Init,
122        root: Self::Root,
123        sender: ComponentSender<Self>,
124    ) -> ComponentParts<Self> {
125        trace!("Initializing SwitchRoot");
126
127        let items: FactoryVecDeque<Workspaces> = FactoryVecDeque::builder()
128            .launch(gtk::FlowBox::default())
129            .detach();
130
131        let clients_only: FactoryVecDeque<crate::switch::clients::Clients> =
132            FactoryVecDeque::builder()
133                .launch(gtk::FlowBox::default())
134                .detach();
135
136        let model = Self {
137            general: init.general,
138            switch: init.switch,
139            open: false,
140            window: root.clone(),
141            controller: None,
142            remove_html: Regex::new(r"<[^>]*>").expect("invalid regex"),
143            data: SwitchData::default(),
144            items,
145            clients_only,
146            #[cfg(feature = "live_windows")]
147            live_thumbnails: std::env::var_os("HYPRSHELL_EXPERIMENTAL").is_some_and(|v| v == "1"),
148            #[cfg(feature = "live_windows")]
149            live_thumbnails_icons: std::env::var_os("HYPRSHELL_EXPERIMENTAL_ICONS")
150                .is_none_or(|v| v != "0"),
151            #[cfg(feature = "live_windows")]
152            capture_manager: None,
153            #[cfg(feature = "live_windows")]
154            timer_handle: None,
155            #[cfg(feature = "live_windows")]
156            thumbnail_refresh_ms: init.thumbnail_refresh_ms,
157            #[cfg(feature = "live_windows")]
158            thumbnail_burst: false,
159        };
160
161        let itemsw: gtk::FlowBox = model.items.widget().clone();
162        let clients_only_w: gtk::FlowBox = model.clients_only.widget().clone();
163        let widgets = view_output!();
164
165        let window = &root;
166        window.init_layer_shell();
167        window.set_namespace(Some(SWITCH_NAMESPACE));
168        window.set_layer(Layer::Overlay);
169        window.set_keyboard_mode(KeyboardMode::Exclusive);
170        sender
171            .input_sender()
172            .emit(SwitchRootInput::SetSwitch(model.switch.clone()));
173        ComponentParts { model, widgets }
174    }
175
176    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
177        trace!("switch::root::update: {message:?}");
178        match message {
179            SwitchRootInput::SetSwitch(switch) => {
180                self.switch = switch;
181                self.setup_keyboard_controller(&sender);
182            }
183            SwitchRootInput::SetGeneral(general) => {
184                self.general = general;
185                self.setup_keyboard_controller(&sender);
186            }
187            SwitchRootInput::OpenSwitch(direction) => {
188                if self.open {
189                    sender
190                        .input_sender()
191                        .emit(SwitchRootInput::Switch(direction));
192                } else {
193                    self.open = true;
194                    self.open_switch(direction, &sender);
195                }
196            }
197            SwitchRootInput::Switch(direction) => {
198                if self.open {
199                    self.navigate(direction);
200                } else {
201                    trace!("not open");
202                }
203            }
204            SwitchRootInput::CloseSwitch(do_switch) => {
205                if self.open {
206                    self.open = false;
207                    self.close_switch(do_switch);
208                } else {
209                    trace!("not open");
210                }
211            }
212            SwitchRootInput::CloseCurrentItem => {
213                if self.open {
214                    self.close_item();
215                } else {
216                    trace!("not open");
217                }
218                sender.input_sender().emit(SwitchRootInput::ReloadSwitch);
219            }
220            SwitchRootInput::ReloadSwitch => {
221                if self.open {
222                    self.reload_switch();
223                } else {
224                    trace!("not open");
225                }
226            }
227            #[cfg(feature = "live_windows")]
228            SwitchRootInput::RefreshThumbnails => self.refresh_thumbnails(&sender),
229        }
230    }
231}
232
233impl SwitchRoot {
234    fn setup_keyboard_controller(&mut self, sender: &ComponentSender<Self>) {
235        // TODO add a check in config check so these always succeed
236        if let Some(k) = Key::from_name(self.switch.key.to_string()) {
237            if let Some(kk) = Key::from_name(self.switch.kill_key.to_string()) {
238                let key_controller = EventControllerKey::new();
239                let sender_2 = sender.clone();
240                key_controller.connect_key_pressed(move |_, key, _, _| {
241                    trace!("Key pressed: {:?}", key);
242                    handle_key(key, k, kk, &sender_2.clone())
243                });
244                if let Some(controller) = self.controller.take() {
245                    self.window.remove_controller(&controller);
246                }
247                self.window.add_controller(key_controller);
248            } else {
249                error!("Invalid kill key name: {}", self.switch.kill_key);
250            }
251        } else {
252            error!("Invalid key name: {}", self.switch.key);
253        }
254    }
255
256    #[allow(unused_variables)]
257    fn open_switch(&mut self, direction: Direction, sender: &ComponentSender<Self>) {
258        let (hypr_data, active_prev) = match collect_data(&SortConfig {
259            filter_current_monitor: self.switch.filter_by_current_monitor,
260            filter_current_workspace: self.switch.filter_by_current_workspace,
261            filter_same_class: self.switch.filter_by_same_class,
262            sort_recent: true,
263            exclude_workspaces: if self.switch.exclude_workspaces.is_empty() {
264                None
265            } else {
266                Some(self.switch.exclude_workspaces.clone())
267            },
268        }) {
269            Ok(data) => data,
270            Err(e) => {
271                error!("Failed to collect data: {}", e);
272                return;
273            }
274        };
275
276        let active = if self.switch.switch_workspaces {
277            find_next_workspace(
278                direction,
279                true,
280                &hypr_data,
281                active_prev,
282                self.general.items_per_row,
283            )
284        } else {
285            find_next_client(
286                direction,
287                true,
288                &hypr_data,
289                active_prev,
290                self.general.items_per_row,
291            )
292        };
293        self.data = SwitchData {
294            active,
295            hypr_data: hypr_data.clone(),
296        };
297
298        trace!("Showing window {:?}", self.window.id());
299        self.window.set_visible(true);
300        self.window.grab_focus();
301
302        if self.switch.switch_workspaces {
303            self.populate_workspace_mode(&hypr_data, self.general.scale, self.data.active);
304        } else {
305            self.populate_clients_only_mode(&hypr_data, self.general.scale, self.data.active);
306        }
307
308        #[cfg(feature = "live_windows")]
309        if self.live_thumbnails {
310            self.capture_manager = CaptureManager::new().map_err(|e| error!("{e}")).ok();
311            self.thumbnail_burst = true;
312            let sender = sender.clone();
313            self.timer_handle = Some(glib::timeout_add_local(
314                Duration::from_millis(THUMBNAIL_BURST_MS),
315                move || {
316                    if sender
317                        .input_sender()
318                        .send(SwitchRootInput::RefreshThumbnails)
319                        .is_err()
320                    {
321                        warn!("Failed to send refresh thumbnails");
322                        return ControlFlow::Break;
323                    }
324                    ControlFlow::Continue
325                },
326            ));
327        }
328    }
329
330    fn populate_workspace_mode(&mut self, hypr_data: &HyprlandData, scale: f64, active: Active) {
331        let mut lock = self.items.guard();
332        lock.clear();
333
334        for (wid, workspace_data) in &hypr_data.workspaces {
335            if !workspace_data.any_client_enabled {
336                trace!("skipping workspace {} with no enabled clients", wid);
337                continue;
338            }
339            // Get clients for this workspace
340            let workspace_clients: Vec<_> = hypr_data
341                .clients
342                .iter()
343                .filter(|(_, client)| client.workspace == *wid && client.enabled)
344                .map(|(id, data)| (*id, data.clone()))
345                .collect();
346
347            let Some(monitor) = hypr_data.monitors.find_by_first(&workspace_data.monitor) else {
348                error!(
349                    "Workspace {} has invalid monitor {}",
350                    wid, workspace_data.monitor
351                );
352                continue;
353            };
354            lock.push_back(WorkspacesInit {
355                monitor_data: monitor.clone(),
356                remove_html: self.remove_html.clone(),
357                id: *wid,
358                data: workspace_data.clone(),
359                scale,
360                clients: workspace_clients,
361                #[cfg(feature = "live_windows")]
362                live_thumbnails: self.live_thumbnails,
363                #[cfg(feature = "live_windows")]
364                live_thumbnails_icons: self.live_thumbnails_icons,
365            });
366        }
367        drop(lock);
368
369        // Set active workspace
370        for (idx, item) in self.items.iter().enumerate() {
371            if item.workspace_id == active.workspace {
372                self.items.send(idx, WorkspacesInput::SetActive(true));
373                break;
374            }
375        }
376    }
377
378    fn populate_clients_only_mode(&mut self, hypr_data: &HyprlandData, scale: f64, active: Active) {
379        let mut lock = self.clients_only.guard();
380        lock.clear();
381
382        for (id, client) in &hypr_data.clients {
383            if !client.enabled {
384                continue;
385            }
386            let Some(monitor) = hypr_data.monitors.find_by_first(&client.monitor) else {
387                error!("Client {} has invalid monitor {}", id, client.monitor);
388                continue;
389            };
390            lock.push_back(crate::switch::clients::ClientsInit {
391                id: *id,
392                scale,
393                monitor_data: monitor.clone(),
394                data: client.clone(),
395                #[cfg(feature = "live_windows")]
396                live_thumbnails: self.live_thumbnails,
397            });
398        }
399        drop(lock);
400
401        // Set active client
402        if let Some(active_id) = active.client {
403            for (idx, item) in self.clients_only.iter().enumerate() {
404                if item.id == active_id {
405                    self.clients_only
406                        .send(idx, crate::switch::clients::ClientsInput::SetActive(true));
407                    break;
408                }
409            }
410        }
411    }
412
413    fn navigate(&mut self, direction: Direction) {
414        let new_active = if self.switch.switch_workspaces {
415            find_next_workspace(
416                direction,
417                true,
418                &self.data.hypr_data,
419                self.data.active,
420                self.general.items_per_row,
421            )
422        } else {
423            find_next_client(
424                direction,
425                true,
426                &self.data.hypr_data,
427                self.data.active,
428                self.general.items_per_row,
429            )
430        };
431
432        let old_active = self.data.active;
433        self.data.active = new_active;
434
435        if self.switch.switch_workspaces {
436            self.update_workspace_active(old_active, new_active);
437        } else {
438            self.update_clients_only_active(old_active, new_active);
439        }
440    }
441
442    fn update_workspace_active(&self, old_active: Active, new_active: Active) {
443        // Update workspace active state
444        if old_active.workspace != new_active.workspace {
445            for (idx, item) in self.items.iter().enumerate() {
446                if item.workspace_id == old_active.workspace {
447                    self.items.send(idx, WorkspacesInput::SetActive(false));
448                }
449                if item.workspace_id == new_active.workspace {
450                    self.items.send(idx, WorkspacesInput::SetActive(true));
451                    if let Some(cid) = new_active.client {
452                        self.items.send(idx, WorkspacesInput::SetActiveClient(cid));
453                    }
454                }
455            }
456        }
457    }
458
459    fn update_clients_only_active(&self, old_active: Active, new_active: Active) {
460        // Clear old active
461        if let Some(old_id) = old_active.client {
462            for (idx, item) in self.clients_only.iter().enumerate() {
463                if item.id == old_id {
464                    self.clients_only
465                        .send(idx, crate::switch::clients::ClientsInput::SetActive(false));
466                    break;
467                }
468            }
469        }
470
471        // Set new active
472        if let Some(new_id) = new_active.client {
473            for (idx, item) in self.clients_only.iter().enumerate() {
474                if item.id == new_id {
475                    self.clients_only
476                        .send(idx, crate::switch::clients::ClientsInput::SetActive(true));
477                    break;
478                }
479            }
480        }
481    }
482
483    fn close_switch(&mut self, do_switch: bool) {
484        trace!("Hiding window {:?}", self.window.id());
485        self.window.set_visible(false);
486
487        // Clear UI
488        {
489            let mut lock = self.items.guard();
490            lock.clear();
491        }
492        {
493            let mut lock = self.clients_only.guard();
494            lock.clear();
495        }
496
497        if do_switch {
498            if let Some(id) = self.data.active.client {
499                debug!(
500                    "Switching to client {}",
501                    self.data
502                        .hypr_data
503                        .clients
504                        .iter()
505                        .find(|(cid, _)| *cid == id)
506                        .map_or_else(|| "<Unknown>".to_string(), |(_, c)| c.title.clone())
507                );
508                // Defer execution to ensure window is hidden first
509                glib::idle_add_local(move || {
510                    if let Err(e) = switch_client(id) {
511                        warn!("Failed to switch to client {id:?}: {e}");
512                    }
513                    ControlFlow::Break
514                });
515            } else {
516                let id = self.data.active.workspace;
517                debug!(
518                    "Switching to workspace {}",
519                    self.data
520                        .hypr_data
521                        .workspaces
522                        .iter()
523                        .find(|(wid, _)| *wid == id)
524                        .map_or_else(|| "<Unknown>".to_string(), |(_, w)| w.name.clone())
525                );
526                glib::idle_add_local(move || {
527                    if let Err(e) = switch_workspace(id) {
528                        tracing::warn!("Failed to switch to workspace {id:?}: {e}");
529                    }
530                    ControlFlow::Break
531                });
532            }
533        }
534        #[cfg(feature = "live_windows")]
535        {
536            if let Some(handle) = self.timer_handle.take() {
537                handle.remove();
538            }
539            self.capture_manager = None;
540        }
541    }
542
543    fn close_item(&self) {
544        if self.switch.switch_workspaces {
545            self.kill_workspace_clients();
546        } else {
547            self.kill_active_client();
548        }
549    }
550
551    fn kill_active_client(&self) {
552        if let Some(id) = self.data.active.client
553            && let Err(e) = exec_lib::kill::kill_client_blocking(id, KILL_TIMEOUT)
554        {
555            // TODO: close on killed to let user close window themself
556            tracing::warn!("Failed to kill client {id}: {e}");
557        }
558    }
559
560    fn kill_workspace_clients(&self) {
561        let workspace_id = self.data.active.workspace;
562        debug!(
563            "Killing all clients in workspace {}",
564            self.data
565                .hypr_data
566                .workspaces
567                .iter()
568                .find(|(wid, _)| *wid == workspace_id)
569                .map_or_else(|| workspace_id.to_string(), |(_, w)| w.name.clone())
570        );
571
572        let clients_to_kill: Vec<_> = self
573            .data
574            .hypr_data
575            .clients
576            .iter()
577            .filter(|(_, client)| client.workspace == workspace_id)
578            .map(|(id, _)| *id)
579            .collect();
580
581        for client_id in clients_to_kill {
582            if let Err(e) = exec_lib::kill::kill_client_blocking(client_id, KILL_TIMEOUT) {
583                // TODO: close on killed to let user close window themself
584                tracing::warn!("Failed to kill client {client_id}: {e}");
585            }
586        }
587    }
588
589    fn reload_switch(&mut self) {
590        let (hypr_data, _) = match collect_data(&SortConfig {
591            filter_current_monitor: self.switch.filter_by_current_monitor,
592            filter_current_workspace: self.switch.filter_by_current_workspace,
593            filter_same_class: self.switch.filter_by_same_class,
594            sort_recent: true,
595            exclude_workspaces: if self.switch.exclude_workspaces.is_empty() {
596                None
597            } else {
598                Some(self.switch.exclude_workspaces.clone())
599            },
600        }) {
601            Ok(data) => data,
602            Err(e) => {
603                error!("Failed to collect data: {}", e);
604                return;
605            }
606        };
607
608        while match self.data.active {
609            Active {
610                client: Some(id), ..
611            } => hypr_data.clients.find_by_first(&id).is_none(),
612            Active { workspace: id, .. } => hypr_data.workspaces.find_by_first(&id).is_none(),
613        } {
614            self.data.active = if self.switch.switch_workspaces {
615                find_next_workspace(
616                    Direction::Right,
617                    true,
618                    &hypr_data,
619                    self.data.active,
620                    self.general.items_per_row,
621                )
622            } else {
623                find_next_client(
624                    Direction::Right,
625                    true,
626                    &hypr_data,
627                    self.data.active,
628                    self.general.items_per_row,
629                )
630            };
631        }
632
633        self.data = SwitchData {
634            active: self.data.active,
635            hypr_data: hypr_data.clone(),
636        };
637
638        if self.switch.switch_workspaces {
639            self.populate_workspace_mode(&hypr_data, self.general.scale, self.data.active);
640        } else {
641            self.populate_clients_only_mode(&hypr_data, self.general.scale, self.data.active);
642        }
643    }
644
645    #[cfg(feature = "live_windows")]
646    fn refresh_thumbnails(&mut self, sender: &ComponentSender<Self>) {
647        use relm4::adw::gdk::Display;
648        let Some(mgr) = &mut self.capture_manager else {
649            return;
650        };
651        let Some(display) = Display::default() else {
652            return;
653        };
654        let mut captures = refresh_captures(mgr, &display, !self.thumbnail_burst);
655        if self.thumbnail_burst && mgr.pending_count() == 0 {
656            self.thumbnail_burst = false;
657            // all initial thumbnails are loaded
658            // remove initial thumbnail burst timer
659            if let Some(h) = self.timer_handle.take() {
660                h.remove();
661            }
662            // start new slower timer if thumbnail_refresh_ms is set
663            if self.thumbnail_refresh_ms != 0 {
664                trace!("Switching from thumbnail_burst refresh to slow refresh");
665                let sender = sender.clone();
666                self.timer_handle = Some(glib::timeout_add_local(
667                    Duration::from_millis(self.thumbnail_refresh_ms),
668                    move || {
669                        if sender
670                            .input_sender()
671                            .send(SwitchRootInput::RefreshThumbnails)
672                            .is_err()
673                        {
674                            warn!("Failed to send refresh thumbnails");
675                            return ControlFlow::Break;
676                        }
677                        ControlFlow::Continue
678                    },
679                ));
680            } else {
681                trace!("All initial thumbnail captures loaded");
682            }
683        }
684
685        if self.switch.switch_workspaces {
686            for (client_id, texture) in captures {
687                for (idx, _) in self.items.iter().enumerate() {
688                    self.items.send(
689                        idx,
690                        WorkspacesInput::UpdateClientThumbnail(client_id, texture.clone()),
691                    );
692                }
693            }
694        } else {
695            for (idx, item) in self.clients_only.iter().enumerate() {
696                if let Some(texture) = captures.remove(&item.id) {
697                    self.clients_only.send(
698                        idx,
699                        crate::switch::clients::ClientsInput::UpdateThumbnail(texture),
700                    );
701                }
702            }
703        }
704    }
705}
706
707fn handle_key(
708    key: Key,
709    s_key: Key,
710    kill_key: Key,
711    event_sender: &ComponentSender<SwitchRoot>,
712) -> glib::Propagation {
713    match key {
714        Key::Escape => {
715            event_sender
716                .input_sender()
717                .emit(SwitchRootInput::CloseSwitch(false));
718            glib::Propagation::Stop
719        }
720        k if k == s_key || k == Key::l || k == Key::Right => {
721            event_sender
722                .input_sender()
723                .emit(SwitchRootInput::Switch(Direction::Right));
724            glib::Propagation::Stop
725        }
726        Key::ISO_Left_Tab | Key::grave | Key::dead_grave | Key::h | Key::Left => {
727            event_sender
728                .input_sender()
729                .emit(SwitchRootInput::Switch(Direction::Left));
730            glib::Propagation::Stop
731        }
732        Key::j | Key::Down => {
733            event_sender
734                .input_sender()
735                .emit(SwitchRootInput::Switch(Direction::Down));
736            glib::Propagation::Stop
737        }
738        Key::k | Key::Up => {
739            event_sender
740                .input_sender()
741                .emit(SwitchRootInput::Switch(Direction::Up));
742            glib::Propagation::Stop
743        }
744        k if k == kill_key || k == Key::Delete => {
745            event_sender
746                .input_sender()
747                .emit(SwitchRootInput::CloseCurrentItem);
748            glib::Propagation::Stop
749        }
750        _ => glib::Propagation::Proceed,
751    }
752}
753
754#[derive(Debug)]
755pub struct SwitchData {
756    pub active: Active,
757    pub hypr_data: HyprlandData,
758}
759
760impl Default for SwitchData {
761    fn default() -> Self {
762        Self {
763            active: Active {
764                client: None,
765                workspace: -1,
766                monitor: -1,
767            },
768            hypr_data: HyprlandData::default(),
769        }
770    }
771}