Summary: | GTK4 child windows/dialogs positioned incorrectly on KWin X11 | ||
---|---|---|---|
Product: | [Plasma] kwin | Reporter: | nyanpasu64 <nyanpasu64> |
Component: | general | Assignee: | KWin default assignee <kwin-bugs-null> |
Status: | RESOLVED UPSTREAM | ||
Severity: | normal | CC: | mgraesslin, nate, qydwhotmail |
Priority: | NOR | ||
Version: | 5.22.2 | ||
Target Milestone: | --- | ||
Platform: | unspecified | ||
OS: | Linux | ||
URL: | https://gitlab.gnome.org/GNOME/gtk/-/issues/4090 | ||
Latest Commit: | Version Fixed In: | ||
Attachments: | There are chances that the dialog window locates in the center of the parent window. |
Description
nyanpasu64
2021-06-25 03:31:01 UTC
Created attachment 139780 [details]
There are chances that the dialog window locates in the center of the parent window.
It can also be reproduced on openSUSE Tumbleweed.
Operating System: openSUSE Tumbleweed 20210628
KDE Plasma Version: 5.22.2
KDE Frameworks Version: 5.83.0
Qt Version: 5.15.2
Kernel Version: 5.12.13-1-default (64-bit)
Graphics Platform: X11
Processors: 8 × AMD Ryzen 7 4700U with Radeon Graphics
Memory: 15.0 GiB of RAM
Graphics Processor: AMD RENOIR
I also noticed that in GTK4 apps, if the dialog spawns on the parent window rather than in the corner of the screen, the KWin title bar will have an empty space to the right of the X button (visible in Comment 1's screenshot). Running kwin_x11 using env QT_LOGGING_RULES="*.debug=true;qt.qpa*.debug=false" kwin_x11 --replace If dialog locates in the top left corner: kwin_core: User timestamp, ASN: 2520815 kwin_core: User timestamp, final: KWin::X11Client(0x56377cf7f630, windowId=0x100017b, caption="Dialogs <2>\u200E") : 2520815 kwin_core: Activation: Belongs to active application kwin_core: XCB error: 152 (BadDamage), sequence: 13413, resource id: 54529620, major code: 143 (DAMAGE), minor code: 2 (Destroy) If dialog locates in the center of the parent window: kwin_core: User timestamp, ASN: 2522656 kwin_core: User timestamp, final: KWin::X11Client(0x56377cf7f630, windowId=0x100018f, caption="Dialogs <2>\u200E", transientFor=KWin::X11Client(0x56377cf089b0, windowId=0x1000004, caption="Dialogs")) : 2522656 kwin_core: Activation: Belongs to active application kwin_core: XCB error: 152 (BadDamage), sequence: 15137, resource id: 54529671, major code: 143 (DAMAGE), minor code: 2 (Destroy) ▣The transient status bool is defined here(https://github.com/KDE/kwin/blob/8c8098a61c8990076f6d0866d662ab7980e49dc7/src/x11client.h#L559) The default value of m_transientForId is equal to XCB_WINDOW_NONE. ▣ m_transientForId is modified by setTransient(). (https://github.com/KDE/kwin/blob/c61085dc2e28cb7d737c9b049499b4433916b194/src/x11client.cpp#L2966) ▣setTransient() is called by readTransientProperty()(https://github.com/KDE/kwin/blob/c61085dc2e28cb7d737c9b049499b4433916b194/src/x11client.cpp#L2947). 1. If transientFor.getTransientFor(&new_transient_for_id) returns false, isTransient() is false. 2. If verifyTransientFor() fails, isTransient() is false. There are some debug messages defined in verifyTransientFor(), but I haven't seen them. So I guess WM_TRANSIENT_FOR is None when verifyTransientFor() fails. ▣verifyTransientFor() is defined here(https://github.com/KDE/kwin/blob/c61085dc2e28cb7d737c9b049499b4433916b194/src/x11client.cpp#L3120) More debug information is needed to find what part is wrong. Top-left corner: kwin_core: transientFor.getTransientFor() returns False. "Dialogs <2>\u200E" kwin_core: verifyTransientFor(): new_transient_for == XCB_WINDOW_NONE and set is False. "Dialogs <2>\u200E" kwin_core: User timestamp, ASN: 13849012 kwin_core: User timestamp, final: KWin::X11Client(0x55822e5d3610, windowId=0x1000096, caption="Dialogs <2>\u200E") : 13849012 kwin_core: Activation: Belongs to active application kwin_core: transientFor.getTransientFor() returns True. "Dialogs <2>\u200E" kwin_core: setTransient(): new_transient_for_id != m_transientForId is True. "Dialogs <2>\u200E" kwin_core: setTransient(): m_transientForId != XCB_WINDOW_NONE && !groupTransient() is True. "Dialogs <2>\u200E" Center: kwin_core: transientFor.getTransientFor() returns True. "Dialogs <2>\u200E" kwin_core: setTransient(): new_transient_for_id != m_transientForId is True. "Dialogs <2>\u200E" kwin_core: setTransient(): m_transientForId != XCB_WINDOW_NONE && !groupTransient() is True. "Dialogs <2>\u200E" kwin_core: User timestamp, ASN: 13970306 kwin_core: User timestamp, final: KWin::X11Client(0x55822e5d3610, windowId=0x10000b8, caption="Dialogs <2>\u200E", transientFor=KWin::X11Client(0x55822dd21fa0, windowId=0x1000004, caption="Dialogs")) : 13970306 kwin_core: Activation: Belongs to active application Problem exists in transientFor.getTransientFor() https://bugs.kde.org/show_bug.cgi?id=439137#add_comment The upstream maintainers have replied: (https://gitlab.gnome.org/GNOME/gtk/-/issues/4070#note_1197122): > We set a transient parent, and mark the dialogs as modal. > > The rest is up to the wm. They closed the bug, saying it's not GTK4's problem. However, the transient-ness of child windows randomly fails to be picked up by kwin, and I suspect (based on windows opening at the center of the screen) it always fails to be picked up by xfwm4. I still don't know if xfwm4 and kwin are both broken in the same scenario but with different symptoms, and mutter and openbox are unaffected. I theorize there's a race condition where GTK4 exposes a new window to the WM before marking it as transient, and the running WM finds a new window and checks the transient state at some point either before or after GTK sets it. xfwm4 always fails to see the transient state, KDE sometimes sees the transient state, and mutter/openbox always see the transient state. KDE is wonky: If the "Dialogs" window appears "where there's empty room" or "on top of the parent window", or if the "Message Dialog" appears "on the window", KDE only puts 2 buttons in the title bar, but adds a gap to the right for a third button. I suspect this is caused by the same-ish race condition.) ---- I decided to perform more logging of opening the "Dialog" window (not dialog box), since it had 3 different cases rather than 2. I copied the above command line: env QT_LOGGING_RULES="*.debug=true;qt.qpa*.debug=false" kwin_x11 --replace (I don't know of any analogous command lines for X11 in general, or other WMs.) *On GTK4* "Dialog" window spawns at top left: kwin_core: User timestamp, ASN: 1248132 kwin_core: User timestamp, final: KWin::X11Client(0x5556e3e0ac90, windowId=0x5000072, caption="Dialogs <2>\u200E", transientFor=KWin::X11Client(0x5556e3b7aed0, windowId=0x5000004, caption="Dialogs")) : 1248132 kwin_core: Activation: Belongs to active application "Dialog" window spawns in empty space (gap to the right of close button): kwin_core: User timestamp, ASN: 1555150 kwin_core: User timestamp, final: KWin::X11Client(0x5654aa852520, windowId=0x5000282, caption="Dialogs <2>\u200E") : 1555150 kwin_core: Activation: Belongs to active application "Dialog" window spawns on top of parent (gap to the right of close button): kwin_core: User timestamp, ASN: 1669802 kwin_core: User timestamp, final: KWin::X11Client(0x5654aa85c6a0, windowId=0x50002fe, caption="Dialogs <2>\u200E", transientFor=KWin::X11Client(0x5654aa257e40, windowId=0x5000004, caption="Dialogs")) : 1669802 kwin_core: Activation: Belongs to active application *On GTK3* "Dialog and Message Boxes" window always ends up in empty space (no gap to the right of close button): kwin_core: User timestamp, ASN: 1602765 kwin_core: User timestamp, final: KWin::X11Client(0x5654aa6e0af0, windowId=0x6000bcf, caption="Dialogs and Message Boxes <2>\u200E", transientFor=KWin::X11Client(0x5654aa6e2fa0, windowId=0x6000007, caption="Dialogs and Message Boxes")) : 1602765 kwin_core: Activation: Belongs to active application *Analysis* On GTK4, if I spawn the "Dialogs" window, top left" and "on top of parent" both show transientFor in the debug message. "In empty space" does not. If I spawn "Message Dialog", both "top left" and "on top of parent" show transientFor in the debug message, and I've never seen a dialog appear without a transientFor message, regardless if it's positioned properly or not. This differs from what qydwhotmail saw! On GTK3, if I spawn the "Dialogs" window, it always spawns on top of parent and always shows transientFor. *Sidenote* After I alt-tab slowly enough for the sidebar to actually appear, then KWin's debug logging gets a lot noisier permanently until I restart kwin. For example: qt.scenegraph.renderloop: - animationStarted() qt.scenegraph.renderloop: *** Starting animation timer qt.scenegraph.renderloop: - polish and sync update request qt.scenegraph.renderloop: polishAndSync (normal) PlasmaQuick::Dialog(0x55db957c0970, visibility=QWindow::Hidden, flags=QFlags<Qt::WindowType>(Dialog|X11BypassWindowManagerHint|FramelessWindowHint|WindowMinMaxButtonsHint), geometry=0,0 396x1440) qt.scenegraph.renderloop: - not exposed, abort qt.scenegraph.renderloop: - ticking non-visual timer qt.scenegraph.renderloop: - ticking non-visual timer qt.scenegraph.renderloop: - ticking non-visual timer kwin_core: User timestamp, ASN: 904915 kwin_core: User timestamp, final: KWin::X11Client(0x55db95dca8f0, windowId=0x3a00326, caption="Dialogs <2>\u200E") : 904915 kwin_core: Activation: Belongs to active application From the description it sounds that window gets mapped without the WM_TRANSIENT_FOR property and KWin and likewise XFWM does not support changing the state of that property. I just checked ICCCM and could not find anything regarding whether it is allowed or disallowed to change that property once the window is mapped. In my opinion it doesn't make sense to change it once it is mapped. At least I cannot imagine how a window manager should handle this property change once the window is mapped. If you are able to monitor the property states once the window gets mapped and then monitor for changes of the property we know more. On the technical side of things, how would I "monitor the property states once the window gets mapped and then monitor for changes of the property"? Should I insert debug statements into kwin? Or try reading kwin or GTK4 myself? On the social side of things, should I ping the GTK bug and ask them to reevaluate GTK4's code? Should I mention my hypothesis or your message? (In reply to nyanpasu64 from comment #8) > On the technical side of things, how would I "monitor the property states > once the window gets mapped and then monitor for changes of the property"? > Should I insert debug statements into kwin? Or try reading kwin or GTK4 > myself? Yes adding debug statements in KWin is the way I would go. It might be possible to also use KWin scripts, though I am not completely sure whether that is exposed (especially if KWin does not handle transient_for changes the change signal is probably missing). > > On the social side of things, should I ping the GTK bug and ask them to > reevaluate GTK4's code? Should I mention my hypothesis or your message? I would wait till we have more information. If the hypothesis is correct then we should ask GTK to reevaluate the code. I guess the window does change its WM_TRANSIENT_FOR property after its initialization if I add debug messages to the right places. I use a custom patch to add more debug information to KWin.(https://build.opensuse.org/package/view_file/home:fusionfuture:branches:KDE:Frameworks5/kwin5/enable-debug.patch?expand=1) Below are debug logs. =============Top-left START============= kwin_core: fetchTransient(): window() = 8388672 kwin_core: manage(): calling readTransientProperty(transientCookie) kwin_core: readTransientProperty(): transientFor.getTransientFor() returns False. "Dialogs <2>\u200E" kwin_core: verifyTransientFor(): new_transient_for == XCB_WINDOW_NONE and set is False. "Dialogs <2>\u200E" kwin_core: readTransientProperty(): (2)new_transient_for_id = 0 kwin_core: User timestamp, ASN: 10647473 kwin_core: User timestamp, final: KWin::X11lCient(0x5576a65c3b40, windowId=0x800040, caption="Dialogs <2>\u200E") : 10647473 kwin_core: Activation: Belongs to active application kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: fetchTransient(): window() = 8388672 kwin_core: readTransient(): calling readTransientProperty(transientFor) kwin_core: readTransientProperty(): (1)new_transient_for_id = 8388612 kwin_core: readTransientProperty(): transientFor.getTransientFor() returns True. "Dialogs <2>\u200E" kwin_core: readTransientProperty(): (2)new_transient_for_id = 8388612 kwin_core: setTransient(): new_transient_for_id ( 8388612 ) != m_transientForId( 0 ) is True. kwin_core: setTransient(): m_transientForId != XCB_WINDOW_NONE && !groupTransient() is True. "Dialogs <2>\u200E" =============Top-left END============= It can be seen from the log and the source code that readTransient() is called by propertyNotifyEvent(xcb_property_notify_event_t *e), and the case is XCB_ATOM_WM_TRANSIENT_FOR. =============Center START============= kwin_core: fetchTransient(): window() = 8388759 kwin_core: manage(): calling readTransientProperty(transientCookie) kwin_core: readTransientProperty(): (1)new_transient_for_id = 8388612 kwin_core: readTransientProperty(): transientFor.getTransientFor() returns True. "Dialogs <2>\u200E" kwin_core: readTransientProperty(): (2)new_transient_for_id = 8388612 kwin_core: setTransient(): new_transient_for_id ( 8388612 ) != m_transientForId( 0 ) is True. kwin_core: setTransient(): m_transientForId != XCB_WINDOW_NONE && !groupTransient() is True. "Dialogs <2>\u200E" kwin_core: User timestamp, ASN: 10694350 kwin_core: User timestamp, final: KWin::X11Client(0x5576a6f708e0, windowId=0x800097, caption="Dialogs <2>\u200E", transientFor=KWin::X11Client(0x5576a664eed0, windowId=0x800004, caption="Dialogs")) : 10694350 kwin_core: Activation: Belongs to active application kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w kwin_core: checkTransient(): m_originalTransientForId != w =============Center END============= Here, readTransient() is not called. Good! That explains the behavior. The placement is done in the manage method, thus once we know it's a transient, it's too late. I think this should go back to GTK devs to look into ensuring that the property only gets update in withdrawn state. Who should comment on the GTK issue? I'm not the most well-versed in KWin or the underlying X11 protocol causing this bug. If I were to comment on GTK, I'd say: "There's a race condition where GTK4 exposes a new window to the WM before marking it as transient, and the running WM finds a new window and checks the transient state at some point either before or after GTK sets it. xfwm4 always fails to see the transient state, KDE sometimes sees the transient state, and mutter/openbox always see the transient state." And link to this bug's Comment 10 or 11. I studied the source code of gtk-demo, and found the bug may exist in the demo program. 1. In main.c, gtk_demo_run() is called when "Run" button is pressed. (https://gitlab.gnome.org/GNOME/gtk/-/blob/master/demos/gtk-demo/main.c#L267) 2, In gtk_demo_run(), "self->func (window)" is called BEFORE gtk_window_set_transient_for() is called which sets the transient property. (https://gitlab.gnome.org/GNOME/gtk/-/blob/master/demos/gtk-demo/main.c#L155) 3. With regard to "self->func (window)", "window" refers to the parent window, and "func" refers to "do_dialog" in dialog.c 4. do_dialog() calls gtk_widget_show() after attributes of the widget are set. (https://gitlab.gnome.org/GNOME/gtk/-/blob/master/demos/gtk-demo/dialog.c#L187) That should explain the race condition. do_dialog() is called before gtk_window_set_transient_for(), and I don't know why they intend to do that. But in message_dialog_clicked(), gtk_window_set_transient_for() called by gtk_message_dialog_new() is called before gtk_widget_show(). (https://gitlab.gnome.org/GNOME/gtk/-/blob/master/demos/gtk-demo/dialog.c#L22) (https://gitlab.gnome.org/GNOME/gtk/-/blob/master/gtk/gtkmessagedialog.c#L532) An issue about gtk-demo is also reported. https://gitlab.gnome.org/GNOME/gtk/-/issues/4090 I don't think the bug is solely in the demo program. gtk4-widget-factory and gtk4-rs apps are also affected, and gtk3-demo (organized differently from gtk4-demo), gtk3-widget-factory, and gtk3-rs apps are unaffected. I might write a minimal C demo program for gtk3 and gtk4 and see if I can get the same-ish code to behave differently. This bug in window creation order may be related to how gtk4 removed the gtk_widget_show_all function. I suspected that gtk4 apps show the window before gtk3 would, but https://docs.gtk.org/gtk4/migrating-3to4.html#widgets-are-now-visible-by-default says that GTK4 windows and dialogs and such must be explicitly shown, so I'm not sure. FYI there is more investigation and discussion happening at https://gitlab.gnome.org/GNOME/gtk/-/issues/4090. I think the buggy GTK4 behavior is not caused by the "dialog parent" race condition, but rather because KWin sees/shows the window before GTK4 renders the window contents and configures the title bar buttons. Is it also a KWin bug that it handles GTK4's behavior poorly and draws the wrong title bar button layout? Looks like a GTK bug I guess! Sadly the GTK folks don't think GTK4 is doing anything wrong, and I stopped investigating, so I still don't know what the issue is. |