use std::{
error::Error,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread::JoinHandle,
time::Duration,
};
use windows::{
Win32::{
Foundation::{COLORREF, CloseHandle, HANDLE, HWND, LPARAM, POINT, RECT, WPARAM},
Graphics::Gdi::{EnumDisplayMonitors, RDW_INVALIDATE, RDW_UPDATENOW, RedrawWindow},
System::StationsAndDesktops::{CloseDesktop, HDESK},
UI::{
HiDpi::{PROCESS_PER_MONITOR_DPI_AWARE, SetProcessDpiAwareness},
Input::KeyboardAndMouse::{
GetAsyncKeyState, VK_LBUTTON, VK_MBUTTON, VK_RBUTTON, VK_XBUTTON1, VK_XBUTTON2,
},
WindowsAndMessaging::{
EnumWindows, FindWindowExW, FindWindowW, GWL_EXSTYLE, GWL_STYLE, GetCursorPos,
GetSystemMetrics, GetWindowLongPtrW, LWA_ALPHA, SM_CXEDGE, SM_CXVIRTUALSCREEN,
SM_CYEDGE, SM_CYVIRTUALSCREEN, SMTO_NORMAL, SPI_GETDESKWALLPAPER,
SPI_SETDESKWALLPAPER, SPIF_SENDCHANGE, SPIF_UPDATEINIFILE, SWP_NOACTIVATE,
SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
SendMessageTimeoutW, SetLayeredWindowAttributes, SetParent, SetWindowLongPtrW,
SetWindowPos, SystemParametersInfoW, WS_CHILD, WS_EX_LAYERED, WS_EX_STATICEDGE,
WS_OVERLAPPEDWINDOW,
},
},
},
core::{Error as WinErr, w},
};
use crate::platform::windows::{
functions::has_workerw_between,
procs::{enum_windows_proc, monitor_enum_proc},
};
#[derive(Default, Debug)]
pub struct AttachWindow {
pub progman_window_handle: Option<HWND>,
pub workerw_window_handle: Option<HWND>,
pub shell_view_window_handle: Option<HWND>,
pub engine_window_handle: Option<HWND>,
pub selected_monitor: Option<MonitorInfo>,
pub desktop_x: i32,
pub desktop_y: i32,
pub current_mouse_state: [bool; 5],
pub previous_mouse_state: [bool; 5],
pub is_pre_24h2: bool,
watcher_kill_flag: Option<Arc<AtomicBool>>,
join_handle: Option<JoinHandle<Result<(), WinErr>>>,
}
impl AttachWindow {
pub fn initialize() -> Result<Self, WinErr> {
let mut windows_platform = Self::default();
unsafe {
let _ = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
windows_platform.progman_window_handle = Some(FindWindowW(w!("Progman"), None)?);
let mut result = 0;
SendMessageTimeoutW(
windows_platform.progman_window_handle.unwrap(),
0x052c,
WPARAM(0),
LPARAM(0),
SMTO_NORMAL,
1000,
Some(&mut result),
);
windows_platform.shell_view_window_handle = FindWindowExW(
windows_platform.progman_window_handle,
None,
w!("SHELLDLL_DefView"),
None,
)
.ok();
windows_platform.workerw_window_handle = FindWindowExW(
windows_platform.progman_window_handle,
None,
w!("WorkerW"),
None,
)
.ok();
if windows_platform.workerw_window_handle.is_none() {
windows_platform.is_pre_24h2 = true;
EnumWindows(
Some(enum_windows_proc),
LPARAM(&mut windows_platform as *mut _ as _),
)?;
}
if windows_platform.workerw_window_handle.is_none() {
return Err(WinErr::empty());
}
}
Ok(windows_platform)
}
pub fn configure_wallpaper_window(
&mut self,
hwnd: isize,
monitor: &MonitorInfo,
static_edge_mode: bool,
) -> Result<(), WinErr> {
let hwnd = HWND(hwnd as _);
self.engine_window_handle = Some(hwnd);
if self.progman_window_handle.is_none() {
return Ok(());
}
unsafe {
if self.is_pre_24h2 {
SetParent(hwnd, self.workerw_window_handle)?;
let mut style = GetWindowLongPtrW(hwnd, GWL_STYLE);
style &= !WS_OVERLAPPEDWINDOW.0 as isize; style |= WS_CHILD.0 as isize; SetWindowLongPtrW(hwnd, GWL_STYLE, style);
} else {
let mut style = GetWindowLongPtrW(hwnd, GWL_STYLE);
style &= !WS_OVERLAPPEDWINDOW.0 as isize; style |= WS_CHILD.0 as isize; SetWindowLongPtrW(hwnd, GWL_STYLE, style);
let mut ex_style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
ex_style |= WS_EX_LAYERED.0 as isize;
if static_edge_mode {
ex_style |= WS_EX_STATICEDGE.0 as isize;
}
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, ex_style);
SetLayeredWindowAttributes(hwnd, COLORREF(0), 255, LWA_ALPHA)?;
SetParent(hwnd, self.progman_window_handle)?;
if self.shell_view_window_handle.is_some() {
SetWindowPos(
hwnd,
self.shell_view_window_handle,
0,
0,
0,
0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE,
)?;
}
if self.workerw_window_handle.is_some() {
SetWindowPos(
self.workerw_window_handle.unwrap(),
self.engine_window_handle,
0,
0,
0,
0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE,
)?;
}
}
self.selected_monitor = Some(monitor.clone());
let (edge_x, edge_y) = if static_edge_mode {
(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE))
} else {
(0, 0)
};
SetWindowPos(
hwnd,
None,
monitor.x - edge_x,
monitor.y - edge_y,
monitor.width + 2 * edge_x,
monitor.height + 2 * edge_y,
SWP_NOZORDER | SWP_NOACTIVATE,
)?;
RedrawWindow(
self.engine_window_handle,
None,
None,
RDW_INVALIDATE | RDW_UPDATENOW,
)
.ok()?;
}
Ok(())
}
#[inline]
fn set_parent(child: HWND, parent: HWND) -> Result<(), WinErr> {
unsafe { SetParent(child, Some(parent))? };
Ok(())
}
pub fn get_wallpaper_target(
&mut self,
monitor_index: Option<i32>,
) -> Result<MonitorInfo, WinErr> {
let monitor_index = monitor_index.unwrap_or(-1);
let monitors = self.enumerate_monitors()?;
if monitor_index < 0 || monitor_index as usize >= monitors.len() {
let mut info = MonitorInfo::default();
info.x = 0;
info.y = 0;
unsafe {
info.width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
info.height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
}
Ok(info)
} else {
Ok(monitors[monitor_index as usize].clone())
}
}
pub fn auto_attach(hwnd: isize, static_edge_mode: bool) -> Result<Self, WinErr> {
let mut window_platform = Self::initialize()?;
let monitor = window_platform.get_wallpaper_target(None)?;
window_platform.configure_wallpaper_window(hwnd, &monitor, static_edge_mode)?;
Ok(window_platform)
}
pub fn enumerate_monitors(&mut self) -> Result<Vec<MonitorInfo>, WinErr> {
let mut monitor_info_vector: Vec<MonitorInfo> = Vec::new();
let lparam = LPARAM(&mut monitor_info_vector as *mut Vec<MonitorInfo> as isize);
unsafe { EnumDisplayMonitors(None, None, Some(monitor_enum_proc), lparam).ok()? };
self.desktop_x = i32::MAX;
self.desktop_y = i32::MAX;
for monitor in &monitor_info_vector {
self.desktop_x = self.desktop_x.min(monitor.x);
self.desktop_y = self.desktop_y.min(monitor.y);
}
for monitor in &mut monitor_info_vector {
monitor.x -= self.desktop_x;
monitor.y -= self.desktop_y;
}
Ok(monitor_info_vector)
}
pub fn start_watcher(&mut self, wathcer_delay: Duration) -> Result<(), &str> {
if self.join_handle.is_some() {
return Err("another watcher is working");
}
let shell_def_view = self.shell_view_window_handle.unwrap().0 as isize;
let engine = self.engine_window_handle.unwrap().0 as isize;
let parent = if self.is_pre_24h2 {
self.workerw_window_handle.unwrap().0 as isize
} else {
self.progman_window_handle.unwrap().0 as isize
};
let kill_flag = Arc::new(AtomicBool::new(false));
self.watcher_kill_flag = Some(kill_flag.clone());
let join_handle = std::thread::spawn(move || -> Result<(), WinErr> {
let mut previous = has_workerw_between(shell_def_view, engine);
let engine_hwnd = HWND(engine as _);
let parent_hwnd = HWND(parent as _);
while !kill_flag.load(Ordering::Acquire) {
std::thread::sleep(wathcer_delay);
let current = has_workerw_between(shell_def_view, engine);
if current != previous {
Self::set_parent(engine_hwnd, parent_hwnd)?;
previous = current;
}
}
Ok(())
});
self.join_handle = Some(join_handle);
Ok(())
}
pub fn get_mouse_position(&self) -> Vector2Platform {
let mut p = POINT::default();
if self.get_relative_cursor_pos(&mut p).is_ok() {
return Vector2Platform {
x: p.x as _,
y: p.y as _,
};
}
Vector2Platform { x: 0_f32, y: 0_f32 }
}
pub fn supports_dynamic_wallpaper() -> bool {
true
}
pub fn supports_multi_monitor() -> bool {
true
}
pub fn is_mouse_button_pressed(&self, button: i32) -> bool {
if button < 0 || button >= 5 {
return false;
}
let button = button as usize;
self.current_mouse_state[button] && !self.previous_mouse_state[button]
}
pub fn is_mouse_button_down(&self, button: i32) -> bool {
if button < 0 || button >= 5 {
return false;
}
self.current_mouse_state[button as usize]
}
pub fn is_mouse_button_released(&self, button: i32) -> bool {
if button < 0 || button >= 5 {
return false;
}
let button = button as usize;
!self.current_mouse_state[button] && self.previous_mouse_state[button]
}
pub fn is_mouse_button_up(&self, button: i32) -> bool {
if button < 0 || button >= 5 {
return false;
}
!self.current_mouse_state[button as usize]
}
pub fn get_mouse_x(&self) -> i32 {
let mut p = POINT::default();
if self.get_relative_cursor_pos(&mut p).is_ok() {
return p.x;
}
0
}
pub fn get_mouse_y(&self) -> i32 {
let mut p = POINT::default();
if self.get_relative_cursor_pos(&mut p).is_ok() {
return p.y;
}
0
}
fn get_relative_cursor_pos(&self, p: &mut POINT) -> Result<(), WinErr> {
unsafe { GetCursorPos(p)? }
p.x -= self.desktop_x;
p.y -= self.desktop_y;
let selected_monitor = self.selected_monitor.as_ref().ok_or(WinErr::empty())?;
p.x -= selected_monitor.x;
p.y -= selected_monitor.y;
Ok(())
}
pub fn stop_watcher(&mut self) -> Result<(), Box<dyn Error>> {
if let Some(kill_flag) = &self.watcher_kill_flag {
kill_flag.store(true, Ordering::Release);
self.watcher_kill_flag = None;
self.join_handle
.take()
.unwrap()
.join()
.map_err(|_| "Watcher thread panicked")??;
Ok(())
} else {
Err("Watcher has not yet started".into())
}
}
pub fn cleanup(&mut self) -> Result<(), Box<dyn Error>> {
let watcher_error = if self.join_handle.is_some() {
self.stop_watcher()
} else {
Ok(())
};
const MAX_PATH: u32 = 260_u32;
if self.engine_window_handle.is_some() {
let mut wallpaper_path = [0_u16; MAX_PATH as usize];
unsafe {
if SystemParametersInfoW(
SPI_GETDESKWALLPAPER,
MAX_PATH,
Some(wallpaper_path.as_mut_ptr() as *mut _),
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
)
.is_ok()
{
SystemParametersInfoW(
SPI_SETDESKWALLPAPER,
0,
Some(wallpaper_path.as_mut_ptr() as *mut _),
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE,
)?;
}
}
}
self.progman_window_handle = None;
self.workerw_window_handle = None;
self.shell_view_window_handle = None;
self.engine_window_handle = None;
watcher_error
}
pub fn update_mouse_state(&mut self) {
self.previous_mouse_state
.copy_from_slice(&self.current_mouse_state);
(0..5).for_each(|i| {
self.current_mouse_state[i] = match get_virtual_key_for_mouse_button(i) as _ {
0 => false,
vk => unsafe { (GetAsyncKeyState(vk) as i32 & 0x8000) != 0 },
}
});
}
}
fn get_virtual_key_for_mouse_button(button: usize) -> u16 {
match button {
0 => VK_LBUTTON.0,
1 => VK_RBUTTON.0,
2 => VK_MBUTTON.0,
3 => VK_XBUTTON1.0,
4 => VK_XBUTTON2.0,
_ => 0,
}
}
impl Drop for AttachWindow {
fn drop(&mut self) {
let _ = self.stop_watcher();
}
}
#[derive(Debug, Default, Clone)]
pub struct MonitorInfo {
pub x: i32, pub y: i32,
pub width: i32, pub height: i32,
pub work_x: i32, pub work_y: i32,
pub work_width: i32, pub work_height: i32, }
#[derive(Debug, Default, Clone)]
pub struct FullscreenOcclusionData {
pub monitor: MonitorInfo,
pub occluded_rects: Vec<RECT>,
}
#[derive(Debug, Default, Clone)]
pub struct Vector2Platform {
pub x: f32,
pub y: f32,
}
pub struct DesktopHandle(pub HDESK);
impl Drop for DesktopHandle {
fn drop(&mut self) {
unsafe {
if !self.0.is_invalid() {
let _ = CloseDesktop(self.0);
}
}
}
}
pub struct Handle(pub HANDLE);
impl Drop for Handle {
fn drop(&mut self) {
unsafe {
if !self.0.is_invalid() {
let _ = CloseHandle(self.0);
}
}
}
}