@@ -39,6 +39,8 @@ pub struct WindowManager { |
| 39 | 39 | pub last_warp: std::time::Instant, |
| 40 | 40 | /// Frame manager for title bars |
| 41 | 41 | pub frames: FrameManager, |
| 42 | + /// Focus history stack - most recently focused windows first (per workspace) |
| 43 | + pub focus_history: Vec<XWindow>, |
| 42 | 44 | } |
| 43 | 45 | |
| 44 | 46 | impl WindowManager { |
@@ -126,6 +128,7 @@ impl WindowManager { |
| 126 | 128 | i3_ipc_server, |
| 127 | 129 | last_warp: std::time::Instant::now(), |
| 128 | 130 | frames: FrameManager::new(), |
| 131 | + focus_history: Vec::new(), |
| 129 | 132 | }) |
| 130 | 133 | } |
| 131 | 134 | |
@@ -409,10 +412,19 @@ impl WindowManager { |
| 409 | 412 | self.workspaces[ws_idx].tree.remove(window); |
| 410 | 413 | } |
| 411 | 414 | |
| 415 | + // Remove from focus history |
| 416 | + self.focus_history.retain(|&w| w != window); |
| 417 | + |
| 412 | 418 | // Update focus if this was the focused window |
| 413 | 419 | if self.focused_window == Some(window) { |
| 414 | | - // Try to focus another window on that workspace |
| 415 | | - self.focused_window = self.workspaces[ws_idx].tree.first_window() |
| 420 | + // Find next window from focus history that's on this workspace |
| 421 | + let next_from_history = self.focus_history.iter() |
| 422 | + .find(|&&w| self.windows.get(&w).map(|win| win.workspace == ws_idx).unwrap_or(false)) |
| 423 | + .copied(); |
| 424 | + |
| 425 | + // Fall back to first window in tree or floating list if no history |
| 426 | + self.focused_window = next_from_history |
| 427 | + .or_else(|| self.workspaces[ws_idx].tree.first_window()) |
| 416 | 428 | .or_else(|| self.workspaces[ws_idx].floating.last().copied()); |
| 417 | 429 | self.workspaces[ws_idx].focused = self.focused_window; |
| 418 | 430 | } |
@@ -466,10 +478,16 @@ impl WindowManager { |
| 466 | 478 | } |
| 467 | 479 | |
| 468 | 480 | /// Set focus to a window. |
| 469 | | - pub fn set_focus(&mut self, window: XWindow) -> Result<()> { |
| 481 | + /// If `warp_pointer` is true, the mouse pointer will be moved to the window center. |
| 482 | + /// Use true for keyboard navigation, false for mouse-initiated focus changes. |
| 483 | + pub fn set_focus(&mut self, window: XWindow, warp_pointer: bool) -> Result<()> { |
| 470 | 484 | self.focused_window = Some(window); |
| 471 | 485 | self.current_workspace_mut().focused = Some(window); |
| 472 | 486 | |
| 487 | + // Update focus history - move window to front |
| 488 | + self.focus_history.retain(|&w| w != window); |
| 489 | + self.focus_history.insert(0, window); |
| 490 | + |
| 473 | 491 | // Clear urgency when window receives focus |
| 474 | 492 | if let Some(win) = self.windows.get_mut(&window) { |
| 475 | 493 | if win.urgent { |
@@ -483,11 +501,13 @@ impl WindowManager { |
| 483 | 501 | self.update_borders()?; |
| 484 | 502 | |
| 485 | 503 | // Warp pointer to center of focused window (mouse follows focus) |
| 486 | | - if let Err(e) = self.conn.warp_pointer_to_window(window) { |
| 487 | | - tracing::warn!("Failed to warp pointer: {}", e); |
| 504 | + if warp_pointer { |
| 505 | + if let Err(e) = self.conn.warp_pointer_to_window(window) { |
| 506 | + tracing::warn!("Failed to warp pointer: {}", e); |
| 507 | + } |
| 508 | + // Record warp time to suppress EnterNotify feedback loop |
| 509 | + self.last_warp = std::time::Instant::now(); |
| 488 | 510 | } |
| 489 | | - // Record warp time to suppress EnterNotify feedback loop |
| 490 | | - self.last_warp = std::time::Instant::now(); |
| 491 | 511 | |
| 492 | 512 | Ok(()) |
| 493 | 513 | } |