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)