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:
parent
3a91e4dee6
commit
9fa2feabc8
6 changed files with 106 additions and 8 deletions
66
client.cpp
66
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<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,
|
||||
|
|
8
client.h
8
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
|
||||
|
||||
|
|
20
events.cpp
20
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<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;
|
||||
|
|
|
@ -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();
|
||||
|
|
10
layers.cpp
10
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?
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue