[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
This commit is contained in:
Vlad Zahorodnii 2020-06-23 19:26:36 +03:00 committed by Vlad Zahorodnii
parent 170741c499
commit ec5a0249e2
4 changed files with 74 additions and 47 deletions

View file

@ -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 <command modifier> + 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);
}
}
}

View file

@ -447,6 +447,9 @@ public:
MouseCommand commandAll3() const {
return CmdAll3;
}
MouseWheelCommand commandAllWheel() const {
return CmdAllWheel;
}
uint keyCmdAllModKey() const {
return CmdAllModKey;
}

View file

@ -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) {

View file

@ -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;