From 9fa2feabc86c332cc0fccd2f72132297ddcb7b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20H=C3=B6glund?= Date: Wed, 9 Nov 2011 20:39:13 +0100 Subject: [PATCH] kwin: Insert an input-only window above each decorated client This input-only window is used to capture events above the client window and preventing them from reaching the client. It is currently used to enlarge the borders by an invisible amount, using the ExtendedBorderRegion provided by the decoration. --- client.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++- client.h | 8 +++++++ events.cpp | 20 ++++++++++++---- geometry.cpp | 7 +++++- layers.cpp | 10 ++++++-- manage.cpp | 3 +++ 6 files changed, 106 insertions(+), 8 deletions(-) diff --git a/client.cpp b/client.cpp index b0f1704f7d..c5246d9aec 100644 --- a/client.cpp +++ b/client.cpp @@ -130,6 +130,7 @@ Client::Client(Workspace* ws) , electricMaximizing(false) , activitiesDefined(false) , needsSessionInteract(false) + , input_window(None) { // TODO: Do all as initialization @@ -348,6 +349,52 @@ void Client::destroyClient() deleteClient(this, Allowed); } +void Client::updateInputWindow() +{ + QRegion region; + + if (!noBorder() && dynamic_cast(decoration)) { + // This function is implemented as a slot to avoid breaking binary + // compatibility + QMetaObject::invokeMethod(decoration, "region", Qt::DirectConnection, + Q_RETURN_ARG(QRegion, region), + Q_ARG(KDecorationDefines::Region, KDecorationDefines::ExtendedBorderRegion)); + } + + if (region.isEmpty()) { + if (input_window) { + XDestroyWindow(display(), input_window); + input_window = None; + } + return; + } + + QRect bounds = region.boundingRect(); + input_offset = bounds.topLeft(); + + // Move the bounding rect to screen coordinates + bounds.translate(geometry().topLeft()); + + // Move the region to input window coordinates + region.translate(-input_offset); + + if (!input_window) { + XSetWindowAttributes attr; + attr.event_mask = EnterWindowMask | LeaveWindowMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + attr.override_redirect = True; + + input_window = XCreateWindow(display(), rootWindow(), bounds.x(), bounds.y(), + bounds.width(), bounds.height(), 0, 0, + InputOnly, 0, CWEventMask | CWOverrideRedirect, &attr); + } else { + XMoveResizeWindow(display(), input_window, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); + } + + XShapeCombineRegion(display(), input_window, ShapeInput, 0, 0, region.handle(), ShapeSet); +} + void Client::updateDecoration(bool check_workspace_pos, bool force) { if (!force && @@ -382,6 +429,7 @@ void Client::updateDecoration(bool check_workspace_pos, bool force) destroyDecoration(); if (check_workspace_pos) checkWorkspacePosition(oldgeom); + updateInputWindow(); blockGeometryUpdates(false); if (!noBorder()) decoration->widget()->show(); @@ -415,6 +463,10 @@ void Client::destroyDecoration() emit geometryShapeChanged(this, oldgeom); } } + if (inputId()) { + XDestroyWindow(display(), input_window); + input_window = None; + } } bool Client::checkBorderSizes(bool also_resize) @@ -737,6 +789,7 @@ void Client::resizeDecoration(const QSize& s) } else { // oldSize != newSize resizeDecorationPixmaps(); } + updateInputWindow(); } bool Client::noBorder() const @@ -1185,8 +1238,11 @@ void Client::internalShow(allowed_t) mapping_state = Mapped; if (old == Unmapped || old == Withdrawn) map(Allowed); - if (old == Kept) + if (old == Kept) { + if (inputId()) + XMapWindow(display(), inputId()); updateHiddenPreview(); + } workspace()->checkUnredirect(); } @@ -1214,6 +1270,8 @@ void Client::internalKeep(allowed_t) mapping_state = Kept; if (old == Unmapped || old == Withdrawn) map(Allowed); + if (inputId()) + XUnmapWindow(display(), inputId()); updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); @@ -1238,6 +1296,8 @@ void Client::map(allowed_t) if (!isShade()) { XMapWindow(display(), wrapper); XMapWindow(display(), client); + if (inputId()) + XMapWindow(display(), inputId()); exportMappingState(NormalState); } else exportMappingState(IconicState); @@ -1258,6 +1318,8 @@ void Client::unmap(allowed_t) XUnmapWindow(display(), frameId()); XUnmapWindow(display(), wrapper); XUnmapWindow(display(), client); + if (inputId()) + XUnmapWindow(display(), inputId()); XSelectInput(display(), wrapper, ClientWinMask | SubstructureNotifyMask); if (decoration != NULL) decoration->widget()->hide(); // Not really necessary, but let it know the state @@ -2182,6 +2244,8 @@ void Client::updateCursor() if (decoration != NULL) decoration->widget()->setCursor(cursor); XDefineCursor(display(), frameId(), cursor.handle()); + if (inputId()) + XDefineCursor(display(), inputId(), cursor.handle()); if (moveResizeMode) // XDefineCursor doesn't change cursor if there's pointer grab active XChangeActivePointerGrab(display(), ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, diff --git a/client.h b/client.h index d484f2f78c..c916029e38 100644 --- a/client.h +++ b/client.h @@ -90,6 +90,7 @@ public: Client(Workspace* ws); Window wrapperId() const; Window decorationId() const; + Window inputId() const { return input_window; } const Client* transientFor() const; Client* transientFor(); @@ -137,6 +138,7 @@ public: virtual QPoint clientPos() const; // Inside of geometry() virtual QSize clientSize() const; virtual QRect visibleRect() const; + QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(XEvent* e); virtual bool eventFilter(QObject* o, QEvent* e); @@ -571,6 +573,8 @@ private: void checkOffscreenPosition (QRect* geom, const QRect& screenArea); + void updateInputWindow(); + Window client; Window wrapper; KDecoration* decoration; @@ -726,6 +730,9 @@ private: bool activitiesDefined; //whether the x property was actually set bool needsSessionInteract; + + Window input_window; + QPoint input_offset; }; /** @@ -1022,6 +1029,7 @@ inline bool Client::hiddenPreview() const } KWIN_COMPARE_PREDICATE(WrapperIdMatchPredicate, Client, Window, cl->wrapperId() == value); +KWIN_COMPARE_PREDICATE(InputIdMatchPredicate, Client, Window, cl->inputId() == value); } // namespace diff --git a/events.cpp b/events.cpp index b18e2caa04..fd1147d22a 100644 --- a/events.cpp +++ b/events.cpp @@ -289,6 +289,9 @@ bool Workspace::workspaceEvent(XEvent * e) } else if (Client* c = findClient(FrameIdMatchPredicate(e->xany.window))) { if (c->windowEvent(e)) return true; + } else if (Client *c = findClient(InputIdMatchPredicate(e->xany.window))) { + if (c->windowEvent(e)) + return true; } else if (Unmanaged* c = findUnmanaged(WindowMatchPredicate(e->xany.window))) { if (c->windowEvent(e)) return true; @@ -1090,7 +1093,7 @@ bool Client::buttonPressEvent(Window w, int button, int state, int x, int y, int return true; } - if (w == wrapperId() || w == frameId() || w == decorationId()) { + if (w == wrapperId() || w == frameId() || w == decorationId() || w == inputId()) { // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace updateUserTime(); workspace()->setWasUserInteraction(); @@ -1173,7 +1176,11 @@ bool Client::buttonPressEvent(Window w, int button, int state, int x, int y, int XAllowEvents(display(), ReplayPointer, CurrentTime); //xTime()); return true; } - if (w == decorationId()) { + if (w == decorationId() || w == inputId()) { + if (w == inputId()) { + x = x_root - geometry().x() + padding_left; + y = y_root - geometry().y() + padding_top; + } if (dynamic_cast(decoration)) // New API processes core events FIRST and only passes unused ones to the decoration return processDecorationButtonPress(button, state, x, y, x_root, y_root, true); @@ -1262,7 +1269,7 @@ bool Client::buttonReleaseEvent(Window w, int /*button*/, int state, int x, int XAllowEvents(display(), SyncPointer, CurrentTime); //xTime()); return true; } - if (w != frameId() && w != decorationId() && w != moveResizeGrabWindow()) + if (w != frameId() && w != decorationId() && w != inputId() && w != moveResizeGrabWindow()) return true; x = this->x(); // translate from grab window to local coords y = this->y(); @@ -1348,12 +1355,17 @@ void Client::checkQuickTilingMaximizationZones(int xroot, int yroot) // return value matters only when filtering events before decoration gets them bool Client::motionNotifyEvent(Window w, int /*state*/, int x, int y, int x_root, int y_root) { - if (w != frameId() && w != decorationId() && w != moveResizeGrabWindow()) + if (w != frameId() && w != decorationId() && w != inputId() && w != moveResizeGrabWindow()) return true; // care only about the whole frame if (!buttonDown) { QPoint mousePos(x, y); if (w == frameId()) mousePos += QPoint(padding_left, padding_top); + if (w == inputId()) { + int x = x_root - geometry().x() + padding_left; + int y = y_root - geometry().y() + padding_top; + mousePos = QPoint(x, y); + } Position newmode = mousePosition(mousePos); if (newmode != mode) { mode = newmode; diff --git a/geometry.cpp b/geometry.cpp index 528a0d852c..cd9d7a08f4 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1917,8 +1917,13 @@ void Client::setGeometry(int x, int y, int w, int h, ForceGeometry_t force, bool XMoveResizeWindow(display(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); - } else + } else { XMoveWindow(display(), frameId(), x, y); + if (inputId()) { + const QPoint pos = QPoint(x, y) + inputPos(); + XMoveWindow(display(), inputId(), pos.x(), pos.y()); + } + } // SELI TODO won't this be too expensive? sendSyntheticConfigureNotify(); updateWindowRules(); diff --git a/layers.cpp b/layers.cpp index 3ce9903808..efcfb872b9 100644 --- a/layers.cpp +++ b/layers.cpp @@ -163,10 +163,16 @@ void Workspace::propagateClients(bool propagate_new_clients) } #endif for (int i = stacking_order.size() - 1; i >= 0; i--) { - if (stacking_order.at(i)->hiddenPreview()) { + Client *client = stacking_order.at(i); + if (client->hiddenPreview()) { continue; } - newWindowStack << (Window*)stacking_order.at(i)->frameId(); + + if (client->inputId()) + // Stack the input window above the frame + newWindowStack << (Window*)client->inputId(); + + newWindowStack << (Window*)client->frameId(); } // TODO isn't it too inefficient to restack always all clients? // TODO don't restack not visible windows? diff --git a/manage.cpp b/manage.cpp index ebf3a54954..fe1086cf4c 100644 --- a/manage.cpp +++ b/manage.cpp @@ -149,6 +149,9 @@ bool Client::manage(Window w, bool isMapped) KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); + // Make sure that the input window is created before we update the stacking order + updateInputWindow(); + workspace()->updateClientLayer(this); SessionInfo* session = workspace()->takeSessionInfo(this);