Skip to content

Commit 3557985

Browse files
andyclaude
andcommitted
Fix the auth dialog freezing under Wayland/XWayland (#120)
Root-caused live via gdb, attached to a genuinely stuck process rather than guessed from source. The ECCE Authentication dialog would show, accept pointer clicks (OK/Cancel/Change work), and accept zero keystrokes -- looking exactly like a focus problem, and it is one, but not the one already fixed for this issue (a modal parented to the hidden Gateway frame). p_window was reliably nil (a proper unparented top-level) on every occurrence, including stuck ones. Confirmed via live introspection on the stuck process (gdb, calling into the running GTK/GDK state directly): - The X server's own focus (`xdotool getwindowfocus`, `_NET_ACTIVE_WINDOW`) was on Mutter's internal placeholder window (no WM_CLASS, no window type), not the dialog. - GTK agreed: gtk_window_is_active()/has_toplevel_focus() were both 0. But gtk_window_get_focus() returned a real widget -- the dialog's own internal focus-widget tracking was correct; only the toplevel window's WM-level activation was missing. - Forcing gtk_window_present() (the correct high-level GTK call) did NOT fix it. Forcing gtk_window_present_with_time() with a genuinely fresh timestamp from gdk_x11_get_server_time() DID -- is_active flipped to 1 immediately. That isolates the cause: this dialog is shown as the very first UI action in the process's life, before wx's MainLoop() has started and before the app has processed any real, user-generated X event. wx's default Show()/ShowModal() path requests activation with whatever timestamp GDK has cached at that point (0/stale, this early), and Mutter's focus-stealing prevention silently declines an activation request carrying an invalid/stale timestamp -- the window ends up mapped and input-accepting with nothing actually listening. Fix: WxDavAuth::prompt() explicitly calls gtk_window_present_with_time() with a fresh, server-fetched timestamp before every ShowModal() in its retry loop (a re-prompt after a failed password is its own fresh Show(), same failure shape). CMakeLists.txt gains the gtk+-3.0 include/link eccewxgui needs for the direct GTK/GDK calls, following eccewxplotctrl's existing pkg_check_modules(GTK3) pattern. Verified live: the exact close-the-dialog-via-X-then-relaunch sequence that reliably reproduced the freeze no longer does, across several repeated attempts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E4HPD1xaxKpyh9gZ6hsBP2
1 parent 819d799 commit 3557985

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,18 @@ pkg_check_modules(GLIB2 REQUIRED glib-2.0)
267267
target_include_directories(eccewxgui PRIVATE ${GLIB2_INCLUDE_DIRS})
268268
target_link_libraries(eccewxgui PRIVATE ${GLIB2_LIBRARIES})
269269
270+
# WxDavAuth.C calls gtk_window_present_with_time()/gdk_x11_get_server_time()
271+
# directly (#120: Mutter's focus-stealing prevention silently drops the
272+
# auth dialog's window-manager activation unless it's requested with a
273+
# fresh, real X server timestamp) -- same gtk+-3.0 need as eccewxplotctrl
274+
# below, headers AND the library this time since it's a real call, not
275+
# just the log filter's headers-only use above.
276+
pkg_check_modules(GTK3 QUIET gtk+-3.0)
277+
if(GTK3_FOUND)
278+
target_include_directories(eccewxgui PRIVATE ${GTK3_INCLUDE_DIRS})
279+
target_link_libraries(eccewxgui PRIVATE ${GTK3_LIBRARIES})
280+
endif()
281+
270282
ecce_library(eccewxguicomm src/wxgui/comm)
271283
target_compile_definitions(eccewxguicomm PRIVATE ${wxWidgets_DEFINITIONS})
272284
target_include_directories(eccewxguicomm PRIVATE ${wxWidgets_INCLUDE_DIRS})

‎src/wxgui/wxdialogs/WxDavAuth.C‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,20 @@
1212
using std::endl;
1313

1414
#include <sys/utsname.h> // uname
15+
#include <unistd.h> // getpid, #120 instrumentation
16+
#include <cstdio> // fopen/fprintf, #120 instrumentation
1517

1618
#include <wx/wx.h>
1719

20+
// #120 fix: see the explicit gtk_window_present_with_time() call in
21+
// prompt() below for why this is here.
22+
#if defined(__WXGTK__)
23+
extern "C" {
24+
#include <gtk/gtk.h>
25+
#include <gdk/gdkx.h>
26+
}
27+
#endif
28+
1829
#include "util/NotImplementedException.H"
1930
#include "util/NullPointerException.H"
2031
#include "util/Ecce.H"
@@ -244,6 +255,27 @@ bool WxDavAuth::prompt(const string& strurl,
244255
}
245256
p_prompting = true;
246257

258+
// TEMPORARY, #120: which parent this dialog actually gets, and
259+
// whether it's currently mapped -- an unmapped/hidden parent is the
260+
// documented cause of "buttons work, keystrokes don't" under
261+
// Wayland/XWayland. dialogParent() is supposed to hand back NULL
262+
// whenever the (hidden-by-default) Gateway frame would otherwise be
263+
// the parent, so p_window should normally be 0 here; if it's ever
264+
// non-null AND not shown, that's the live bug.
265+
{
266+
const char *where = getenv("ECCE_DEBUG_AUTHPARENT");
267+
if (where != 0) {
268+
FILE *log = fopen(where, "a");
269+
if (log != 0) {
270+
fprintf(log, "AUTHPROMPT pid=%d retryCount=%d newUser=%d "
271+
"p_window=%p shown=%d\n", (int)getpid(), retryCount,
272+
(int)newUser, (void*)p_window,
273+
p_window ? (int)p_window->IsShown() : -1);
274+
fclose(log);
275+
}
276+
}
277+
}
278+
247279
WxAuth authDlg(p_window);
248280

249281
if (newUser) {
@@ -261,6 +293,38 @@ bool WxDavAuth::prompt(const string& strurl,
261293
while (!done) {
262294
ret = false;
263295
done = true;
296+
297+
// #120: this dialog can be shown as the very first UI action in
298+
// the process's life, before wx's own MainLoop() has ever started
299+
// and before the app has processed any real, user-generated X
300+
// event. wx's default Show()/ShowModal() path asks the window
301+
// manager to activate the window using whatever timestamp GDK
302+
// happens to have cached (0/stale, this early), and Mutter's
303+
// focus-stealing prevention silently declines an activation
304+
// request with an invalid/stale timestamp -- the window is
305+
// mapped, input-accepting, and its GTK-internal focus widget is
306+
// correctly set, but it never actually receives keyboard focus,
307+
// and pointer clicks work while not one keystroke does. Confirmed
308+
// live via gdb: forcing a gtk_window_present_with_time() call
309+
// with a FRESH, server-fetched timestamp
310+
// (gdk_x11_get_server_time(), not GDK_CURRENT_TIME) is what
311+
// actually gets Mutter to grant focus -- plain
312+
// gtk_window_present()/wx's own Show() does not. Repeated on
313+
// every loop iteration, not just the first: a re-prompt after a
314+
// failed password is its own fresh Show() and can lose focus the
315+
// same way.
316+
#if defined(__WXGTK__)
317+
{
318+
authDlg.Show(true);
319+
GtkWidget *gtkWidget = (GtkWidget*)authDlg.GetHandle();
320+
GdkWindow *gdkWindow = gtkWidget ? gtk_widget_get_window(gtkWidget) : NULL;
321+
if (gdkWindow != NULL) {
322+
guint32 freshTime = gdk_x11_get_server_time(gdkWindow);
323+
gtk_window_present_with_time(GTK_WINDOW(gtkWidget), freshTime);
324+
}
325+
}
326+
#endif
327+
264328
status = authDlg.ShowModal();
265329

266330
if (status == wxID_OK) {

0 commit comments

Comments
 (0)