multimonitor: fix focus tracking and spawn on mouse position
- SHA
c7ea6ce4dc83b60001cb128e07a2d45dfeb26e83- Parents
-
066e8f2 - Tree
5b6b8a0
c7ea6ce
c7ea6ce4dc83b60001cb128e07a2d45dfeb26e83066e8f2
5b6b8a0| Status | File | + | - |
|---|---|---|---|
| M |
gar/src/core/mod.rs
|
34 | 0 |
| M |
gar/src/x11/connection.rs
|
6 | 0 |
| M |
gar/src/x11/events.rs
|
9 | 3 |
gar/src/core/mod.rsmodified@@ -207,6 +207,29 @@ impl WindowManager { | ||
| 207 | 207 | self.monitors.iter().position(|m| m.active_workspace == workspace_idx) |
| 208 | 208 | } |
| 209 | 209 | |
| 210 | + /// Find the monitor index containing a point (x, y in root window coordinates). | |
| 211 | + pub fn monitor_idx_at_point(&self, x: i16, y: i16) -> usize { | |
| 212 | + self.monitors | |
| 213 | + .iter() | |
| 214 | + .position(|m| { | |
| 215 | + let g = &m.geometry; | |
| 216 | + x >= g.x && x < g.x + g.width as i16 && | |
| 217 | + y >= g.y && y < g.y + g.height as i16 | |
| 218 | + }) | |
| 219 | + .unwrap_or(self.focused_monitor) // Fallback to focused monitor | |
| 220 | + } | |
| 221 | + | |
| 222 | + /// Get the workspace that should receive new windows (based on mouse position). | |
| 223 | + /// This supports spawning windows on the monitor where the mouse is. | |
| 224 | + pub fn workspace_for_new_window(&self) -> usize { | |
| 225 | + if let Ok((x, y)) = self.conn.get_pointer_position() { | |
| 226 | + let monitor_idx = self.monitor_idx_at_point(x, y); | |
| 227 | + self.monitors[monitor_idx].active_workspace | |
| 228 | + } else { | |
| 229 | + self.focused_workspace | |
| 230 | + } | |
| 231 | + } | |
| 232 | + | |
| 210 | 233 | /// Calculate struts (reserved space for panels/docks) for a monitor. |
| 211 | 234 | fn calculate_struts(&self, _monitor_idx: usize) -> (u32, u32, u32, u32) { |
| 212 | 235 | if self.config.bar_height > 0 { |
@@ -602,6 +625,17 @@ impl WindowManager { | ||
| 602 | 625 | } |
| 603 | 626 | } |
| 604 | 627 | |
| 628 | + // Update focused workspace and monitor based on the window's workspace | |
| 629 | + // This is critical for focus-follows-mouse to work correctly with multimonitor | |
| 630 | + if let Some(win) = self.windows.get(&window) { | |
| 631 | + let ws_idx = win.workspace; | |
| 632 | + self.focused_workspace = ws_idx; | |
| 633 | + // Update focused_monitor to the monitor displaying this workspace (if visible) | |
| 634 | + if let Some(monitor_idx) = self.monitor_idx_for_workspace(ws_idx) { | |
| 635 | + self.focused_monitor = monitor_idx; | |
| 636 | + } | |
| 637 | + } | |
| 638 | + | |
| 605 | 639 | self.focused_window = Some(window); |
| 606 | 640 | self.current_workspace_mut().focused = Some(window); |
| 607 | 641 | |
gar/src/x11/connection.rsmodified@@ -504,6 +504,12 @@ impl Connection { | ||
| 504 | 504 | Ok(()) |
| 505 | 505 | } |
| 506 | 506 | |
| 507 | + /// Get the current mouse pointer position (root window coordinates). | |
| 508 | + pub fn get_pointer_position(&self) -> Result<(i16, i16), Error> { | |
| 509 | + let reply = self.conn.query_pointer(self.root)?.reply()?; | |
| 510 | + Ok((reply.root_x, reply.root_y)) | |
| 511 | + } | |
| 512 | + | |
| 507 | 513 | /// Set window border width and color. |
| 508 | 514 | pub fn set_border(&self, window: Window, width: u32, color: u32) -> Result<(), Error> { |
| 509 | 515 | let aux = ChangeWindowAttributesAux::new().border_pixel(color); |
gar/src/x11/events.rsmodified@@ -493,9 +493,15 @@ impl WindowManager { | ||
| 493 | 493 | // Check window rules first |
| 494 | 494 | let rule_actions = self.check_rules(window); |
| 495 | 495 | |
| 496 | - // Determine target workspace (rule or current) | |
| 497 | - let target_workspace = rule_actions.workspace.unwrap_or(self.focused_workspace + 1); | |
| 498 | - let target_idx = target_workspace.saturating_sub(1).min(self.workspaces.len() - 1); | |
| 496 | + // Determine target workspace: rule > mouse position > focused | |
| 497 | + // This makes windows spawn on the monitor where the mouse is | |
| 498 | + let target_idx = if let Some(ws) = rule_actions.workspace { | |
| 499 | + // Rule specifies workspace (1-indexed) | |
| 500 | + ws.saturating_sub(1).min(self.workspaces.len() - 1) | |
| 501 | + } else { | |
| 502 | + // Use workspace of monitor under mouse pointer | |
| 503 | + self.workspace_for_new_window() | |
| 504 | + }; | |
| 499 | 505 | |
| 500 | 506 | // Determine if window should float (rule > ICCCM/EWMH hints) |
| 501 | 507 | let should_float = rule_actions.floating.unwrap_or_else(|| self.conn.should_float(window)); |