From 2441b2cefa0b0178a486d877f3495d2d10ad5bb3 Mon Sep 17 00:00:00 2001 From: Pete Scheyen Date: Sat, 25 Apr 2026 11:51:52 -0400 Subject: [PATCH 1/2] menu: restore focus to feh window after hiding menu focus was set to menu_cover on menu open but never restore on hide (say after performing a File->Delete); on Xwayland RevertToPointerRoot doesn't recover it; added an explicit call to XSetInputFocus back to fehwin->win before destroying the cover window. --- src/menu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/menu.c b/src/menu.c index 1fde292c..34b85ae6 100644 --- a/src/menu.c +++ b/src/menu.c @@ -514,6 +514,8 @@ void feh_menu_hide(feh_menu * m, int func_free) if (m == menu_root) { if (menu_cover) { D(("DESTROYING menu cover\n")); + if (m->fehwin) + XSetInputFocus(disp, m->fehwin->win, RevertToPointerRoot, CurrentTime); XDestroyWindow(disp, menu_cover); menu_cover = 0; } From 366dbbec22084d259c5e486ed309bed91e3608d4 Mon Sep 17 00:00:00 2001 From: Pete Scheyen Date: Sat, 25 Apr 2026 14:51:42 -0400 Subject: [PATCH 2/2] Fix zoom, pan, and display glitches on Wayland. BUG REPORT: At random times when pressing the zoom or pan keys, the zoom or pan will appear to be ignored or in the wrong direction. User experience is a very random experience. Two related fixes: 1. Preserve zoom and pan across ConfigureNotify events (it seems the Wayland compositor sends these events at unexpected times, triggering the had_resize code path and resetting zoom/recentering the image and clobbering the user's intent. Added a manual_zoom flag to winwidget, set on user zoom input and cleared on image load. When this flag is set, skip the zoom/position reset in the had_resize block. 2. Force an immediate frame presentation after each render since Wayland batches XSetWindowBackgroundPixmap/XClearWindow requests. --- src/events.c | 2 ++ src/keyevents.c | 2 ++ src/winwidget.c | 49 ++++++++++++++++++++++++++++--------------------- src/winwidget.h | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/events.c b/src/events.c index bafc5170..8b450b22 100644 --- a/src/events.c +++ b/src/events.c @@ -253,6 +253,7 @@ static void feh_event_handle_ButtonPress(XEvent * ev) } else if (feh_is_bb(EVENT_zoom_in, button, state)) { D(("Zoom_In Button Press event\n")); D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); + winwid->manual_zoom = 1; winwid->click_offset_x = ev->xbutton.x; winwid->click_offset_y = ev->xbutton.y; winwid->old_zoom = winwid->zoom; @@ -281,6 +282,7 @@ static void feh_event_handle_ButtonPress(XEvent * ev) } else if (feh_is_bb(EVENT_zoom_out, button, state)) { D(("Zoom_Out Button Press event\n")); D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); + winwid->manual_zoom = 1; winwid->click_offset_x = ev->xbutton.x; winwid->click_offset_y = ev->xbutton.y; winwid->old_zoom = winwid->zoom; diff --git a/src/keyevents.c b/src/keyevents.c index 2f9b1d67..528af1f6 100644 --- a/src/keyevents.c +++ b/src/keyevents.c @@ -602,6 +602,7 @@ void feh_event_handle_generic(winwidget winwid, unsigned int state, KeySym keysy feh_event_invoke_action(winwid, 9); } else if (feh_is_kp(EVENT_zoom_in, state, keysym, button)) { + winwid->manual_zoom = 1; winwid->old_zoom = winwid->zoom; winwid->zoom = winwid->zoom * opt.zoom_rate; @@ -616,6 +617,7 @@ void feh_event_handle_generic(winwidget winwid, unsigned int state, KeySym keysy winwidget_render_image(winwid, 0, 0); } else if (feh_is_kp(EVENT_zoom_out, state, keysym, button)) { + winwid->manual_zoom = 1; winwid->old_zoom = winwid->zoom; winwid->zoom = winwid->zoom / opt.zoom_rate; diff --git a/src/winwidget.c b/src/winwidget.c index 407fa5e1..e407dde6 100644 --- a/src/winwidget.c +++ b/src/winwidget.c @@ -485,32 +485,36 @@ void winwidget_render_image(winwidget winwid, int resize, int force_alias) double required_zoom = 1.0; feh_calc_needed_zoom(&required_zoom, winwid->im_w, winwid->im_h, winwid->w, winwid->h); - winwid->zoom = opt.default_zoom ? (0.01 * opt.default_zoom) : 1.0; - - if ((opt.scale_down || (winwid->full_screen && !opt.default_zoom)) - && winwid->zoom > required_zoom) - winwid->zoom = required_zoom; - else if ((opt.zoom_mode && required_zoom > 1) - && (!opt.default_zoom || required_zoom < winwid->zoom)) - winwid->zoom = required_zoom; - - if (opt.offset_flags & XValue) { - if (opt.offset_flags & XNegative) { - winwid->im_x = winwid->w - (winwid->im_w * winwid->zoom) - opt.offset_x; + if (!winwid->manual_zoom) { + winwid->zoom = opt.default_zoom ? (0.01 * opt.default_zoom) : 1.0; + + if ((opt.scale_down || (winwid->full_screen && !opt.default_zoom)) + && winwid->zoom > required_zoom) + winwid->zoom = required_zoom; + else if ((opt.zoom_mode && required_zoom > 1) + && (!opt.default_zoom || required_zoom < winwid->zoom)) + winwid->zoom = required_zoom; + + if (opt.offset_flags & XValue) { + if (opt.offset_flags & XNegative) { + winwid->im_x = winwid->w - (winwid->im_w * winwid->zoom) - opt.offset_x; + } else { + winwid->im_x = - opt.offset_x * winwid->zoom; + } } else { - winwid->im_x = - opt.offset_x * winwid->zoom; + winwid->im_x = (int) (winwid->w - (winwid->im_w * winwid->zoom)) >> 1; } - } else { - winwid->im_x = (int) (winwid->w - (winwid->im_w * winwid->zoom)) >> 1; - } - if (opt.offset_flags & YValue) { - if (opt.offset_flags & YNegative) { - winwid->im_y = winwid->h - (winwid->im_h * winwid->zoom) - opt.offset_y; + if (opt.offset_flags & YValue) { + if (opt.offset_flags & YNegative) { + winwid->im_y = winwid->h - (winwid->im_h * winwid->zoom) - opt.offset_y; + } else { + winwid->im_y = - opt.offset_y * winwid->zoom; + } } else { - winwid->im_y = - opt.offset_y * winwid->zoom; + winwid->im_y = (int) (winwid->h - (winwid->im_h * winwid->zoom)) >> 1; } } else { - winwid->im_y = (int) (winwid->h - (winwid->im_h * winwid->zoom)) >> 1; + winwidget_sanitise_offsets(winwid); } } @@ -618,6 +622,7 @@ void winwidget_render_image(winwidget winwid, int resize, int force_alias) XSetWindowBackgroundPixmap(disp, winwid->win, winwid->bg_pmap); XClearWindow(disp, winwid->win); + XFlush(disp); return; } @@ -640,6 +645,7 @@ void winwidget_render_image_cached(winwidget winwid) feh_draw_info(winwid); XSetWindowBackgroundPixmap(disp, winwid->win, winwid->bg_pmap); XClearWindow(disp, winwid->win); + XFlush(disp); } double feh_calc_needed_zoom(double *zoom, int orig_w, int orig_h, int dest_w, int dest_h) @@ -1103,6 +1109,7 @@ void winwidget_reset_image(winwidget winwid) winwid->im_x = 0; winwid->im_y = 0; } + winwid->manual_zoom = 0; winwid->im_angle = 0.0; winwid->has_rotated = 0; return; diff --git a/src/winwidget.h b/src/winwidget.h index 0894b5ae..e0cd2839 100644 --- a/src/winwidget.h +++ b/src/winwidget.h @@ -84,7 +84,7 @@ struct __winwidget { int force_aliasing; double im_angle; enum win_type type; - unsigned char had_resize, full_screen; + unsigned char had_resize, full_screen, manual_zoom; Imlib_Image im; GC gc; Pixmap bg_pmap;