@@ -134,7 +134,7 @@ impl WindowManager { |
| 134 | 134 | let should_float = rule_actions.floating.unwrap_or_else(|| self.conn.should_float(window)); |
| 135 | 135 | |
| 136 | 136 | // Subscribe to events on the window |
| 137 | | - // Floating windows get POINTER_MOTION for edge resize cursors |
| 137 | + // Floating windows get POINTER_MOTION for edge resize cursor feedback |
| 138 | 138 | let base_events = EventMask::ENTER_WINDOW |
| 139 | 139 | | EventMask::FOCUS_CHANGE |
| 140 | 140 | | EventMask::PROPERTY_CHANGE |
@@ -175,6 +175,19 @@ impl WindowManager { |
| 175 | 175 | } |
| 176 | 176 | |
| 177 | 177 | pub fn handle_event(&mut self, event: Event) -> Result<()> { |
| 178 | + // Log events to debug cursor flicker |
| 179 | + match &event { |
| 180 | + Event::MotionNotify(e) => { |
| 181 | + tracing::info!("MOTION: window={} pos=({},{})", e.event, e.root_x, e.root_y); |
| 182 | + } |
| 183 | + Event::EnterNotify(e) => { |
| 184 | + tracing::info!("ENTER: window={}", e.event); |
| 185 | + } |
| 186 | + Event::ButtonPress(e) => { |
| 187 | + tracing::info!("BUTTON: window={} btn={}", e.event, e.detail); |
| 188 | + } |
| 189 | + _ => {} |
| 190 | + } |
| 178 | 191 | match event { |
| 179 | 192 | Event::MapRequest(e) => self.handle_map_request(e)?, |
| 180 | 193 | Event::ConfigureRequest(e) => self.handle_configure_request(e)?, |
@@ -256,7 +269,7 @@ impl WindowManager { |
| 256 | 269 | let should_float = rule_actions.floating.unwrap_or_else(|| self.conn.should_float(window)); |
| 257 | 270 | |
| 258 | 271 | // Subscribe to events on the window |
| 259 | | - // Floating windows get POINTER_MOTION for edge resize cursors |
| 272 | + // Floating windows get POINTER_MOTION for edge resize cursor feedback |
| 260 | 273 | let base_events = EventMask::ENTER_WINDOW |
| 261 | 274 | | EventMask::FOCUS_CHANGE |
| 262 | 275 | | EventMask::PROPERTY_CHANGE |
@@ -449,8 +462,10 @@ impl WindowManager { |
| 449 | 462 | start_y: event.root_y, |
| 450 | 463 | start_geometry: geometry, |
| 451 | 464 | }); |
| 452 | | - // Grab pointer for motion events |
| 453 | | - self.conn.grab_pointer(target)?; |
| 465 | + // Clear window cursor to prevent conflict with grab cursor |
| 466 | + self.conn.clear_window_cursor(target)?; |
| 467 | + // Grab pointer for motion events (use fleur/move cursor) |
| 468 | + self.conn.grab_pointer(Some(self.conn.cursor_move))?; |
| 454 | 469 | return Ok(()); |
| 455 | 470 | } else if event.detail == 3 { |
| 456 | 471 | // Mod+Button3 = Resize (quadrant-based edge detection) |
@@ -463,7 +478,10 @@ impl WindowManager { |
| 463 | 478 | start_geometry: geometry, |
| 464 | 479 | edge, |
| 465 | 480 | }); |
| 466 | | - self.conn.grab_pointer(target)?; |
| 481 | + // Clear window cursor to prevent conflict with grab cursor |
| 482 | + self.conn.clear_window_cursor(target)?; |
| 483 | + let cursor = self.cursor_for_edge(edge); |
| 484 | + self.conn.grab_pointer(Some(cursor))?; |
| 467 | 485 | return Ok(()); |
| 468 | 486 | } |
| 469 | 487 | } |
@@ -492,7 +510,10 @@ impl WindowManager { |
| 492 | 510 | edge, |
| 493 | 511 | }); |
| 494 | 512 | tracing::debug!("Grabbing pointer for resize, geometry={:?}", geometry); |
| 495 | | - self.conn.grab_pointer(window)?; |
| 513 | + // Clear window cursor to prevent conflict with grab cursor |
| 514 | + self.conn.clear_window_cursor(window)?; |
| 515 | + let cursor = self.cursor_for_edge(edge); |
| 516 | + self.conn.grab_pointer(Some(cursor))?; |
| 496 | 517 | self.conn.flush()?; |
| 497 | 518 | return Ok(()); |
| 498 | 519 | } |
@@ -549,11 +570,19 @@ impl WindowManager { |
| 549 | 570 | fn handle_button_release(&mut self, event: ButtonReleaseEvent) -> Result<()> { |
| 550 | 571 | tracing::debug!("ButtonRelease: button={}, window={}, in_drag={}", |
| 551 | 572 | event.detail, event.event, self.drag_state.is_some()); |
| 552 | | - if self.drag_state.is_some() { |
| 573 | + if let Some(drag) = self.drag_state.take() { |
| 553 | 574 | tracing::debug!("Ending drag operation"); |
| 554 | | - self.drag_state = None; |
| 555 | 575 | self.conn.ungrab_pointer()?; |
| 556 | 576 | self.conn.flush()?; |
| 577 | + |
| 578 | + // Restore edge cursor if pointer is still over the dragged floating window |
| 579 | + let window = match drag { |
| 580 | + DragState::Move { window, .. } | DragState::Resize { window, .. } => window, |
| 581 | + }; |
| 582 | + if self.is_floating(window) { |
| 583 | + // Use the release event position to update cursor |
| 584 | + self.update_edge_cursor(window, event.root_x, event.root_y)?; |
| 585 | + } |
| 557 | 586 | } |
| 558 | 587 | Ok(()) |
| 559 | 588 | } |
@@ -565,11 +594,13 @@ impl WindowManager { |
| 565 | 594 | let is_managed = self.windows.contains_key(&window); |
| 566 | 595 | let is_float = is_managed && self.is_floating(window); |
| 567 | 596 | |
| 597 | + // Debug: log all motion events to track down cursor flicker |
| 598 | + tracing::debug!( |
| 599 | + "MotionNotify: window={} managed={} floating={} root_xy=({},{}) event_xy=({},{})", |
| 600 | + window, is_managed, is_float, event.root_x, event.root_y, event.event_x, event.event_y |
| 601 | + ); |
| 602 | + |
| 568 | 603 | if is_float { |
| 569 | | - tracing::trace!( |
| 570 | | - "Motion on floating window {}: root({},{}) event({},{})", |
| 571 | | - window, event.root_x, event.root_y, event.event_x, event.event_y |
| 572 | | - ); |
| 573 | 604 | self.update_edge_cursor(window, event.root_x, event.root_y)?; |
| 574 | 605 | } |
| 575 | 606 | return Ok(()); |
@@ -620,9 +651,11 @@ impl WindowManager { |
| 620 | 651 | /// Handle pointer motion for edge cursor changes on floating windows. |
| 621 | 652 | fn update_edge_cursor(&mut self, window: u32, root_x: i16, root_y: i16) -> Result<()> { |
| 622 | 653 | if !self.is_floating(window) { |
| 623 | | - // Not a floating window, ensure cursor is normal |
| 624 | | - if self.current_edge_cursor.is_some() { |
| 625 | | - self.conn.set_window_cursor(window, self.conn.cursor_normal)?; |
| 654 | + // Not a floating window, clear any edge cursor state |
| 655 | + if let Some((old_window, old_edge)) = self.current_edge_cursor { |
| 656 | + if old_edge != ResizeEdge::None { |
| 657 | + self.conn.clear_window_cursor(old_window)?; |
| 658 | + } |
| 626 | 659 | self.current_edge_cursor = None; |
| 627 | 660 | } |
| 628 | 661 | return Ok(()); |
@@ -637,28 +670,29 @@ impl WindowManager { |
| 637 | 670 | return Ok(()); // No change needed |
| 638 | 671 | } |
| 639 | 672 | |
| 640 | | - // Get the appropriate cursor for this edge |
| 641 | | - let cursor = match edge { |
| 642 | | - ResizeEdge::TopLeft => self.conn.cursor_top_left, |
| 643 | | - ResizeEdge::Top => self.conn.cursor_top, |
| 644 | | - ResizeEdge::TopRight => self.conn.cursor_top_right, |
| 645 | | - ResizeEdge::Left => self.conn.cursor_left, |
| 646 | | - ResizeEdge::Right => self.conn.cursor_right, |
| 647 | | - ResizeEdge::BottomLeft => self.conn.cursor_bottom_left, |
| 648 | | - ResizeEdge::Bottom => self.conn.cursor_bottom, |
| 649 | | - ResizeEdge::BottomRight => self.conn.cursor_bottom_right, |
| 650 | | - ResizeEdge::None => self.conn.cursor_normal, |
| 651 | | - }; |
| 652 | | - |
| 653 | | - // Set cursor on the window |
| 654 | | - self.conn.set_window_cursor(window, cursor)?; |
| 655 | | - self.conn.flush()?; |
| 656 | | - |
| 657 | 673 | if edge != ResizeEdge::None { |
| 658 | | - tracing::debug!("Set resize cursor {:?} for floating window {} edge", edge, window); |
| 674 | + let cursor = match edge { |
| 675 | + ResizeEdge::TopLeft => self.conn.cursor_top_left, |
| 676 | + ResizeEdge::Top => self.conn.cursor_top, |
| 677 | + ResizeEdge::TopRight => self.conn.cursor_top_right, |
| 678 | + ResizeEdge::Left => self.conn.cursor_left, |
| 679 | + ResizeEdge::Right => self.conn.cursor_right, |
| 680 | + ResizeEdge::BottomLeft => self.conn.cursor_bottom_left, |
| 681 | + ResizeEdge::Bottom => self.conn.cursor_bottom, |
| 682 | + ResizeEdge::BottomRight => self.conn.cursor_bottom_right, |
| 683 | + ResizeEdge::None => unreachable!(), |
| 684 | + }; |
| 685 | + self.conn.set_window_cursor(window, cursor)?; |
| 686 | + self.conn.flush()?; |
| 659 | 687 | self.current_edge_cursor = Some((window, edge)); |
| 688 | + } else if let Some((old_window, old_edge)) = current { |
| 689 | + if old_edge != ResizeEdge::None { |
| 690 | + self.conn.clear_window_cursor(old_window)?; |
| 691 | + self.conn.flush()?; |
| 692 | + } |
| 693 | + self.current_edge_cursor = Some((window, ResizeEdge::None)); |
| 660 | 694 | } else { |
| 661 | | - self.current_edge_cursor = None; |
| 695 | + self.current_edge_cursor = Some((window, ResizeEdge::None)); |
| 662 | 696 | } |
| 663 | 697 | |
| 664 | 698 | Ok(()) |
@@ -1696,6 +1730,21 @@ impl WindowManager { |
| 1696 | 1730 | .unwrap_or(false) |
| 1697 | 1731 | } |
| 1698 | 1732 | |
| 1733 | + /// Get the appropriate cursor for a resize edge. |
| 1734 | + fn cursor_for_edge(&self, edge: ResizeEdge) -> u32 { |
| 1735 | + match edge { |
| 1736 | + ResizeEdge::TopLeft => self.conn.cursor_top_left, |
| 1737 | + ResizeEdge::Top => self.conn.cursor_top, |
| 1738 | + ResizeEdge::TopRight => self.conn.cursor_top_right, |
| 1739 | + ResizeEdge::Left => self.conn.cursor_left, |
| 1740 | + ResizeEdge::Right => self.conn.cursor_right, |
| 1741 | + ResizeEdge::BottomLeft => self.conn.cursor_bottom_left, |
| 1742 | + ResizeEdge::Bottom => self.conn.cursor_bottom, |
| 1743 | + ResizeEdge::BottomRight => self.conn.cursor_bottom_right, |
| 1744 | + ResizeEdge::None => self.conn.cursor_normal, |
| 1745 | + } |
| 1746 | + } |
| 1747 | + |
| 1699 | 1748 | fn get_floating_geometry(&self, window: u32) -> Rect { |
| 1700 | 1749 | self.windows |
| 1701 | 1750 | .get(&window) |
@@ -1953,7 +2002,7 @@ impl WindowManager { |
| 1953 | 2002 | win.floating = false; |
| 1954 | 2003 | } |
| 1955 | 2004 | |
| 1956 | | - // Remove POINTER_MOTION event mask (no longer need edge detection) |
| 2005 | + // Standard event mask for tiled window |
| 1957 | 2006 | self.conn.select_input( |
| 1958 | 2007 | window, |
| 1959 | 2008 | EventMask::ENTER_WINDOW |
@@ -1964,7 +2013,7 @@ impl WindowManager { |
| 1964 | 2013 | |
| 1965 | 2014 | // Clear edge cursor state if this window had one |
| 1966 | 2015 | if self.current_edge_cursor.map(|(w, _)| w) == Some(window) { |
| 1967 | | - self.conn.set_window_cursor(window, self.conn.cursor_normal)?; |
| 2016 | + self.conn.clear_window_cursor(window)?; |
| 1968 | 2017 | self.current_edge_cursor = None; |
| 1969 | 2018 | } |
| 1970 | 2019 | |
@@ -2010,7 +2059,7 @@ impl WindowManager { |
| 2010 | 2059 | win.floating_geometry = geometry; |
| 2011 | 2060 | } |
| 2012 | 2061 | |
| 2013 | | - // Add POINTER_MOTION event mask for edge detection |
| 2062 | + // Event mask for floating window (includes POINTER_MOTION for edge cursor) |
| 2014 | 2063 | self.conn.select_input( |
| 2015 | 2064 | window, |
| 2016 | 2065 | EventMask::ENTER_WINDOW |