free_launch/model/
window.rs1use niri_ipc::{
2 Action::FocusWindow, Request as NiriRequest, Timestamp, Window as NiriWindow, socket::Socket,
3};
4use readlock::Shared;
5use std::{collections::HashMap, path::Path, sync::LazyLock};
6use tracing::{error, warn};
7
8use crate::launch_entries::{
9 action_name::ActionName,
10 action_result::ActionResult,
11 launch_entry::LaunchEntry,
12 launch_entry_trait::{LaunchAction, LaunchId, Launchable},
13};
14
15pub(crate) struct Window {
16 window: NiriWindow,
17}
18
19impl Window {
20 pub(crate) fn name(&self) -> Option<String> {
21 self.window.title.clone()
22 }
23
24 pub(crate) fn id(&self) -> String {
25 format!("window-{}", self.window.id)
26 }
27
28 pub(crate) fn focus_timestamp(&self) -> Option<Timestamp> {
31 self.window.focus_timestamp
32 }
33
34 fn focus(&self) {
35 match Socket::connect() {
37 Ok(mut socket) => {
38 let request = NiriRequest::Action(FocusWindow { id: self.window.id });
39 if let Err(e) = socket.send(request) {
40 warn!("Failed to focus window {}: {}", self.window.id, e);
41 }
42 }
43 Err(e) => {
44 warn!("Failed to connect to niri socket: {}", e);
45 }
46 }
47 }
48}
49
50impl From<NiriWindow> for Window {
51 fn from(window: NiriWindow) -> Self {
52 Window { window }
53 }
54}
55
56impl LaunchId for Window {
57 fn id(&self) -> String {
58 format!("window-{}", self.window.id)
59 }
60
61 fn launchable(&self) -> bool {
62 true
65 }
66
67 fn file_path(&self) -> &Path {
68 Path::new("")
70 }
71
72 fn icon_name(&self) -> Option<&str> {
73 None
75 }
76
77 fn comment(&self) -> Option<&str> {
78 None
80 }
81
82 fn debug_ui(&self, ui: &mut egui::Ui, count: usize) {
83 ui.label(format!(" [{}] ID: {}", count, self.id()));
84 if let Some(title) = &self.window.title {
85 ui.label(format!(" Title: {}", title));
86 }
87 if let Some(app_id) = &self.window.app_id {
88 ui.label(format!(" App ID: {}", app_id));
89 }
90 ui.label(format!(" Window ID: {}", self.window.id));
91 if let Some(focus_id) = self.window.focus_timestamp {
92 ui.label(format!(" Focus History ID: {:?}", focus_id));
93 }
94 }
95}
96
97static DISPATCH_TABLE: LazyLock<HashMap<&'static str, fn(&Window)>> = LazyLock::new(|| {
98 let mut m: HashMap<&'static str, fn(&Window)> = HashMap::new();
99 m.insert("focus", Window::focus);
100 m
101});
102
103impl LaunchAction for Window {
104 fn actions(&self) -> Vec<&'static str> {
105 DISPATCH_TABLE.keys().copied().collect()
106 }
107
108 fn default_action(&self) -> &'static str {
109 "focus"
110 }
111
112 fn secondary_action(&self) -> &'static str {
113 "focus"
115 }
116
117 fn invoke(&self, action_name: &ActionName) -> ActionResult {
118 let action = match action_name {
120 ActionName::Default => self.default_action(),
121 ActionName::Action(name) => name,
122 };
123 if let Some(func) = DISPATCH_TABLE.get(action) {
124 func(self);
125 ActionResult::Success
126 } else {
127 let error_message = format!(
128 "Unknown action '{}' for DesktopItem '{}'",
129 action,
130 self.name().unwrap_or_default()
131 );
132 error!(error_message);
133 ActionResult::Error(error_message)
134 }
135 }
136
137 fn invoke_default(&self) -> ActionResult {
138 self.invoke(&ActionName::Default)
139 }
140}
141
142impl Launchable for Window {}
143
144pub(crate) fn load_niri_windows() -> (Option<Socket>, HashMap<String, Shared<LaunchEntry>>) {
146 let (niri_socket, windows) = match Socket::connect() {
148 Ok(mut socket) => {
149 let windows = match socket.send(NiriRequest::Windows) {
151 Ok(response) => match response {
152 Ok(response) => match response {
153 niri_ipc::Response::Windows(windows) => {
154 windows
156 .iter()
157 .map(|w| {
158 let window = LaunchEntry::from(w.clone());
159 let window_id = window.id.clone();
160 let window_lock = Shared::new(window);
161 (window_id, window_lock)
162 })
163 .collect()
164 }
165 _ => Default::default(),
166 },
179 Err(e) => {
180 warn!("Error from Niri: {}", e);
181 Default::default()
182 }
183 },
184 Err(e) => {
185 warn!("Error communicating with Niri socket: {}", e);
186 Default::default()
187 }
188 };
189 (Some(socket), windows)
190 }
191 Err(e) => {
192 warn!("Could not connect to Niri's IPC socket: {}", e);
194 (None, Default::default())
195 }
196 };
197
198 (niri_socket, windows)
209}