From ec5a0249e26acdf12677df535df3d3faa6a18b49 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 23 Jun 2020 19:26:36 +0300 Subject: [PATCH] [x11] Hold a passive grab on buttons only when needed Due to a bug in the XI2 protocol, clients have to reset scroll valuators on XI_Enter because the scroll valuators might have changed while the pointer was elsewhere. The XI_Enter event is usually sent when an input device enters the window, but it can also be generated by a passive grab. If an XI_Enter event has been generated by a passive grab, the client should not reset scroll valuators. Unfortunately, there is no any reliable way for the client to determine if an XI_Enter event has been sent in response to a deactivated passive grab. A correct fix for the scroll issues in GTK apps would involve changes in the XI2 protocol. As a work around, we can hold a passive grab only if the current mouse wheel action is either "Activate and scroll" or "Activate, raise, and scroll." BUG: 394772 FIXED-IN: 5.19.3 --- events.cpp | 113 ++++++++++++++++++++++++++++++-------------------- options.h | 3 ++ x11client.cpp | 1 + x11client.h | 4 +- 4 files changed, 74 insertions(+), 47 deletions(-) diff --git a/events.cpp b/events.cpp index ffc96d9b22..d2d1962ae5 100644 --- a/events.cpp +++ b/events.cpp @@ -823,21 +823,22 @@ void X11Client::leaveNotifyEvent(xcb_leave_notify_event_t *e) #define XCapL KKeyServer::modXLock() #define XNumL KKeyServer::modXNumLock() #define XScrL KKeyServer::modXScrollLock() -void X11Client::grabButton(int modifier) +void X11Client::grabButton(Qt::KeyboardModifier modifier, uint8_t button) { - unsigned int mods[ 8 ] = { - 0, XCapL, XNumL, XNumL | XCapL, - XScrL, XScrL | XCapL, - XScrL | XNumL, XScrL | XNumL | XCapL - }; - for (int i = 0; - i < 8; - ++i) - m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, modifier | mods[ i ]); -} + uint16_t x11Modifier; + + switch (modifier) { + case Qt::MetaModifier: + x11Modifier = KKeyServer::modXMeta(); + break; + case Qt::AltModifier: + x11Modifier = KKeyServer::modXAlt(); + break; + default: + x11Modifier = 0; + break; + } -void X11Client::ungrabButton(int modifier) -{ unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, @@ -846,47 +847,69 @@ void X11Client::ungrabButton(int modifier) for (int i = 0; i < 8; ++i) - m_wrapper.ungrabButton(modifier | mods[ i ]); + m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, x11Modifier | mods[ i ], button); } #undef XCapL #undef XNumL #undef XScrL -/** - * Releases the passive grab for some modifier combinations when a - * window becomes active. This helps broken X programs that - * missinterpret LeaveNotify events in grab mode to work properly - * (Motif, AWT, Tk, ...) - */ +bool X11Client::isMostRecentlyRaised() const +{ + // The last toplevel in the unconstrained stacking order is the most recently raised one. + return workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; +} + void X11Client::updateMouseGrab() { - if (workspace()->globalShortcutsDisabled()) { - m_wrapper.ungrabButton(); - // keep grab for the simple click without modifiers if needed (see below) - bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; - if (!(!options->isClickRaise() || not_obscured)) - grabButton(XCB_NONE); + xcb_ungrab_button(connection(), XCB_BUTTON_INDEX_ANY, m_wrapper, XCB_MOD_MASK_ANY); + + if (TabBox::TabBox::self()->forcedGlobalMouseGrab()) { // see TabBox::establishTabBoxGrab() + m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); return; } - if (isActive() && !TabBox::TabBox::self()->forcedGlobalMouseGrab()) { // see TabBox::establishTabBoxGrab() - // first grab all modifier combinations - m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); - // remove the grab for no modifiers only if the window - // is unobscured or if the user doesn't want click raise - // (it is unobscured if it the topmost in the unconstrained stacking order, i.e. it is - // the most recently raised window) - bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; - if (!options->isClickRaise() || not_obscured) - ungrabButton(XCB_NONE); - else - grabButton(XCB_NONE); - ungrabButton(XCB_MOD_MASK_SHIFT); - ungrabButton(XCB_MOD_MASK_CONTROL); - ungrabButton(XCB_MOD_MASK_CONTROL | XCB_MOD_MASK_SHIFT); - } else { - m_wrapper.ungrabButton(); - // simply grab all modifier combinations - m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); + + // When a passive grab is activated or deactivated, the X server will generate crossing + // events as if the pointer were suddenly to warp from its current position to some position + // in the grab window. Some /broken/ X11 clients do get confused by such EnterNotify and + // LeaveNotify events so we release the passive grab for the active window. + // + // The passive grab below is established so the window can be raised or activated when it + // is clicked. + if ((options->focusPolicyIsReasonable() && !isActive()) || + (options->isClickRaise() && !isMostRecentlyRaised())) { + if (options->commandWindow1() != Options::MouseNothing) { + grabButton(Qt::NoModifier, XCB_BUTTON_INDEX_1); + } + if (options->commandWindow2() != Options::MouseNothing) { + grabButton(Qt::NoModifier, XCB_BUTTON_INDEX_2); + } + if (options->commandWindow3() != Options::MouseNothing) { + grabButton(Qt::NoModifier, XCB_BUTTON_INDEX_3); + } + if (options->commandWindowWheel() != Options::MouseNothing) { + grabButton(Qt::NoModifier, XCB_BUTTON_INDEX_4); + grabButton(Qt::NoModifier, XCB_BUTTON_INDEX_5); + } + } + + // We want to grab + buttons no matter what state the window is in. The + // client will receive funky EnterNotify and LeaveNotify events, but there is nothing that + // we can do about it, unfortunately. + + if (!workspace()->globalShortcutsDisabled()) { + if (options->commandAll1() != Options::MouseNothing) { + grabButton(options->commandAllModifier(), XCB_BUTTON_INDEX_1); + } + if (options->commandAll2() != Options::MouseNothing) { + grabButton(options->commandAllModifier(), XCB_BUTTON_INDEX_2); + } + if (options->commandAll3() != Options::MouseNothing) { + grabButton(options->commandAllModifier(), XCB_BUTTON_INDEX_3); + } + if (options->commandAllWheel() != Options::MouseWheelNothing) { + grabButton(options->commandAllModifier(), XCB_BUTTON_INDEX_4); + grabButton(options->commandAllModifier(), XCB_BUTTON_INDEX_5); + } } } diff --git a/options.h b/options.h index ee31cfea90..19e69bfef3 100644 --- a/options.h +++ b/options.h @@ -447,6 +447,9 @@ public: MouseCommand commandAll3() const { return CmdAll3; } + MouseWheelCommand commandAllWheel() const { + return CmdAllWheel; + } uint keyCmdAllModKey() const { return CmdAllModKey; } diff --git a/x11client.cpp b/x11client.cpp index 6e2509d3cf..f7ecd99b54 100644 --- a/x11client.cpp +++ b/x11client.cpp @@ -162,6 +162,7 @@ X11Client::X11Client() m_frameGeometry = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Client::updateCaption); + connect(options, &Options::configChanged, this, &X11Client::updateMouseGrab); connect(options, &Options::condensedTitleChanged, this, &X11Client::updateCaption); connect(this, &X11Client::moveResizeCursorChanged, this, [this] (CursorShape cursor) { diff --git a/x11client.h b/x11client.h index 268f6aaaa5..454861a484 100644 --- a/x11client.h +++ b/x11client.h @@ -397,6 +397,7 @@ Q_SIGNALS: private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed + bool isMostRecentlyRaised() const; void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; @@ -417,8 +418,7 @@ private: void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; - void grabButton(int mod); - void ungrabButton(int mod); + void grabButton(Qt::KeyboardModifier modifier, uint8_t button); void resizeDecoration(); void createDecoration(const QRect &oldgeom) override;