← KD100

Fix cursor hover not preventing OSD auto-hide

2680d8215587960a0dd1ac6b4b1eab6d01a2f7ec · Claude · Feb 7, 2026 05:12

The auto-hide check was running BEFORE X11 events were processed,
so cursor_inside was never updated in time. Reordered the logic:

1. Process X11 events first (updates cursor_inside from EnterNotify)
2. Then check auto-hide (now respects the updated cursor_inside)

https://claude.ai/code/session_01H5sr8TkRyt6U1vdkVZRRvy

+68 −68 · 1 file changed

  • src/osd.c +68 −68
diff --git a/src/osd.c b/src/osd.c
index 79643c6..da5aa24 100644
--- a/src/osd.c
+++ b/src/osd.c
@@ -535,79 +535,79 @@ void osd_update(osd_state_t* osd) {
     Window win = (Window)osd->window;
     long now = osd_get_time_ms();
 
-    // Auto-hide check: if auto_show is enabled, OSD is visible, cursor is outside, and enough time passed
-    if (osd->auto_show && osd->mode != OSD_MODE_HIDDEN && !osd->cursor_inside && osd->last_action_time_ms > 0) {
-        long time_since_action = now - osd->last_action_time_ms;
-        if (time_since_action > osd->display_duration_ms) {
-            osd_hide(osd);
-            return;
-        }
-    }
-
-    // Only process X11 events if visible
-    if (osd->mode == OSD_MODE_HIDDEN) return;
-
-    while (XPending(dpy)) {
-        XEvent event;
-        XNextEvent(dpy, &event);
-
-        switch (event.type) {
-            case Expose:
-                if (event.xexpose.count == 0) {
-                    osd_redraw(osd);
-                }
-                break;
-
-            case ButtonPress:
-                if (event.xbutton.button == Button1) {
-                    // Start dragging from anywhere on the window
-                    osd->dragging = 1;
-                    osd->drag_start_x = event.xbutton.x_root - osd->pos_x;
-                    osd->drag_start_y = event.xbutton.y_root - osd->pos_y;
-                }
-                break;
-
-            case ButtonRelease:
-                if (event.xbutton.button == Button1) {
-                    if (osd->dragging) {
-                        // If barely moved and clicked in title bar, toggle mode
-                        int dx = event.xbutton.x_root - osd->pos_x - osd->drag_start_x;
-                        int dy = event.xbutton.y_root - osd->pos_y - osd->drag_start_y;
-                        int title_height = osd->font_size + 12;
-                        if (abs(dx) < 5 && abs(dy) < 5 && event.xbutton.y < title_height) {
-                            osd_toggle_mode(osd);
+    // Process X11 events first (to update cursor_inside before auto-hide check)
+    if (osd->mode != OSD_MODE_HIDDEN) {
+        while (XPending(dpy)) {
+            XEvent event;
+            XNextEvent(dpy, &event);
+
+            switch (event.type) {
+                case Expose:
+                    if (event.xexpose.count == 0) {
+                        osd_redraw(osd);
+                    }
+                    break;
+
+                case ButtonPress:
+                    if (event.xbutton.button == Button1) {
+                        // Start dragging from anywhere on the window
+                        osd->dragging = 1;
+                        osd->drag_start_x = event.xbutton.x_root - osd->pos_x;
+                        osd->drag_start_y = event.xbutton.y_root - osd->pos_y;
+                    }
+                    break;
+
+                case ButtonRelease:
+                    if (event.xbutton.button == Button1) {
+                        if (osd->dragging) {
+                            // If barely moved and clicked in title bar, toggle mode
+                            int dx = event.xbutton.x_root - osd->pos_x - osd->drag_start_x;
+                            int dy = event.xbutton.y_root - osd->pos_y - osd->drag_start_y;
+                            int title_height = osd->font_size + 12;
+                            if (abs(dx) < 5 && abs(dy) < 5 && event.xbutton.y < title_height) {
+                                osd_toggle_mode(osd);
+                            }
+                            osd->dragging = 0;
                         }
-                        osd->dragging = 0;
                     }
-                }
-                break;
-
-            case MotionNotify:
-                if (osd->dragging) {
-                    // Consume all pending motion events (coalesce)
-                    while (XCheckTypedWindowEvent(dpy, win, MotionNotify, &event));
-
-                    osd->pos_x = event.xmotion.x_root - osd->drag_start_x;
-                    osd->pos_y = event.xmotion.y_root - osd->drag_start_y;
-                    XMoveWindow(dpy, win, osd->pos_x, osd->pos_y);
-                    XFlush(dpy);
-                }
-                break;
+                    break;
 
-            case ConfigureNotify:
-                // Window was moved or resized
-                break;
+                case MotionNotify:
+                    if (osd->dragging) {
+                        // Consume all pending motion events (coalesce)
+                        while (XCheckTypedWindowEvent(dpy, win, MotionNotify, &event));
 
-            case EnterNotify:
-                // Cursor entered the window - don't auto-hide while hovering
-                osd->cursor_inside = 1;
-                break;
+                        osd->pos_x = event.xmotion.x_root - osd->drag_start_x;
+                        osd->pos_y = event.xmotion.y_root - osd->drag_start_y;
+                        XMoveWindow(dpy, win, osd->pos_x, osd->pos_y);
+                        XFlush(dpy);
+                    }
+                    break;
+
+                case ConfigureNotify:
+                    // Window was moved or resized
+                    break;
+
+                case EnterNotify:
+                    // Cursor entered the window - don't auto-hide while hovering
+                    osd->cursor_inside = 1;
+                    break;
+
+                case LeaveNotify:
+                    // Cursor left the window - restart auto-hide timer
+                    osd->cursor_inside = 0;
+                    osd->last_action_time_ms = osd_get_time_ms();
+                    break;
+            }
+        }
+    }
 
-            case LeaveNotify:
-                // Cursor left the window - restart auto-hide timer
-                osd->cursor_inside = 0;
-                osd->last_action_time_ms = osd_get_time_ms();
-                break;
+    // Auto-hide check: AFTER processing events so cursor_inside is up-to-date
+    if (osd->auto_show && osd->mode != OSD_MODE_HIDDEN && !osd->cursor_inside && osd->last_action_time_ms > 0) {
+        long time_since_action = now - osd->last_action_time_ms;
+        if (time_since_action > osd->display_duration_ms) {
+            osd_hide(osd);
+            return;
         }
     }