From 84d75cb5674577098d584c7f3634ea2a81d8b9f2 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Tue, 8 Oct 2019 11:46:59 +0300 Subject: [PATCH] [x11] Add support for _GTK_FRAME_EXTENTS Summary: KDE is known for having a strong view on the client-side decorations vs server-side decorations issue. The main argument raised against CSD is that desktop will look less consistent when clients start drawing window decorations by themselves, which is somewhat true. It all ties to how well each toolkit is integrated with the desktop environment. KDE doesn't control the desktop market on Linux. Another big "player" is GNOME. Both KDE and GNOME have very polarized views on in which direction desktop should move forward. The KDE community is pushing more toward server-side decorations while the GNOME community is pushing more toward client-side decorations. Both communities have developed great applications and it's not rare to see a GNOME application being used in KDE Plasma. The only problem is that these different views are not left behind the curtain and our users pay the price. Resizing GTK clients in Plasma became practically impossible due to resize borders having small hit area. When a client draws its window decoration, it's more likely that it also draws the drop-shadow around the decoration. The compositor must know the extents of the shadow so things like snapping and so on work as expected. And here lies the problem... While the xdg-shell protocol has a way to specify such things, the NetWM spec doesn't have anything like that. There's _GTK_FRAME_EXTENTS in the wild, however the problem with it is that it's a proprietary atom, which is specific only to GTK apps. Due to that, _GTK_FRAME_EXTENTS wasn't implemented because implementing anything like that would require major changes in how we think about geometry. Recent xdg-shell window geometry patches adjusted geometry abstractions in kwin to such a degree that it's very easy to add support for client side decorated clients on X11. We just have to make sure that the X11Client class provides correct buffer geometry and frame geometry when the gtk frame extents are set. Even though the X11 code is feature frozen, I still think it's worth to have _GTK_FRAME_EXTENTS support in kwin because it will fix the resize issues. Also, because KWin/Wayland is unfortunately far from becoming default, it will help us with testing some implementation bits of the window geometry from xdg-shell. BUG: 390550 FIXED-IN: 5.18.0 Test Plan: Things like quick tiling, maximizing, tiling scripts and so on work as expected with GTK clients. Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: cblack, trmdi, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D24660 --- abstract_client.cpp | 38 ++++ abstract_client.h | 39 +++- atoms.cpp | 1 - atoms.h | 1 - events.cpp | 5 +- geometry.cpp | 164 ++++++++------- manage.cpp | 16 +- netinfo.cpp | 3 +- .../platforms/x11/standalone/glxbackend.cpp | 2 +- toplevel.cpp | 5 +- x11client.cpp | 189 ++++++++++++++++-- x11client.h | 28 ++- 12 files changed, 378 insertions(+), 113 deletions(-) diff --git a/abstract_client.cpp b/abstract_client.cpp index 673005a00e..7c61bf9bd2 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -2009,4 +2009,42 @@ QMargins AbstractClient::frameMargins() const return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); } +QPoint AbstractClient::framePosToClientPos(const QPoint &point) const +{ + return point + QPoint(borderLeft(), borderTop()); +} + +QPoint AbstractClient::clientPosToFramePos(const QPoint &point) const +{ + return point - QPoint(borderLeft(), borderTop()); +} + +QSize AbstractClient::frameSizeToClientSize(const QSize &size) const +{ + const int width = size.width() - borderLeft() - borderRight(); + const int height = size.height() - borderTop() - borderBottom(); + return QSize(width, height); +} + +QSize AbstractClient::clientSizeToFrameSize(const QSize &size) const +{ + const int width = size.width() + borderLeft() + borderRight(); + const int height = size.height() + borderTop() + borderBottom(); + return QSize(width, height); +} + +QRect AbstractClient::frameRectToClientRect(const QRect &rect) const +{ + const QPoint position = framePosToClientPos(rect.topLeft()); + const QSize size = frameSizeToClientSize(rect.size()); + return QRect(position, size); +} + +QRect AbstractClient::clientRectToFrameRect(const QRect &rect) const +{ + const QPoint position = clientPosToFramePos(rect.topLeft()); + const QSize size = clientSizeToFrameSize(rect.size()); + return QRect(position, size); +} + } diff --git a/abstract_client.h b/abstract_client.h index 32e0356075..db48176e40 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -624,7 +624,7 @@ public: void updateLayer(); enum ForceGeometry_t { NormalGeometrySet, ForceGeometrySet }; - void move(int x, int y, ForceGeometry_t force = NormalGeometrySet); + virtual void move(int x, int y, ForceGeometry_t force = NormalGeometrySet); void move(const QPoint &p, ForceGeometry_t force = NormalGeometrySet); virtual void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) = 0; void resizeWithChecks(const QSize& s, ForceGeometry_t force = NormalGeometrySet); @@ -655,6 +655,43 @@ public: QSize adjustedSize(const QSize&, Sizemode mode = SizemodeAny) const; QSize adjustedSize() const; + /** + * Calculates the matching client position for the given frame position @p point. + */ + virtual QPoint framePosToClientPos(const QPoint &point) const; + /** + * Calculates the matching frame position for the given client position @p point. + */ + virtual QPoint clientPosToFramePos(const QPoint &point) const; + /** + * Calculates the matching client size for the given frame size @p size. + * + * Notice that size constraints won't be applied. + * + * Default implementation returns the frame size with frame margins being excluded. + */ + virtual QSize frameSizeToClientSize(const QSize &size) const; + /** + * Calculates the matching frame size for the given client size @p size. + * + * Notice that size constraints won't be applied. + * + * Default implementation returns the client size with frame margins being included. + */ + virtual QSize clientSizeToFrameSize(const QSize &size) const; + /** + * Calculates the matching client rect for the given frame rect @p rect. + * + * Notice that size constraints won't be applied. + */ + QRect frameRectToClientRect(const QRect &rect) const; + /** + * Calculates the matching frame rect for the given client rect @p rect. + * + * Notice that size constraints won't be applied. + */ + QRect clientRectToFrameRect(const QRect &rect) const; + bool isMove() const { return isMoveResize() && moveResizePointerMode() == PositionCenter; } diff --git a/atoms.cpp b/atoms.cpp index 9e65624455..22705e1c9e 100644 --- a/atoms.cpp +++ b/atoms.cpp @@ -64,7 +64,6 @@ Atoms::Atoms() , kde_color_sheme(QByteArrayLiteral("_KDE_NET_WM_COLOR_SCHEME")) , kde_skip_close_animation(QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION")) , kde_screen_edge_show(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW")) - , gtk_frame_extents(QByteArrayLiteral("_GTK_FRAME_EXTENTS")) , kwin_dbus_service(QByteArrayLiteral("_ORG_KDE_KWIN_DBUS_SERVICE")) , utf8_string(QByteArrayLiteral("UTF8_STRING")) , text(QByteArrayLiteral("TEXT")) diff --git a/atoms.h b/atoms.h index e31f5adad7..9749731a08 100644 --- a/atoms.h +++ b/atoms.h @@ -73,7 +73,6 @@ public: Xcb::Atom kde_color_sheme; Xcb::Atom kde_skip_close_animation; Xcb::Atom kde_screen_edge_show; - Xcb::Atom gtk_frame_extents; Xcb::Atom kwin_dbus_service; Xcb::Atom utf8_string; Xcb::Atom text; diff --git a/events.cpp b/events.cpp index 8347581258..cb30d74020 100644 --- a/events.cpp +++ b/events.cpp @@ -482,6 +482,9 @@ bool X11Client::windowEvent(xcb_generic_event_t *e) if (dirtyProperties2 & NET::WM2DesktopFileName) { setDesktopFileName(QByteArray(info->desktopFileName())); } + if (dirtyProperties2 & NET::WM2GTKFrameExtents) { + setClientFrameExtents(info->gtkFrameExtents()); + } } const uint8_t eventType = e->response_type & ~0x80; @@ -754,8 +757,6 @@ void X11Client::propertyNotifyEvent(xcb_property_notify_event_t *e) updateColorScheme(); else if (e->atom == atoms->kde_screen_edge_show) updateShowOnScreenEdge(); - else if (e->atom == atoms->gtk_frame_extents) - detectGtkFrameExtents(); else if (e->atom == atoms->kde_net_wm_appmenu_service_name) checkApplicationMenuServiceName(); else if (e->atom == atoms->kde_net_wm_appmenu_object_path) diff --git a/geometry.cpp b/geometry.cpp index 335c11b589..bdef7a8224 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1323,8 +1323,7 @@ void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const { // first, get the window size for the given frame size s - QSize wsize(frame.width() - (borderLeft() + borderRight()), - frame.height() - (borderTop() + borderBottom())); + QSize wsize = frameSizeToClientSize(frame); if (wsize.isEmpty()) wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 1)); @@ -1507,11 +1506,11 @@ QSize X11Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool nofra h = h1; } + QSize size(w, h); if (!noframe) { - w += borderLeft() + borderRight(); - h += borderTop() + borderBottom(); + size = clientSizeToFrameSize(size); } - return rules()->checkSize(QSize(w, h)); + return rules()->checkSize(size); } /** @@ -1532,7 +1531,7 @@ void X11Client::getWmNormalHints() // update to match restrictions QSize new_size = adjustedSize(); if (new_size != size() && !isFullScreen()) { - QRect origClientGeometry(pos() + clientPos(), clientSize()); + QRect origClientGeometry = m_clientGeometry; resizeWithChecks(new_size); if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, @@ -1575,10 +1574,10 @@ void X11Client::sendSyntheticConfigureNotify() c.response_type = XCB_CONFIGURE_NOTIFY; c.event = window(); c.window = window(); - c.x = x() + clientPos().x(); - c.y = y() + clientPos().y(); - c.width = clientSize().width(); - c.height = clientSize().height(); + c.x = m_clientGeometry.x(); + c.y = m_clientGeometry.y(); + c.width = m_clientGeometry.width(); + c.height = m_clientGeometry.height(); c.border_width = 0; c.above_sibling = XCB_WINDOW_NONE; c.override_redirect = 0; @@ -1586,15 +1585,17 @@ void X11Client::sendSyntheticConfigureNotify() xcb_flush(connection()); } -const QPoint X11Client::calculateGravitation(bool invert, int gravity) const +QPoint X11Client::gravityAdjustment(xcb_gravity_t gravity) const { - int dx, dy; - dx = dy = 0; + int dx = 0; + int dy = 0; - if (gravity == 0) // default (nonsense) value for the argument - gravity = m_geometryHints.windowGravity(); - -// dx, dy specify how the client window moves to make space for the frame + // dx, dy specify how the client window moves to make space for the frame. + // In general we have to compute the reference point and from that figure + // out how much we need to shift the client, however given that we ignore + // the border width attribute and the extents of the server-side decoration + // are known in advance, we can simplify the math quite a bit and express + // the required window gravity adjustment in terms of border sizes. switch(gravity) { case XCB_GRAVITY_NORTH_WEST: // move down right default: @@ -1614,7 +1615,9 @@ const QPoint X11Client::calculateGravitation(bool invert, int gravity) const dy = 0; break; case XCB_GRAVITY_CENTER: - break; // will be handled specially + dx = (borderLeft() - borderRight()) / 2; + dy = (borderTop() - borderBottom()) / 2; + break; case XCB_GRAVITY_STATIC: // don't move dx = 0; dy = 0; @@ -1636,15 +1639,18 @@ const QPoint X11Client::calculateGravitation(bool invert, int gravity) const dy = -borderBottom(); break; } - if (gravity != XCB_GRAVITY_CENTER) { - // translate from client movement to frame movement - dx -= borderLeft(); - dy -= borderTop(); - } else { - // center of the frame will be at the same position client center without frame would be - dx = - (borderLeft() + borderRight()) / 2; - dy = - (borderTop() + borderBottom()) / 2; - } + + return QPoint(dx, dy); +} + +const QPoint X11Client::calculateGravitation(bool invert) const +{ + const QPoint adjustment = gravityAdjustment(m_geometryHints.windowGravity()); + + // translate from client movement to frame movement + const int dx = adjustment.x() - borderLeft(); + const int dy = adjustment.y() - borderTop(); + if (!invert) return QPoint(x() + dx, y() + dy); else @@ -1700,23 +1706,25 @@ void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, if (gravity == 0) // default (nonsense) value for the argument gravity = m_geometryHints.windowGravity(); if (value_mask & configurePositionMask) { - QPoint new_pos = calculateGravitation(true, gravity); // undo gravitation + QPoint new_pos = framePosToClientPos(pos()); + new_pos -= gravityAdjustment(xcb_gravity_t(gravity)); if (value_mask & XCB_CONFIG_WINDOW_X) { new_pos.setX(rx); } if (value_mask & XCB_CONFIG_WINDOW_Y) { new_pos.setY(ry); } - // clever(?) workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager - if (new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() + if (new_pos.x() == m_clientGeometry.x() && new_pos.y() == m_clientGeometry.y() && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) { new_pos.setX(x()); new_pos.setY(y()); } + new_pos += gravityAdjustment(xcb_gravity_t(gravity)); + new_pos = clientPosToFramePos(new_pos); int nw = clientSize().width(); int nh = clientSize().height(); @@ -1732,11 +1740,10 @@ void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, if (newScreen != rules()->checkScreen(newScreen)) return; // not allowed by rule - QRect origClientGeometry(pos() + clientPos(), clientSize()); + QRect origClientGeometry = m_clientGeometry; GeometryUpdatesBlocker blocker(this); move(new_pos); plainResize(ns); - setFrameGeometry(QRect(calculateGravitation(false, gravity), size())); QRect area = workspace()->clientArea(WorkArea, this); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() && area.contains(origClientGeometry)) @@ -1762,7 +1769,7 @@ void X11Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, QSize ns = sizeForClientSize(QSize(nw, nh)); if (ns != size()) { // don't restore if some app sets its own size again - QRect origClientGeometry(pos() + clientPos(), clientSize()); + QRect origClientGeometry = m_clientGeometry; GeometryUpdatesBlocker blocker(this); resizeWithChecks(ns, xcb_gravity_t(gravity)); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { @@ -1938,25 +1945,34 @@ void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t for // Such code is wrong and should be changed to handle the case when the window is shaded, // for example using X11Client::clientSize() + QRect frameGeometry(x, y, w, h); + QRect bufferGeometry; + if (shade_geometry_change) ; // nothing else if (isShade()) { - if (h == borderTop() + borderBottom()) { + if (frameGeometry.height() == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { - client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); - h = borderTop() + borderBottom(); + m_clientGeometry = frameRectToClientRect(frameGeometry); + frameGeometry.setHeight(borderTop() + borderBottom()); } } else { - client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); + m_clientGeometry = frameRectToClientRect(frameGeometry); } - QRect g(x, y, w, h); - if (!areGeometryUpdatesBlocked() && g != rules()->checkGeometry(g)) { - qCDebug(KWIN_CORE) << "forced geometry fail:" << g << ":" << rules()->checkGeometry(g); + if (isDecorated()) { + bufferGeometry = frameGeometry; + } else { + bufferGeometry = m_clientGeometry; } - if (force == NormalGeometrySet && geom == g && pendingGeometryUpdate() == PendingGeometryNone) + if (!areGeometryUpdatesBlocked() && frameGeometry != rules()->checkGeometry(frameGeometry)) { + qCDebug(KWIN_CORE) << "forced geometry fail:" << frameGeometry << ":" << rules()->checkGeometry(frameGeometry); + } + if (!canUpdateGeometry(frameGeometry, bufferGeometry, force)) { return; - geom = g; + } + m_bufferGeometry = bufferGeometry; + geom = frameGeometry; if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed @@ -1966,11 +1982,11 @@ void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t for setPendingGeometryUpdate(PendingGeometryNormal); return; } - QSize oldClientSize = m_frame.geometry().size(); - bool resized = (frameGeometryBeforeUpdateBlocking().size() != geom.size() || pendingGeometryUpdate() == PendingGeometryForced); + const QRect oldBufferGeometry = bufferGeometryBeforeUpdateBlocking(); + bool resized = (oldBufferGeometry.size() != m_bufferGeometry.size() || pendingGeometryUpdate() == PendingGeometryForced); if (resized) { resizeDecoration(); - m_frame.setGeometry(x, y, w, h); + m_frame.setGeometry(m_bufferGeometry); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); @@ -1986,9 +2002,9 @@ void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t for if (compositing()) // Defer the X update until we leave this mode needsXWindowMove = true; else - m_frame.move(x, y); // sendSyntheticConfigureNotify() on finish shall be sufficient + m_frame.move(m_bufferGeometry.topLeft()); // sendSyntheticConfigureNotify() on finish shall be sufficient } else { - m_frame.move(x, y); + m_frame.move(m_bufferGeometry.topLeft()); sendSyntheticConfigureNotify(); } @@ -2002,11 +2018,9 @@ void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t for screens()->setCurrent(this); workspace()->updateStackingOrder(); - // need to regenerate decoration pixmaps when - // - size is changed - if (resized) { - if (oldClientSize != QSize(w,h)) - discardWindowPixmap(); + // Need to regenerate decoration pixmaps when the buffer size is changed. + if (oldBufferGeometry.size() != m_bufferGeometry.size()) { + discardWindowPixmap(); } emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); @@ -2017,28 +2031,37 @@ void X11Client::setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t for void X11Client::plainResize(int w, int h, ForceGeometry_t force) { + QSize frameSize(w, h); + QSize bufferSize; + // this code is also duplicated in X11Client::setGeometry(), and it's also commented there if (shade_geometry_change) ; // nothing else if (isShade()) { - if (h == borderTop() + borderBottom()) { + if (frameSize.height() == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { - client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); - h = borderTop() + borderBottom(); + m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); + frameSize.setHeight(borderTop() + borderBottom()); } } else { - client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); + m_clientGeometry.setSize(frameSizeToClientSize(frameSize)); } - QSize s(w, h); - if (!areGeometryUpdatesBlocked() && s != rules()->checkSize(s)) { - qCDebug(KWIN_CORE) << "forced size fail:" << s << ":" << rules()->checkSize(s); + if (isDecorated()) { + bufferSize = frameSize; + } else { + bufferSize = m_clientGeometry.size(); + } + if (!areGeometryUpdatesBlocked() && frameSize != rules()->checkSize(frameSize)) { + qCDebug(KWIN_CORE) << "forced size fail:" << frameSize << ":" << rules()->checkSize(frameSize); } // resuming geometry updates is handled only in setGeometry() Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); - if (force == NormalGeometrySet && geom.size() == s) + if (!canUpdateSize(frameSize, bufferSize, force)) { return; - geom.setSize(s); + } + m_bufferGeometry.setSize(bufferSize); + geom.setSize(frameSize); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed @@ -2048,10 +2071,8 @@ void X11Client::plainResize(int w, int h, ForceGeometry_t force) setPendingGeometryUpdate(PendingGeometryNormal); return; } - QSize oldClientSize = m_frame.geometry().size(); resizeDecoration(); - m_frame.resize(w, h); -// resizeDecoration( s ); + m_frame.resize(m_bufferGeometry.size()); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); @@ -2063,8 +2084,9 @@ void X11Client::plainResize(int w, int h, ForceGeometry_t force) updateWindowRules(Rules::Position|Rules::Size); screens()->setCurrent(this); workspace()->updateStackingOrder(); - if (oldClientSize != QSize(w,h)) + if (bufferGeometryBeforeUpdateBlocking().size() != m_bufferGeometry.size()) { discardWindowPixmap(); + } emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); @@ -2105,12 +2127,6 @@ void AbstractClient::move(int x, int y, ForceGeometry_t force) emit geometryChanged(); } -void X11Client::doMove(int x, int y) -{ - m_frame.move(x, y); - sendSyntheticConfigureNotify(); -} - void AbstractClient::blockGeometryUpdates(bool block) { if (block) { @@ -2657,7 +2673,7 @@ void X11Client::leaveMoveResize() { if (needsXWindowMove) { // Do the deferred move - m_frame.move(geom.topLeft()); + m_frame.move(m_bufferGeometry.topLeft()); needsXWindowMove = false; } if (!isResize()) @@ -3110,8 +3126,8 @@ void X11Client::doResizeSync() syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11 syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed } // and no human can control faster resizes anyway - const QRect &moveResizeGeom = moveResizeGeometry(); - m_client.setGeometry(0, 0, moveResizeGeom.width() - (borderLeft() + borderRight()), moveResizeGeom.height() - (borderTop() + borderBottom())); + const QRect moveResizeClientGeometry = frameRectToClientRect(moveResizeGeometry()); + m_client.setGeometry(0, 0, moveResizeClientGeometry.width(), moveResizeClientGeometry.height()); } void AbstractClient::performMoveResize() diff --git a/manage.cpp b/manage.cpp index 9741f4390b..ce30263b3a 100644 --- a/manage.cpp +++ b/manage.cpp @@ -95,11 +95,11 @@ bool X11Client::manage(xcb_window_t w, bool isMapped) NET::WM2InitialMappingState | NET::WM2IconPixmap | NET::WM2OpaqueRegion | - NET::WM2DesktopFileName; + NET::WM2DesktopFileName | + NET::WM2GTKFrameExtents; auto wmClientLeaderCookie = fetchWmClientLeader(); auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); - auto gtkFrameExtentsCookie = fetchGtkFrameExtents(); auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); auto colorSchemeCookie = fetchColorScheme(); auto firstInTabBoxCookie = fetchFirstInTabBox(); @@ -138,9 +138,9 @@ bool X11Client::manage(xcb_window_t w, bool isMapped) if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), true); detectShape(window()); - readGtkFrameExtents(gtkFrameExtentsCookie); detectNoBorder(); fetchIconicName(); + setClientFrameExtents(info->gtkFrameExtents()); // Needs to be done before readTransient() because of reading the group checkGroup(); @@ -323,8 +323,14 @@ bool X11Client::manage(xcb_window_t w, bool isMapped) if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) placementDone = false; // Weird, do not trust. - if (placementDone) - move(geom.x(), geom.y()); // Before gravitating + if (placementDone) { + QPoint position = geom.topLeft(); + // Session contains the position of the frame geometry before gravitating. + if (!session) { + position = clientPosToFramePos(position); + } + move(position); + } // Create client group if the window will have a decoration bool dontKeepInArea = false; diff --git a/netinfo.cpp b/netinfo.cpp index 4fa340a5f4..65c91b21dd 100644 --- a/netinfo.cpp +++ b/netinfo.cpp @@ -110,7 +110,8 @@ RootInfo *RootInfo::create() NET::WM2FullPlacement | NET::WM2FullscreenMonitors | NET::WM2KDEShadow | - NET::WM2OpaqueRegion; + NET::WM2OpaqueRegion | + NET::WM2GTKFrameExtents; #ifdef KWIN_BUILD_ACTIVITIES properties2 |= NET::WM2Activities; #endif diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index 424311a743..3897525554 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -861,7 +861,7 @@ bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visuali bool GlxTexture::loadTexture(WindowPixmap *pixmap) { Toplevel *t = pixmap->toplevel(); - return loadTexture(pixmap->pixmap(), t->size(), t->visual()); + return loadTexture(pixmap->pixmap(), t->bufferGeometry().size(), t->visual()); } OpenGLBackend *GlxTexture::backend() diff --git a/toplevel.cpp b/toplevel.cpp index 811b6b14ca..d66aac3ff2 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -408,8 +408,11 @@ void Toplevel::getDamageRegionReply() region += QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height); + const QRect bufferRect = bufferGeometry(); + const QRect frameRect = frameGeometry(); + damage_region += region; - repaints_region += region; + repaints_region += region.translated(bufferRect.topLeft() - frameRect.topLeft()); free(reply); } diff --git a/x11client.cpp b/x11client.cpp index f4ce2278b8..e5e0aa90d0 100644 --- a/x11client.cpp +++ b/x11client.cpp @@ -31,6 +31,7 @@ along with this program. If not, see . #include "deleted.h" #include "focuschain.h" #include "group.h" +#include "screens.h" #include "shadow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" @@ -130,7 +131,6 @@ X11Client::X11Client() , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) - , m_clientSideDecorated(false) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; @@ -158,7 +158,6 @@ X11Client::X11Client() //client constructed be connected to the workspace wrapper geom = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) - client_size = QSize(100, 100); connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &X11Client::updateCaption); @@ -256,7 +255,7 @@ void X11Client::releaseWindow(bool on_shutdown) m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); m_client.deleteProperty(atoms->net_frame_extents); m_client.deleteProperty(atoms->kde_net_wm_frame_strut); - m_client.reparent(rootWindow(), x(), y()); + m_client.reparent(rootWindow(), m_bufferGeometry.x(), m_bufferGeometry.y()); xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); if (on_shutdown) @@ -549,21 +548,29 @@ void X11Client::updateFrameExtents() info->setFrameExtents(strut); } -Xcb::Property X11Client::fetchGtkFrameExtents() const +void X11Client::setClientFrameExtents(const NETStrut &strut) { - return Xcb::Property(false, m_client, atoms->gtk_frame_extents, XCB_ATOM_CARDINAL, 0, 4); -} + const QMargins clientFrameExtents(strut.left, strut.top, strut.right, strut.bottom); + if (m_clientFrameExtents == clientFrameExtents) { + return; + } -void X11Client::readGtkFrameExtents(Xcb::Property &prop) -{ - m_clientSideDecorated = !prop.isNull() && prop->type != 0; - emit clientSideDecoratedChanged(); -} + const bool wasClientSideDecorated = isClientSideDecorated(); + m_clientFrameExtents = clientFrameExtents; -void X11Client::detectGtkFrameExtents() -{ - Xcb::Property prop = fetchGtkFrameExtents(); - readGtkFrameExtents(prop); + // We should resize the client when its custom frame extents are changed so + // the logical bounds remain the same. This however means that we will send + // several configure requests to the application upon restoring it from the + // maximized or fullscreen state. Notice that a client-side decorated client + // cannot be shaded, therefore it's okay not to use the adjusted size here. + setFrameGeometry(frameGeometry()); + + if (wasClientSideDecorated != isClientSideDecorated()) { + emit clientSideDecoratedChanged(); + } + + // This will invalidate the window quads cache. + emit geometryShapeChanged(this, frameGeometry()); } /** @@ -607,6 +614,11 @@ bool X11Client::noBorder() const bool X11Client::userCanSetNoBorder() const { + // Client-side decorations and server-side decorations are mutually exclusive. + if (isClientSideDecorated()) { + return false; + } + return !isFullScreen() && !isShade(); } @@ -1973,7 +1985,7 @@ xcb_window_t X11Client::frameId() const QRect X11Client::bufferGeometry() const { - return geom; + return m_bufferGeometry; } QMargins X11Client::bufferMargins() const @@ -1981,6 +1993,70 @@ QMargins X11Client::bufferMargins() const return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); } +QPoint X11Client::framePosToClientPos(const QPoint &point) const +{ + int x = point.x(); + int y = point.y(); + + if (isDecorated()) { + x += borderLeft(); + y += borderTop(); + } else { + x -= m_clientFrameExtents.left(); + y -= m_clientFrameExtents.top(); + } + + return QPoint(x, y); +} + +QPoint X11Client::clientPosToFramePos(const QPoint &point) const +{ + int x = point.x(); + int y = point.y(); + + if (isDecorated()) { + x -= borderLeft(); + y -= borderTop(); + } else { + x += m_clientFrameExtents.left(); + y += m_clientFrameExtents.top(); + } + + return QPoint(x, y); +} + +QSize X11Client::frameSizeToClientSize(const QSize &size) const +{ + int width = size.width(); + int height = size.height(); + + if (isDecorated()) { + width -= borderLeft() + borderRight(); + height -= borderTop() + borderBottom(); + } else { + width += m_clientFrameExtents.left() + m_clientFrameExtents.right(); + height += m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); + } + + return QSize(width, height); +} + +QSize X11Client::clientSizeToFrameSize(const QSize &size) const +{ + int width = size.width(); + int height = size.height(); + + if (isDecorated()) { + width += borderLeft() + borderRight(); + height += borderTop() + borderBottom(); + } else { + width -= m_clientFrameExtents.left() + m_clientFrameExtents.right(); + height -= m_clientFrameExtents.top() + m_clientFrameExtents.bottom(); + } + + return QSize(width, height); +} + Xcb::Property X11Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); @@ -2075,7 +2151,7 @@ void X11Client::addDamage(const QRegion &damage) setupWindowManagementInterface(); } } - repaints_region += damage; + repaints_region += damage.translated(bufferGeometry().topLeft() - frameGeometry().topLeft()); Toplevel::addDamage(damage); } @@ -2140,5 +2216,84 @@ void X11Client::handleSync() addRepaintFull(); } +bool X11Client::canUpdatePosition(const QPoint &frame, const QPoint &buffer, ForceGeometry_t force) const +{ + // Obey forced geometry updates. + if (force != NormalGeometrySet) { + return true; + } + // Server-side geometry and our geometry are out of sync. + if (bufferGeometry().topLeft() != buffer) { + return true; + } + if (frameGeometry().topLeft() != frame) { + return true; + } + return false; +} + +bool X11Client::canUpdateSize(const QSize &frame, const QSize &buffer, ForceGeometry_t force) const +{ + // Obey forced geometry updates. + if (force != NormalGeometrySet) { + return true; + } + // Server-side geometry and our geometry are out of sync. + if (bufferGeometry().size() != buffer) { + return true; + } + if (frameGeometry().size() != frame) { + return true; + } + return false; +} + +bool X11Client::canUpdateGeometry(const QRect &frame, const QRect &buffer, ForceGeometry_t force) const +{ + if (canUpdatePosition(frame.topLeft(), buffer.topLeft(), force)) { + return true; + } + if (canUpdateSize(frame.size(), buffer.size(), force)) { + return true; + } + return pendingGeometryUpdate() != PendingGeometryNone; +} + +void X11Client::move(int x, int y, ForceGeometry_t force) +{ + const QPoint framePosition(x, y); + m_clientGeometry.moveTopLeft(framePosToClientPos(framePosition)); + const QPoint bufferPosition = isDecorated() ? framePosition : m_clientGeometry.topLeft(); + // resuming geometry updates is handled only in setGeometry() + Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); + if (!areGeometryUpdatesBlocked() && framePosition != rules()->checkPosition(framePosition)) { + qCDebug(KWIN_CORE) << "forced position fail:" << framePosition << ":" << rules()->checkPosition(framePosition); + } + if (!canUpdatePosition(framePosition, bufferPosition, force)) { + return; + } + m_bufferGeometry.moveTopLeft(bufferPosition); + geom.moveTopLeft(framePosition); + if (areGeometryUpdatesBlocked()) { + if (pendingGeometryUpdate() == PendingGeometryForced) { + // Maximum, nothing needed. + } else if (force == ForceGeometrySet) { + setPendingGeometryUpdate(PendingGeometryForced); + } else { + setPendingGeometryUpdate(PendingGeometryNormal); + } + return; + } + m_frame.move(m_bufferGeometry.topLeft()); + sendSyntheticConfigureNotify(); + updateWindowRules(Rules::Position); + screens()->setCurrent(this); + workspace()->updateStackingOrder(); + // client itself is not damaged + addRepaintDuringGeometryUpdates(); + updateGeometryBeforeUpdateBlocking(); + emit geometryChanged(); +} + } // namespace diff --git a/x11client.h b/x11client.h index 3107e2d986..06867d9054 100644 --- a/x11client.h +++ b/x11client.h @@ -92,6 +92,11 @@ public: QRect bufferGeometry() const override; QMargins bufferMargins() const override; + QPoint framePosToClientPos(const QPoint &point) const override; + QPoint clientPosToFramePos(const QPoint &point) const override; + QSize frameSizeToClientSize(const QSize &size) const override; + QSize clientSizeToFrameSize(const QSize &size) const override; + bool isTransient() const override; bool groupTransient() const override; bool wasOriginallyGroupTransient() const; @@ -176,6 +181,8 @@ public: void updateShape(); + using AbstractClient::move; + void move(int x, int y, ForceGeometry_t force = NormalGeometrySet) override; using AbstractClient::setFrameGeometry; void setFrameGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes @@ -220,7 +227,8 @@ public: void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; - const QPoint calculateGravitation(bool invert, int gravity = 0) const; // FRAME public? + QPoint gravityAdjustment(xcb_gravity_t gravity) const; + const QPoint calculateGravitation(bool invert) const; void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); @@ -360,7 +368,6 @@ protected: void doSetSkipSwitcher() override; bool belongsToDesktop() const override; void setGeometryRestore(const QRect &geo) override; - void doMove(int x, int y) override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; @@ -436,11 +443,12 @@ private: void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); - Xcb::Property fetchGtkFrameExtents() const; - void readGtkFrameExtents(Xcb::Property &prop); - void detectGtkFrameExtents(); void destroyDecoration() override; void updateFrameExtents(); + void setClientFrameExtents(const NETStrut &strut); + bool canUpdatePosition(const QPoint &frame, const QPoint &buffer, ForceGeometry_t force) const; + bool canUpdateSize(const QSize &frame, const QSize &buffer, ForceGeometry_t force) const; + bool canUpdateGeometry(const QRect &frame, const QRect &buffer, ForceGeometry_t force) const; void internalShow(); void internalHide(); @@ -514,6 +522,8 @@ private: } m_fullscreenMode; MaximizeMode max_mode; + QRect m_bufferGeometry = QRect(0, 0, 100, 100); + QRect m_clientGeometry = QRect(0, 0, 100, 100); QRect geom_restore; QRect geom_fs_restore; QTimer* shadeHoverTimer; @@ -525,7 +535,6 @@ private: xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; - QSize client_size; bool shade_geometry_change; SyncRequest syncRequest; static bool check_active_modal; ///< \see X11Client::checkActiveModal() @@ -548,10 +557,11 @@ private: QTimer *m_focusOutTimer; QList m_connections; - bool m_clientSideDecorated; QMetaObject::Connection m_edgeRemoveConnection; QMetaObject::Connection m_edgeGeometryTrackingConnection; + + QMargins m_clientFrameExtents; }; inline xcb_window_t X11Client::wrapperId() const @@ -561,7 +571,7 @@ inline xcb_window_t X11Client::wrapperId() const inline bool X11Client::isClientSideDecorated() const { - return m_clientSideDecorated; + return !m_clientFrameExtents.isNull(); } inline bool X11Client::groupTransient() const @@ -648,7 +658,7 @@ inline bool X11Client::isManaged() const inline QSize X11Client::clientSize() const { - return client_size; + return m_clientGeometry.size(); } inline void X11Client::plainResize(const QSize& s, ForceGeometry_t force)