1use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc};
4use windows::{
5 core::{Error, PWSTR},
6 Win32::{
7 Foundation::{CloseHandle, HANDLE, HWND},
8 System::Threading::{
9 OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_FORMAT,
10 PROCESS_QUERY_LIMITED_INFORMATION,
11 },
12 UI::WindowsAndMessaging::{GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId},
13 },
14};
15
16use crate::{nwg_ext::enum_child_windows, vd};
17
18pub fn all_windows() -> Vec<HWND> {
20 let mut result = Vec::new();
21 enum_child_windows(None, |window| {
22 result.push(window);
23 ControlFlow::Continue(())
24 });
25 result
26}
27
28pub fn get_window_title(window: HWND) -> Result<String, Error> {
35 let mut length = unsafe { GetWindowTextLengthW(window) };
36 if length == 0 {
37 return Ok(String::new());
38 }
39 length += 1;
40 let mut title: Vec<u16> = vec![0; length as usize];
41 let len = unsafe { GetWindowTextW(window, &mut title) };
42 if len != 0 {
43 Ok(String::from_utf16(title[0..(len as usize)].as_ref())?)
44 } else {
45 Err(Error::from_win32())
46 }
47}
48
49pub fn get_window_process_id(window: HWND) -> Result<u32, Error> {
56 let mut process_id = 0;
57 let thread_id = unsafe { GetWindowThreadProcessId(window, Some(&mut process_id)) };
58 if thread_id == 0 {
59 Err(Error::from_win32())
60 } else {
61 Ok(process_id)
62 }
63}
64
65pub fn get_process_full_name(process_id: u32) -> Result<String, Error> {
73 struct ProcessHandle(HANDLE);
74 impl ProcessHandle {
75 fn close(self) -> Result<(), Error> {
76 let handle = self.0;
77 std::mem::forget(self);
78 unsafe { CloseHandle(handle) }
79 }
80 }
81 impl Drop for ProcessHandle {
82 fn drop(&mut self) {
83 let _ = unsafe { CloseHandle(self.0) };
84 }
85 }
86 let handle = ProcessHandle(unsafe {
87 OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_id)?
89 });
90 let mut buffer: Vec<u16> = vec![0; 1024];
91 let mut length = buffer.len() as u32;
92 unsafe {
93 QueryFullProcessImageNameW(
94 handle.0,
95 PROCESS_NAME_FORMAT(0),
96 PWSTR::from_raw(buffer.as_mut_ptr()),
97 &mut length,
98 )?;
99 }
100 handle.close()?;
101 Ok(String::from_utf16(&buffer[..length as usize])?)
102}
103
104#[allow(clippy::assigning_clones)]
106pub fn get_process_name(process_id: u32) -> Result<String, Error> {
107 let mut exe_path = get_process_full_name(process_id)?;
108 if let Some(slash) = exe_path.rfind(['\\', '/']) {
109 exe_path = exe_path[slash + 1..].to_owned();
110 }
111 if exe_path.ends_with(".exe") {
112 exe_path.truncate(exe_path.len() - 4);
113 }
114 Ok(exe_path)
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum VirtualDesktopInfo {
119 WindowPinned,
120 AppPinned,
121 AtDesktop {
122 desktop: vd::Desktop,
124 index: u32,
127 },
128}
129impl fmt::Display for VirtualDesktopInfo {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 Self::WindowPinned => write!(f, "Pinned Window"),
133 Self::AppPinned => write!(f, "Pinned App"),
134 Self::AtDesktop { index, .. } => fmt::Display::fmt(&(index + 1), f),
135 }
136 }
137}
138impl VirtualDesktopInfo {
139 pub fn new(window: HWND) -> vd::Result<Self> {
140 if vd::is_pinned_app(window)? {
141 Ok(Self::AppPinned)
142 } else if vd::is_pinned_window(window)? {
143 Ok(Self::WindowPinned)
144 } else {
145 let desktop = vd::get_window_desktop(window)?;
146 let index = desktop.get_index()?;
147 Ok(Self::AtDesktop { desktop, index })
148 }
149 }
150
151 #[must_use]
155 pub fn is_window_pinned(&self) -> bool {
156 matches!(self, Self::WindowPinned)
157 }
158
159 #[must_use]
163 pub fn is_app_pinned(&self) -> bool {
164 matches!(self, Self::AppPinned)
165 }
166
167 #[must_use]
171 pub fn is_at_desktop(&self) -> bool {
172 matches!(self, Self::AtDesktop { .. })
173 }
174}
175
176#[derive(Debug, Clone)]
177pub enum GetAllError {
178 Title(Error),
179 ProcessId(Error),
180 ProcessName(Error),
181 VirtualDesktop(vd::Error),
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
185pub struct WindowHandle(pub isize);
186impl WindowHandle {
187 pub fn as_hwnd(self) -> HWND {
188 HWND(self.0 as *mut _)
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct WindowInfo {
194 pub handle: WindowHandle,
195 pub title: String,
196 pub process_id: u32,
197 pub process_name: Arc<str>,
198 pub virtual_desktop: VirtualDesktopInfo,
199}
200impl WindowInfo {
201 pub fn get_all() -> Vec<WindowInfo> {
202 Self::try_get_all()
203 .filter_map(|res| match res {
204 Ok(info) => Some(info),
205 Err(e) => {
206 tracing::trace!("Failed to get window info: {:?}", e);
207 None
208 }
209 })
210 .collect()
211 }
212 pub fn try_get_all() -> impl Iterator<Item = Result<WindowInfo, GetAllError>> {
213 let mut process_names: HashMap<u32, Arc<str>> = HashMap::new();
214 all_windows()
215 .into_iter()
216 .map(move |handle| -> Result<WindowInfo, GetAllError> {
217 let virtual_desktop =
218 VirtualDesktopInfo::new(handle).map_err(GetAllError::VirtualDesktop)?;
219 let title = get_window_title(handle).map_err(GetAllError::Title)?;
220 let process_id = get_window_process_id(handle).map_err(GetAllError::ProcessId)?;
221 let process_name = if let Some(name) = process_names.get(&process_id) {
222 name.clone()
223 } else {
224 let name = Arc::<str>::from(
225 get_process_name(process_id).map_err(GetAllError::ProcessName)?,
226 );
227 process_names.insert(process_id, name.clone());
228 name
229 };
230 Ok(WindowInfo {
231 handle: WindowHandle(handle.0 as isize),
232 title,
233 process_id,
234 process_name,
235 virtual_desktop,
236 })
237 })
238 }
239}