From 657ac441ae3bc6c748908a6e88b9e1a37d411420 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Fri, 22 Dec 2023 17:01:57 -0800 Subject: [PATCH] feat(wm): use device id for monitor index pref This commit begins to build on some of the knowledge shared by EBNull in allowing users to specify monitor index preferences using physical device identifiers. This does not presently go all the way to EDIDs, but the display model and what I believe is a port identifier on the display adapter(s) can be used to uniquely identify a display in most use cases. However, I believe I may have unfortunately run into a bug in either windows-rs or Rust itself, as when the code calling EnumDisplayDevices is called, it always fails when running a release build, and always succeeds when running a debug build. This needs to be investigated further. re #612 --- komorebi-core/src/lib.rs | 1 + komorebi/src/main.rs | 2 ++ komorebi/src/monitor.rs | 6 ++++ komorebi/src/process_command.rs | 5 +++ komorebi/src/static_config.rs | 10 ++++++ komorebi/src/window_manager.rs | 3 ++ komorebi/src/windows_api.rs | 53 +++++++++++++++++++++++++++---- komorebi/src/windows_callbacks.rs | 37 ++++++++++++++++++++- komorebic/src/main.rs | 17 ++++++++++ 9 files changed, 127 insertions(+), 7 deletions(-) diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index f50fe2ab8..56c2d517e 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -84,6 +84,7 @@ pub enum SocketMessage { FlipLayout(Axis), // Monitor and Workspace Commands MonitorIndexPreference(usize, i32, i32, i32, i32), + DisplayIndexPreference(usize, String), EnsureWorkspaces(usize, usize), EnsureNamedWorkspaces(usize, Vec), NewWorkspace, diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 801e23af7..989a3c861 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -143,6 +143,8 @@ lazy_static! { ])); static ref MONITOR_INDEX_PREFERENCES: Arc>> = Arc::new(Mutex::new(HashMap::new())); + static ref DISPLAY_INDEX_PREFERENCES: Arc>> = + Arc::new(Mutex::new(HashMap::new())); static ref WORKSPACE_RULES: Arc>> = Arc::new(Mutex::new(HashMap::new())); static ref REGEX_IDENTIFIERS: Arc>> = diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index b29d6914d..1d320c3c9 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -24,6 +24,10 @@ pub struct Monitor { #[getset(get = "pub", set = "pub")] name: String, #[getset(get = "pub", set = "pub")] + device: Option, + #[getset(get = "pub", set = "pub")] + device_id: Option, + #[getset(get = "pub", set = "pub")] size: Rect, #[getset(get = "pub", set = "pub")] work_area_size: Rect, @@ -44,6 +48,8 @@ pub fn new(id: isize, size: Rect, work_area_size: Rect, name: String) -> Monitor Monitor { id, name, + device: None, + device_id: None, size, work_area_size, work_area_offset: None, diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 8535cd9e6..de3cf8bfc 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -59,6 +59,7 @@ use crate::BORDER_OVERFLOW_IDENTIFIERS; use crate::BORDER_WIDTH; use crate::CUSTOM_FFM; use crate::DATA_DIR; +use crate::DISPLAY_INDEX_PREFERENCES; use crate::FLOAT_IDENTIFIERS; use crate::HIDING_BEHAVIOUR; use crate::INITIAL_CONFIGURATION_LOADED; @@ -673,6 +674,10 @@ impl WindowManager { }, ); } + SocketMessage::DisplayIndexPreference(index_preference, ref display) => { + let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock(); + display_index_preferences.insert(index_preference, display.clone()); + } SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => { self.ensure_workspaces_for_monitor(monitor_idx, workspace_count)?; } diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 1b3e6e8d4..27252a328 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -19,6 +19,7 @@ use crate::BORDER_WIDTH; use crate::DATA_DIR; use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_WORKSPACE_PADDING; +use crate::DISPLAY_INDEX_PREFERENCES; use crate::FLOAT_IDENTIFIERS; use crate::HIDING_BEHAVIOUR; use crate::LAYERED_WHITELIST; @@ -312,6 +313,9 @@ pub struct StaticConfig { /// Set monitor index preferences #[serde(skip_serializing_if = "Option::is_none")] pub monitor_index_preferences: Option>, + /// Set display index preferences + #[serde(skip_serializing_if = "Option::is_none")] + pub display_index_preferences: Option>, } impl From<&WindowManager> for StaticConfig { @@ -432,6 +436,7 @@ impl From<&WindowManager> for StaticConfig { layered_applications: None, object_name_change_applications: None, monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()), + display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.lock().clone()), } } } @@ -444,6 +449,11 @@ impl StaticConfig { *preferences = monitor_index_preferences.clone(); } + if let Some(display_index_preferences) = &self.display_index_preferences { + let mut preferences = DISPLAY_INDEX_PREFERENCES.lock(); + *preferences = display_index_preferences.clone(); + } + if let Some(behaviour) = self.window_hiding_behaviour { let mut window_hiding_behaviour = HIDING_BEHAVIOUR.lock(); *window_hiding_behaviour = behaviour; diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 49130f38e..59d530cc1 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -49,6 +49,7 @@ use crate::workspace::Workspace; use crate::BORDER_HWND; use crate::BORDER_OVERFLOW_IDENTIFIERS; use crate::DATA_DIR; +use crate::DISPLAY_INDEX_PREFERENCES; use crate::FLOAT_IDENTIFIERS; use crate::HOME_DIR; use crate::LAYERED_WHITELIST; @@ -103,6 +104,7 @@ pub struct State { pub border_overflow_identifiers: Vec, pub name_change_on_launch_identifiers: Vec, pub monitor_index_preferences: HashMap, + pub display_index_preferences: HashMap, } impl AsRef for WindowManager { @@ -132,6 +134,7 @@ impl From<&WindowManager> for State { border_overflow_identifiers: BORDER_OVERFLOW_IDENTIFIERS.lock().clone(), name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(), monitor_index_preferences: MONITOR_INDEX_PREFERENCES.lock().clone(), + display_index_preferences: DISPLAY_INDEX_PREFERENCES.lock().clone(), } } } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index a5d4a2091..a3898c885 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -30,11 +30,13 @@ use windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP; use windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED; use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL; use windows::Win32::Graphics::Gdi::CreateSolidBrush; +use windows::Win32::Graphics::Gdi::EnumDisplayDevicesA; use windows::Win32::Graphics::Gdi::EnumDisplayMonitors; use windows::Win32::Graphics::Gdi::GetMonitorInfoW; use windows::Win32::Graphics::Gdi::InvalidateRect; use windows::Win32::Graphics::Gdi::MonitorFromPoint; use windows::Win32::Graphics::Gdi::MonitorFromWindow; +use windows::Win32::Graphics::Gdi::DISPLAY_DEVICEA; use windows::Win32::Graphics::Gdi::HBRUSH; use windows::Win32::Graphics::Gdi::HDC; use windows::Win32::Graphics::Gdi::HMONITOR; @@ -93,6 +95,7 @@ use windows::Win32::UI::WindowsAndMessaging::ShowWindow; use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW; use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint; use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT; +use windows::Win32::UI::WindowsAndMessaging::EDD_GET_DEVICE_INTERFACE_NAME; use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE; use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT; @@ -164,8 +167,8 @@ impl_from_integer_for_windows_result!(usize, isize, u16, u32, i32); impl From> for Result { fn from(result: WindowsResult) -> Self { match result { - WindowsResult::Err(error) => Self::Err(error), - WindowsResult::Ok(ok) => Self::Ok(ok), + WindowsResult::Err(error) => Err(error), + WindowsResult::Ok(ok) => Ok(ok), } } } @@ -221,7 +224,7 @@ impl WindowsApi { let mut monitors: Vec<(String, isize)> = vec![]; let monitors_ref: &mut Vec<(String, isize)> = monitors.as_mut(); Self::enum_display_monitors( - Option::Some(windows_callbacks::valid_display_monitors), + Some(windows_callbacks::valid_display_monitors), monitors_ref as *mut Vec<(String, isize)> as isize, )?; @@ -230,9 +233,47 @@ impl WindowsApi { pub fn load_monitor_information(monitors: &mut Ring) -> Result<()> { Self::enum_display_monitors( - Option::Some(windows_callbacks::enum_display_monitor), + Some(windows_callbacks::enum_display_monitor), monitors as *mut Ring as isize, - ) + )?; + + Ok(()) + } + + pub fn enum_display_devices( + index: u32, + lp_device: Option<[u8; 32]>, + ) -> Result { + #[allow(clippy::option_if_let_else)] + let lp_device = if let Some(lp_device) = lp_device { + PCSTR::from_raw(lp_device.as_ptr()) + } else { + PCSTR::null() + }; + + let mut display_device = DISPLAY_DEVICEA { + cb: u32::try_from(std::mem::size_of::())?, + ..Default::default() + }; + + match unsafe { + EnumDisplayDevicesA( + lp_device, + index, + std::ptr::addr_of_mut!(display_device), + EDD_GET_DEVICE_INTERFACE_NAME, + ) + } + .ok() + { + Ok(_) => {} + Err(error) => { + tracing::error!("enum_display_devices: {}", error); + return Err(error.into()); + } + } + + Ok(display_device) } pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> Result<()> { @@ -245,7 +286,7 @@ impl WindowsApi { if let Some(workspace) = monitor.workspaces_mut().front_mut() { // EnumWindows will enumerate through windows on all monitors Self::enum_windows( - Option::Some(windows_callbacks::enum_window), + Some(windows_callbacks::enum_window), workspace.containers_mut() as *mut VecDeque as isize, )?; diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index 6b1529eb2..f192beaac 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -40,6 +40,7 @@ use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL; use crate::BORDER_COLOUR_CURRENT; use crate::BORDER_RECT; use crate::BORDER_WIDTH; +use crate::DISPLAY_INDEX_PREFERENCES; use crate::MONITOR_INDEX_PREFERENCES; use crate::TRANSPARENCY_COLOUR; @@ -72,7 +73,32 @@ pub extern "system" fn enum_display_monitor( } } - if let Ok(m) = WindowsApi::monitor(hmonitor.0) { + let current_index = monitors.elements().len(); + + if let Ok(mut m) = WindowsApi::monitor(hmonitor.0) { + #[allow(clippy::cast_possible_truncation)] + if let Ok(d) = WindowsApi::enum_display_devices(current_index as u32, None) { + let name = String::from_utf8_lossy(&d.DeviceName); + let clean_name = name + .replace('\u{0000}', "") + .trim_start_matches(r"\\.\") + .to_string(); + + if clean_name.eq(m.name()) { + if let Ok(device) = WindowsApi::enum_display_devices(0, Some(d.DeviceName)) { + let id = String::from_utf8_lossy(&device.DeviceID); + let clean_id = id.replace('\u{0000}', ""); + + let mut split: Vec<_> = clean_id.split('#').collect(); + split.remove(0); + split.remove(split.len() - 1); + + m.set_device(Option::from(split[0].to_string())); + m.set_device_id(Option::from(split.join("-"))); + } + } + } + let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock(); let mut index_preference = None; for (index, monitor_size) in &*monitor_index_preferences { @@ -81,6 +107,15 @@ pub extern "system" fn enum_display_monitor( } } + let display_index_preferences = DISPLAY_INDEX_PREFERENCES.lock(); + for (index, device) in &*display_index_preferences { + if let Some(known_device) = m.device() { + if device == known_device { + index_preference = Option::from(index); + } + } + } + if let Some(preference) = index_preference { let current_len = monitors.elements().len(); if *preference > current_len { diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 216f9e786..56123d60f 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -416,6 +416,14 @@ struct MonitorIndexPreference { bottom: i32, } +#[derive(Parser, AhkFunction)] +struct DisplayIndexPreference { + /// Preferred monitor index (zero-indexed) + index_preference: usize, + /// Display name as identified in komorebic state + display: String, +} + #[derive(Parser, AhkFunction)] struct EnsureWorkspaces { /// Monitor index (zero-indexed) @@ -930,6 +938,9 @@ enum SubCommand { /// Set the monitor index preference for a monitor identified using its size #[clap(arg_required_else_help = true)] MonitorIndexPreference(MonitorIndexPreference), + /// Set the display index preference for a monitor identified using its display name + #[clap(arg_required_else_help = true)] + DisplayIndexPreference(DisplayIndexPreference), /// Create at least this many workspaces for the specified monitor #[clap(arg_required_else_help = true)] EnsureWorkspaces(EnsureWorkspaces), @@ -1890,6 +1901,12 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue .as_bytes()?, )?; } + SubCommand::DisplayIndexPreference(arg) => { + send_message( + &SocketMessage::DisplayIndexPreference(arg.index_preference, arg.display) + .as_bytes()?, + )?; + } SubCommand::EnsureWorkspaces(workspaces) => { send_message( &SocketMessage::EnsureWorkspaces(workspaces.monitor, workspaces.workspace_count)