diff --git a/Cargo.lock b/Cargo.lock index 45132b2c..d0435973 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -796,6 +796,33 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "komoborders" +version = "0.1.0" +dependencies = [ + "color-eyre", + "dirs", + "komoborders-client", + "komorebi", + "komorebi-client", + "lazy_static", + "parking_lot", + "serde", + "serde_json", + "uds_windows", + "windows 0.54.0", +] + +[[package]] +name = "komoborders-client" +version = "0.1.0" +dependencies = [ + "dirs", + "serde", + "serde_json_lenient", + "uds_windows", +] + [[package]] name = "komorebi" version = "0.1.26-dev.0" @@ -810,6 +837,7 @@ dependencies = [ "getset", "hex_color", "hotwatch", + "komoborders-client", "komorebi-core", "lazy_static", "miow", diff --git a/Cargo.toml b/Cargo.toml index 160ec357..343dc06c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ resolver = "2" members = [ - "derive-ahk", + "derive-ahk", "komoborders", "komoborders-client", "komorebi", "komorebi-client", "komorebi-core", @@ -18,6 +18,8 @@ dirs = "5" color-eyre = "0.6" serde_json = { package = "serde_json_lenient", version = "0.1" } sysinfo = "0.30" +serde = { version = "1", features = ["derive"] } +uds_windows = "1" [workspace.dependencies.windows] version = "0.54" diff --git a/komoborders-client/Cargo.toml b/komoborders-client/Cargo.toml new file mode 100644 index 00000000..72d1fd9e --- /dev/null +++ b/komoborders-client/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "komoborders-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +uds_windows = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } +dirs = { workspace = true } diff --git a/komoborders-client/src/lib.rs b/komoborders-client/src/lib.rs new file mode 100644 index 00000000..e4329d4b --- /dev/null +++ b/komoborders-client/src/lib.rs @@ -0,0 +1,74 @@ +use serde::Deserialize; +use serde::Serialize; +use std::io::Write; +use std::str::FromStr; +use uds_windows::UnixStream; + +const KOMOBORDERS: &str = "komoborders.sock"; + +pub fn send_message(message: &SocketMessage) -> std::io::Result<()> { + let socket = dirs::data_local_dir() + .expect("there is no local data directory") + .join("komorebi") + .join(KOMOBORDERS); + + let mut connected = false; + while !connected { + if let Ok(mut stream) = UnixStream::connect(&socket) { + connected = true; + stream.write_all(serde_json::to_string(message)?.as_bytes())?; + } + } + + Ok(()) +} + +#[derive(Copy, Clone, Serialize, Deserialize)] +pub enum ZOrder { + Top, + NoTopMost, + Bottom, + TopMost, +} + +// impl From for ZOrder { +// fn from(value: isize) -> Self { +// match value { +// -2 => Self::NoTopMost, +// -1 => Self::TopMost, +// 0 => Self::Top, +// 1 => Self::Bottom, +// _ => unimplemented!(), +// } +// } +// } + +impl Into for ZOrder { + fn into(self) -> isize { + match self { + ZOrder::Top => 0, + ZOrder::NoTopMost => -2, + ZOrder::Bottom => 1, + ZOrder::TopMost => -1, + } + } +} + +#[derive(Serialize, Deserialize)] +pub enum SocketMessage { + FocusedColour(u32, u32, u32), + UnfocusedColour(u32, u32, u32), + MonocleColour(u32, u32, u32), + StackColour(u32, u32, u32), + Width(i32), + Offset(i32), + ZOrder(ZOrder), +} + +impl FromStr for SocketMessage { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} diff --git a/komoborders/Cargo.toml b/komoborders/Cargo.toml new file mode 100644 index 00000000..60f2a7f4 --- /dev/null +++ b/komoborders/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "komoborders" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +komorebi-client = { path = "../komorebi-client" } +komoborders-client = { path = "../komoborders-client" } +komorebi = { path = "../komorebi" } +serde_json = "1" +color-eyre = "0.6" +windows = { workspace = true } +lazy_static = "1" +parking_lot = "0.12" +uds_windows = { workspace = true } +serde = { workspace = true } +dirs = { workspace = true } \ No newline at end of file diff --git a/komoborders/src/border.rs b/komoborders/src/border.rs new file mode 100644 index 00000000..40def805 --- /dev/null +++ b/komoborders/src/border.rs @@ -0,0 +1,199 @@ +use komoborders_client::ZOrder; +use lazy_static::lazy_static; +use parking_lot::Mutex; +use std::sync::atomic::AtomicI32; +use std::sync::atomic::AtomicU32; +use std::sync::atomic::Ordering; +use std::sync::mpsc; +use std::sync::Arc; +use std::time::Duration; +use windows::core::PCWSTR; +use windows::Win32::Foundation::COLORREF; +use windows::Win32::Foundation::HWND; +use windows::Win32::Foundation::LPARAM; +use windows::Win32::Foundation::LRESULT; +use windows::Win32::Foundation::WPARAM; +use windows::Win32::Graphics::Gdi::BeginPaint; +use windows::Win32::Graphics::Gdi::CreatePen; +use windows::Win32::Graphics::Gdi::EndPaint; +use windows::Win32::Graphics::Gdi::InvalidateRect; +use windows::Win32::Graphics::Gdi::Rectangle; +use windows::Win32::Graphics::Gdi::SelectObject; +use windows::Win32::Graphics::Gdi::ValidateRect; +use windows::Win32::Graphics::Gdi::PAINTSTRUCT; +use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME; +use windows::Win32::Graphics::Gdi::PS_SOLID; +use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; +use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW; +use windows::Win32::UI::WindowsAndMessaging::GetMessageW; +use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; +use windows::Win32::UI::WindowsAndMessaging::TranslateMessage; +use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW; +use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW; +use windows::Win32::UI::WindowsAndMessaging::MSG; +use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; +use windows::Win32::UI::WindowsAndMessaging::WM_PAINT; +use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; + +use crate::FocusKind; +use crate::FOCUSED_STATE; +use crate::RECT_STATE; +use komorebi::Rgb; +use komorebi::WindowsApi; +use komorebi_client::Rect; + +pub static TRANSPARENCY: u32 = 0; +pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8); +pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1); + +lazy_static! { + pub static ref Z_ORDER: Arc> = Arc::new(Mutex::new(ZOrder::Bottom)); + pub static ref FOCUSED: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb( + Rgb::new(66, 165, 245) + ))); + pub static ref UNFOCUSED: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb( + Rgb::new(128, 128, 128) + ))); + pub static ref MONOCLE: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb( + Rgb::new(255, 51, 153) + ))); + pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(komorebi_client::Colour::Rgb( + Rgb::new(0, 165, 66) + ))); +} + +pub struct Border { + pub hwnd: isize, +} + +impl Border { + pub const fn hwnd(&self) -> HWND { + HWND(self.hwnd) + } + + pub fn create(id: &str) -> color_eyre::Result { + let name: Vec = format!("komoborder-{id}\0").encode_utf16().collect(); + let class_name = PCWSTR(name.as_ptr()); + + let h_module = WindowsApi::module_handle_w()?; + + let window_class = WNDCLASSW { + hInstance: h_module.into(), + lpszClassName: class_name, + style: CS_HREDRAW | CS_VREDRAW, + lpfnWndProc: Some(Self::callback), + hbrBackground: WindowsApi::create_solid_brush(TRANSPARENCY), + ..Default::default() + }; + + let _ = WindowsApi::register_class_w(&window_class); + + let (hwnd_sender, hwnd_receiver) = mpsc::channel(); + + std::thread::spawn(move || -> color_eyre::Result<()> { + let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), h_module)?; + hwnd_sender.send(hwnd)?; + + let mut message = MSG::default(); + unsafe { + while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() { + TranslateMessage(&message); + DispatchMessageW(&message); + std::thread::sleep(Duration::from_millis(10)); + } + } + + Ok(()) + }); + + Ok(Self { + hwnd: hwnd_receiver.recv()?, + }) + } + + pub fn destroy(&self) -> color_eyre::Result<()> { + WindowsApi::destroy_window(self.hwnd()) + } + + pub fn update(&self, rect: &Rect) -> color_eyre::Result<()> { + // Make adjustments to the border + let mut rect = *rect; + rect.add_margin(BORDER_WIDTH.load(Ordering::SeqCst)); + rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst)); + + // Store the border rect so that it can be used by the callback + { + let mut rects = RECT_STATE.lock(); + rects.insert(self.hwnd, rect); + } + + // Update the position of the border + WindowsApi::set_border_pos(self.hwnd(), &rect, HWND((*Z_ORDER.lock()).into()))?; + + // Invalidate the rect to trigger the callback to update colours etc. + self.invalidate(); + + Ok(()) + } + + pub fn invalidate(&self) { + let _ = unsafe { InvalidateRect(self.hwnd(), None, false) }; + } + + pub extern "system" fn callback( + window: HWND, + message: u32, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + unsafe { + match message { + WM_PAINT => { + let rects = RECT_STATE.lock(); + + // With the rect that we stored in Self::update + if let Some(rect) = rects.get(&window.0).copied() { + // Grab the focus kind for this border + let focus_kind = { + FOCUSED_STATE + .lock() + .get(&window.0) + .copied() + .unwrap_or(FocusKind::Unfocused) + }; + + // Set up the brush to draw the border + let mut ps = PAINTSTRUCT::default(); + let hdc = BeginPaint(window, &mut ps); + let hpen = CreatePen( + PS_SOLID | PS_INSIDEFRAME, + BORDER_WIDTH.load(Ordering::SeqCst), + COLORREF(match focus_kind { + FocusKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst), + FocusKind::Single => FOCUSED.load(Ordering::SeqCst), + FocusKind::Stack => STACK.load(Ordering::SeqCst), + FocusKind::Monocle => MONOCLE.load(Ordering::SeqCst), + }), + ); + + let hbrush = WindowsApi::create_solid_brush(TRANSPARENCY); + + // Draw the border + SelectObject(hdc, hpen); + SelectObject(hdc, hbrush); + Rectangle(hdc, 0, 0, rect.right, rect.bottom); + EndPaint(window, &ps); + ValidateRect(window, None); + } + + LRESULT(0) + } + WM_DESTROY => { + PostQuitMessage(0); + LRESULT(0) + } + _ => DefWindowProcW(window, message, wparam, lparam), + } + } + } +} diff --git a/komoborders/src/main.rs b/komoborders/src/main.rs new file mode 100644 index 00000000..cf81cf35 --- /dev/null +++ b/komoborders/src/main.rs @@ -0,0 +1,259 @@ +#![warn(clippy::all, clippy::nursery, clippy::pedantic)] +#![allow( + clippy::missing_errors_doc, + clippy::redundant_pub_crate, + clippy::significant_drop_tightening, + clippy::significant_drop_in_scrutinee +)] + +mod border; + +use komorebi_client::Rect; +use komorebi_client::UnixListener; +use lazy_static::lazy_static; +use parking_lot::Mutex; +use std::collections::HashMap; +use std::io::BufRead; +use std::io::BufReader; +use std::io::ErrorKind; +use std::str::FromStr; +use std::sync::atomic::Ordering; +use std::time::Duration; +use uds_windows::UnixStream; +use windows::Win32::Foundation::HWND; + +use crate::border::Border; +use crate::border::BORDER_WIDTH; +use crate::border::FOCUSED; +use crate::border::MONOCLE; +use crate::border::STACK; +use crate::border::UNFOCUSED; +use crate::border::Z_ORDER; +use komorebi::WindowsApi; +use komorebi_client::Rgb; + +lazy_static! { + static ref BORDER_STATE: Mutex> = Mutex::new(HashMap::new()); + static ref RECT_STATE: Mutex> = Mutex::new(HashMap::new()); + static ref FOCUSED_STATE: Mutex> = Mutex::new(HashMap::new()); +} + +#[derive(Copy, Clone)] +enum FocusKind { + Unfocused, + Single, + Stack, + Monocle, +} + +pub fn read_commands_uds(stream: UnixStream) -> color_eyre::Result<()> { + let reader = BufReader::new(stream.try_clone()?); + for line in reader.lines() { + let message = komoborders_client::SocketMessage::from_str(&line?)?; + + match message { + komoborders_client::SocketMessage::FocusedColour(r, g, b) => FOCUSED.store( + komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(), + Ordering::SeqCst, + ), + komoborders_client::SocketMessage::UnfocusedColour(r, g, b) => UNFOCUSED.store( + komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(), + Ordering::SeqCst, + ), + komoborders_client::SocketMessage::MonocleColour(r, g, b) => MONOCLE.store( + komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(), + Ordering::SeqCst, + ), + komoborders_client::SocketMessage::StackColour(r, g, b) => STACK.store( + komorebi::Colour::Rgb(Rgb::new(r, g, b)).into(), + Ordering::SeqCst, + ), + komoborders_client::SocketMessage::Width(width) => { + BORDER_WIDTH.store(width, Ordering::SeqCst) + } + komoborders_client::SocketMessage::Offset(offset) => { + BORDER_WIDTH.store(offset, Ordering::SeqCst) + } + komoborders_client::SocketMessage::ZOrder(z_order) => { + let mut z = Z_ORDER.lock(); + *z = z_order; + } + } + + let borders = BORDER_STATE.lock(); + for (_, border) in borders.iter() { + border.invalidate(); + } + } + + Ok(()) +} + +fn main() -> color_eyre::Result<()> { + WindowsApi::set_process_dpi_awareness_context()?; + let socket = dirs::data_local_dir() + .expect("there is no local data directory") + .join("komorebi") + .join("komoborders.sock"); + + match std::fs::remove_file(&socket) { + Ok(()) => {} + Err(error) => match error.kind() { + // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ + ErrorKind::NotFound => {} + _ => { + return Err(error.into()); + } + }, + }; + + let listener = UnixListener::bind(&socket)?; + std::thread::spawn(move || { + for client in listener.incoming() { + match client { + Ok(stream) => match read_commands_uds(stream) { + Ok(()) => { + println!("processed message"); + } + Err(error) => { + println!("{error}"); + } + }, + Err(error) => { + println!("{error}"); + break; + } + } + } + }); + + let komorebi = komorebi_client::subscribe("komoborders")?; + + for client in komorebi.incoming() { + match client { + Ok(subscription) => { + let reader = BufReader::new(subscription); + + #[allow(clippy::lines_filter_map_ok)] + for line in reader.lines().flatten() { + if let Ok(notification) = + serde_json::from_str::(&line) + { + let mut borders = BORDER_STATE.lock(); + // Check the state every time we receive a notification + let state = notification.state; + + for m in state.monitors.elements() { + // Only operate on the focused workspace of each monitor + if let Some(ws) = m.focused_workspace() { + let mut should_proceed = true; + + // Handle the monocle container separately + if let Some(monocle) = ws.monocle_container() { + for (_, border) in borders.iter() { + border.destroy()?; + } + + borders.clear(); + let border = borders + .entry(monocle.id().clone()) + .or_insert_with(|| Border::create(monocle.id()).unwrap()); + + { + let mut focused = FOCUSED_STATE.lock(); + focused.insert(border.hwnd, FocusKind::Monocle); + } + + let rect = WindowsApi::window_rect( + monocle.focused_window().unwrap().hwnd(), + )?; + + border.update(&rect)?; + should_proceed = false; + } + + if should_proceed { + let is_maximized = WindowsApi::is_zoomed(HWND( + WindowsApi::foreground_window().unwrap_or_default(), + )); + + if is_maximized { + for (_, border) in borders.iter() { + border.destroy()?; + } + + borders.clear(); + should_proceed = false; + } + } + + if should_proceed { + // Destroy any borders not associated with the focused workspace + let container_ids = ws + .containers() + .iter() + .map(|c| c.id().clone()) + .collect::>(); + + for (id, border) in borders.iter() { + if !container_ids.contains(id) { + border.destroy()?; + } + } + + // Remove them from the border map + borders.retain(|k, _| container_ids.contains(k)); + + for (idx, c) in ws.containers().iter().enumerate() { + // Get the border entry for this container from the map or create one + let border = borders + .entry(c.id().clone()) + .or_insert_with(|| Border::create(c.id()).unwrap()); + + // Update the focused state for all containers on this workspace + { + let mut focused = FOCUSED_STATE.lock(); + focused.insert( + border.hwnd, + if idx != ws.focused_container_idx() { + FocusKind::Unfocused + } else { + if c.windows().len() > 1 { + FocusKind::Stack + } else { + FocusKind::Single + } + }, + ); + } + + let rect = WindowsApi::window_rect( + c.focused_window().unwrap().hwnd(), + )?; + + border.update(&rect)?; + } + } + } + } + } + } + } + Err(error) => { + if error.raw_os_error().expect("could not get raw os error") == 109 { + while komorebi_client::send_message( + &komorebi_client::SocketMessage::AddSubscriberSocket(String::from( + "komoborders", + )), + ) + .is_err() + { + std::thread::sleep(Duration::from_secs(5)); + } + } + } + } + } + + Ok(()) +} diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index dac15009..c3e924f9 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" [dependencies] komorebi-core = { path = "../komorebi-core" } +komoborders-client = { path = "../komoborders-client" } bitflags = { version = "2", features = ["serde"] } clap = { version = "4", features = ["derive"] } diff --git a/komorebi/src/colour.rs b/komorebi/src/colour.rs index 092b4309..7428b004 100644 --- a/komorebi/src/colour.rs +++ b/komorebi/src/colour.rs @@ -65,7 +65,7 @@ pub struct Rgb { } impl Rgb { - pub fn new(r: u32, g: u32, b: u32) -> Self { + pub const fn new(r: u32, g: u32, b: u32) -> Self { Self { r, g, b } } } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 9ec5fdf2..8c6b1343 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -1255,12 +1255,21 @@ impl WindowManager { WindowKind::Single => { BORDER_COLOUR_SINGLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); BORDER_COLOUR_CURRENT.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + komoborders_client::send_message( + &komoborders_client::SocketMessage::FocusedColour(r, g, b), + )?; } WindowKind::Stack => { BORDER_COLOUR_STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + komoborders_client::send_message( + &komoborders_client::SocketMessage::StackColour(r, g, b), + )?; } WindowKind::Monocle => { BORDER_COLOUR_MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + komoborders_client::send_message( + &komoborders_client::SocketMessage::MonocleColour(r, g, b), + )?; } } @@ -1275,10 +1284,14 @@ impl WindowManager { SocketMessage::BorderWidth(width) => { BORDER_WIDTH.store(width, Ordering::SeqCst); WindowsApi::invalidate_border_rect()?; + komoborders_client::send_message(&komoborders_client::SocketMessage::Width(width))?; } SocketMessage::BorderOffset(offset) => { BORDER_OFFSET.store(offset, Ordering::SeqCst); WindowsApi::invalidate_border_rect()?; + komoborders_client::send_message(&komoborders_client::SocketMessage::Offset( + offset, + ))?; } SocketMessage::StackbarMode(mode) => { let mut stackbar_mode = STACKBAR_MODE.lock(); diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 46a6fb1b..2bfdd7d6 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -7,6 +7,7 @@ use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::workspace::Workspace; +use crate::Rgb; use crate::ACTIVE_WINDOW_BORDER_STYLE; use crate::BORDER_COLOUR_CURRENT; use crate::BORDER_COLOUR_MONOCLE; @@ -475,13 +476,36 @@ impl StaticConfig { }, ); + komoborders_client::send_message(&komoborders_client::SocketMessage::Width( + self.border_width.unwrap_or(8), + ))?; + BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst); + komoborders_client::send_message(&komoborders_client::SocketMessage::Width( + self.border_offset.unwrap_or(-1), + ))?; + if let Some(colours) = &self.active_window_border_colours { BORDER_COLOUR_SINGLE.store(u32::from(colours.single), Ordering::SeqCst); BORDER_COLOUR_CURRENT.store(u32::from(colours.single), Ordering::SeqCst); BORDER_COLOUR_STACK.store(u32::from(colours.stack), Ordering::SeqCst); BORDER_COLOUR_MONOCLE.store(u32::from(colours.monocle), Ordering::SeqCst); + + let single: Rgb = u32::from(colours.single).into(); + komoborders_client::send_message(&komoborders_client::SocketMessage::FocusedColour( + single.r, single.g, single.b, + ))?; + + let stack: Rgb = u32::from(colours.stack).into(); + komoborders_client::send_message(&komoborders_client::SocketMessage::StackColour( + stack.r, stack.g, stack.b, + ))?; + + let monocle: Rgb = u32::from(colours.monocle).into(); + komoborders_client::send_message(&komoborders_client::SocketMessage::MonocleColour( + monocle.r, monocle.g, monocle.b, + ))?; } let active_window_border_style = self.active_window_border_style.unwrap_or_default(); diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index aa223ad4..235d5ff5 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -124,6 +124,7 @@ use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION; use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS; use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX; use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE; +use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC; use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED; @@ -414,7 +415,8 @@ impl WindowsApi { // top of other pop-up dialogs such as a file picker dialog from // Firefox. When adjusting this in the future, it's important to check // those dialog cases. - Self::set_window_pos(hwnd, layout, HWND_TOP, flags.bits()) + // TODO: Make the HWND_X flag configurable + Self::set_window_pos(hwnd, layout, HWND_BOTTOM, flags.bits()) } pub fn hide_border_window(hwnd: HWND) -> Result<()> { @@ -424,6 +426,11 @@ impl WindowsApi { Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits()) } + pub fn set_border_pos(hwnd: HWND, layout: &Rect, position: HWND) -> Result<()> { + let flags = { SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE }; + Self::set_window_pos(hwnd, layout, position, flags.bits()) + } + /// set_window_pos calls SetWindowPos without any accounting for Window decorations. fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> { unsafe { @@ -461,6 +468,13 @@ impl WindowsApi { } } + pub fn destroy_window(hwnd: HWND) -> Result<()> { + match Self::post_message(hwnd, WM_DESTROY, WPARAM(0), LPARAM(0)) { + Ok(()) => Ok(()), + Err(_) => Err(anyhow!("could not close window")), + } + } + pub fn hide_window(hwnd: HWND) { Self::show_window(hwnd, SW_HIDE); }