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.
This commit is contained in:
Fredrik Höglund 2011-11-09 20:39:13 +01:00
parent 3a91e4dee6
commit 9fa2feabc8
6 changed files with 106 additions and 8 deletions

View file

@ -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<KDecorationUnstable*>(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,

View file

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

View file

@ -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<KDecorationUnstable*>(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;

View file

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

View file

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

View file

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