From 6b95bf95f9e4e37a5d1b9af8e637887b52541364 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Wed, 19 Mar 2025 18:35:43 -0700 Subject: [PATCH] fix(wm): unset all duplicate monitor serial ids This commit fixes a rare issue, seen exclusively with Acer monitors so far, where two monitors of the same model can have an identical serial number id. If we encounter a system which has two connected monitors with the same serial id number, the serial id number will be forcefully unset and blacklisted for the rest of the session. In this case, users must fall back to using device_id for options like display_index_preferences. Possibly a little overkill, but since this has been such a headache I'm going to opt for this approach over #1368 for now. --- komorebi/src/lib.rs | 2 + komorebi/src/monitor_reconciliator/mod.rs | 45 +++++++++++++++++++++-- komorebi/src/window_manager.rs | 3 ++ komorebi/src/windows_api.rs | 42 ++++++++++++++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index 8d87e90a..4e6ecc81 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -173,6 +173,8 @@ lazy_static! { matching_strategy: Option::from(MatchingStrategy::Equals), }), ])); + static ref DUPLICATE_MONITOR_SERIAL_IDS: Arc>> = + Arc::new(RwLock::new(Vec::new())); static ref SUBSCRIPTION_PIPES: Arc>> = Arc::new(Mutex::new(HashMap::new())); pub static ref SUBSCRIPTION_SOCKETS: Arc>> = diff --git a/komorebi/src/monitor_reconciliator/mod.rs b/komorebi/src/monitor_reconciliator/mod.rs index b9d57816..13c4e2c7 100644 --- a/komorebi/src/monitor_reconciliator/mod.rs +++ b/komorebi/src/monitor_reconciliator/mod.rs @@ -13,6 +13,7 @@ use crate::State; use crate::WindowManager; use crate::WindowsApi; use crate::DISPLAY_INDEX_PREFERENCES; +use crate::DUPLICATE_MONITOR_SERIAL_IDS; use crate::WORKSPACE_MATCHING_RULES; use crossbeam_channel::Receiver; use crossbeam_channel::Sender; @@ -84,9 +85,32 @@ pub fn insert_in_monitor_cache(serial_or_device_id: &str, monitor: Monitor) { } pub fn attached_display_devices() -> color_eyre::Result> { - Ok(win32_display_data::connected_displays_all() + let all_displays = win32_display_data::connected_displays_all() .flatten() - .map(|display| { + .collect::>(); + + let mut serial_id_map = HashMap::new(); + + for d in &all_displays { + if let Some(id) = &d.serial_number_id { + *serial_id_map.entry(id.clone()).or_insert(0) += 1; + } + } + + for d in &all_displays { + if let Some(id) = &d.serial_number_id { + if serial_id_map.get(id).copied().unwrap_or_default() > 1 { + let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write(); + if !dupes.contains(id) { + (*dupes).push(id.clone()); + } + } + } + } + + Ok(all_displays + .into_iter() + .map(|mut display| { let path = display.device_path; let (device, device_id) = if path.is_empty() { @@ -103,6 +127,13 @@ pub fn attached_display_devices() -> color_eyre::Result> { let name = display.device_name.trim_start_matches(r"\\.\").to_string(); let name = name.split('\\').collect::>()[0].to_string(); + if let Some(id) = &display.serial_number_id { + let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read(); + if dupes.contains(id) { + display.serial_number_id = None; + } + } + monitor::new( display.hmonitor, display.size.into(), @@ -270,9 +301,15 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // Make sure that in our state any attached displays have the latest Win32 data for monitor in wm.monitors_mut() { for attached in &attached_devices { - if attached.serial_number_id().eq(monitor.serial_number_id()) - || attached.device_id().eq(monitor.device_id()) + let serial_number_ids_match = if let (Some(attached_snid), Some(m_snid)) = + (attached.serial_number_id(), monitor.serial_number_id()) { + attached_snid.eq(m_snid) + } else { + false + }; + + if serial_number_ids_match || attached.device_id().eq(monitor.device_id()) { monitor.set_id(attached.id()); monitor.set_device(attached.device().clone()); monitor.set_device_id(attached.device_id().clone()); diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index f42eaca3..f8b2e035 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -84,6 +84,7 @@ use crate::Rgb; use crate::CUSTOM_FFM; use crate::DATA_DIR; use crate::DISPLAY_INDEX_PREFERENCES; +use crate::DUPLICATE_MONITOR_SERIAL_IDS; use crate::HIDING_BEHAVIOUR; use crate::HOME_DIR; use crate::IGNORE_IDENTIFIERS; @@ -218,6 +219,7 @@ pub struct GlobalState { pub name_change_on_launch_identifiers: Vec, pub monitor_index_preferences: HashMap, pub display_index_preferences: HashMap, + pub ignored_duplicate_monitor_serial_ids: Vec, pub workspace_rules: Vec, pub window_hiding_behaviour: HidingBehaviour, pub configuration_dir: PathBuf, @@ -273,6 +275,7 @@ impl Default for GlobalState { 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.read().clone(), + ignored_duplicate_monitor_serial_ids: DUPLICATE_MONITOR_SERIAL_IDS.read().clone(), workspace_rules: WORKSPACE_MATCHING_RULES.lock().clone(), window_hiding_behaviour: *HIDING_BEHAVIOUR.lock(), configuration_dir: HOME_DIR.clone(), diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index c25526cb..dfa4d6c0 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -153,6 +153,7 @@ use crate::windows_callbacks; use crate::Window; use crate::WindowManager; use crate::DISPLAY_INDEX_PREFERENCES; +use crate::DUPLICATE_MONITOR_SERIAL_IDS; use crate::MONITOR_INDEX_PREFERENCES; macro_rules! as_ptr { @@ -258,7 +259,30 @@ impl WindowsApi { let monitors = &mut wm.monitors; let monitor_usr_idx_map = &mut wm.monitor_usr_idx_map; - 'read: for display in win32_display_data::connected_displays_all().flatten() { + let all_displays = win32_display_data::connected_displays_all() + .flatten() + .collect::>(); + + let mut serial_id_map = HashMap::new(); + + for d in &all_displays { + if let Some(id) = &d.serial_number_id { + *serial_id_map.entry(id.clone()).or_insert(0) += 1; + } + } + + for d in &all_displays { + if let Some(id) = &d.serial_number_id { + if serial_id_map.get(id).copied().unwrap_or_default() > 1 { + let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write(); + if !dupes.contains(id) { + (*dupes).push(id.clone()); + } + } + } + } + + 'read: for mut display in all_displays { let path = display.device_path.clone(); let (device, device_id) = if path.is_empty() { @@ -281,6 +305,13 @@ impl WindowsApi { } } + if let Some(id) = &display.serial_number_id { + let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read(); + if dupes.contains(id) { + display.serial_number_id = None; + } + } + let m = monitor::new( display.hmonitor, display.size.into(), @@ -972,7 +1003,7 @@ impl WindowsApi { } pub fn monitor(hmonitor: isize) -> Result { - for display in win32_display_data::connected_displays_all().flatten() { + for mut display in win32_display_data::connected_displays_all().flatten() { if display.hmonitor == hmonitor { let path = display.device_path; @@ -990,6 +1021,13 @@ impl WindowsApi { let name = display.device_name.trim_start_matches(r"\\.\").to_string(); let name = name.split('\\').collect::>()[0].to_string(); + if let Some(id) = &display.serial_number_id { + let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read(); + if dupes.contains(id) { + display.serial_number_id = None; + } + } + let monitor = monitor::new( hmonitor, display.size.into(),