diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -624,7 +624,7 @@ 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 @@ 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/abstract_client.cpp b/abstract_client.cpp --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -2009,4 +2009,42 @@ 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/atoms.h b/atoms.h --- a/atoms.h +++ b/atoms.h @@ -73,7 +73,6 @@ 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/atoms.cpp b/atoms.cpp --- a/atoms.cpp +++ b/atoms.cpp @@ -64,7 +64,6 @@ , 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/events.cpp b/events.cpp --- a/events.cpp +++ b/events.cpp @@ -482,6 +482,9 @@ 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 @@ 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 --- a/geometry.cpp +++ b/geometry.cpp @@ -1323,8 +1323,7 @@ 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 @@ 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 @@ // 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,26 +1574,28 @@ 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; xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&c)); 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ // 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 @@ 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 @@ 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 @@ 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::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)); + } + if (isDecorated()) { + bufferSize = frameSize; + } else { + bufferSize = m_clientGeometry.size(); } - QSize s(w, h); - if (!areGeometryUpdatesBlocked() && s != rules()->checkSize(s)) { - qCDebug(KWIN_CORE) << "forced size fail:" << s << ":" << rules()->checkSize(s); + 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 @@ 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 @@ 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 @@ 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 @@ { 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 @@ 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 --- a/manage.cpp +++ b/manage.cpp @@ -95,11 +95,11 @@ 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 @@ 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 @@ 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 --- a/netinfo.cpp +++ b/netinfo.cpp @@ -110,7 +110,8 @@ 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 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -861,7 +861,7 @@ 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 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -408,8 +408,11 @@ 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.h b/x11client.h --- a/x11client.h +++ b/x11client.h @@ -92,6 +92,11 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } 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 @@ 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 @@ 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 bool X11Client::isClientSideDecorated() const { - return m_clientSideDecorated; + return !m_clientFrameExtents.isNull(); } inline bool X11Client::groupTransient() const @@ -648,7 +658,7 @@ inline QSize X11Client::clientSize() const { - return client_size; + return m_clientGeometry.size(); } inline void X11Client::plainResize(const QSize& s, ForceGeometry_t force) diff --git a/x11client.cpp b/x11client.cpp --- a/x11client.cpp +++ b/x11client.cpp @@ -31,6 +31,7 @@ #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 @@ , 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 @@ //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 @@ 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 @@ 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::userCanSetNoBorder() const { + // Client-side decorations and server-side decorations are mutually exclusive. + if (isClientSideDecorated()) { + return false; + } + return !isFullScreen() && !isShade(); } @@ -1973,14 +1985,78 @@ QRect X11Client::bufferGeometry() const { - return geom; + return m_bufferGeometry; } 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 @@ setupWindowManagementInterface(); } } - repaints_region += damage; + repaints_region += damage.translated(bufferGeometry().topLeft() - frameGeometry().topLeft()); Toplevel::addDamage(damage); } @@ -2140,5 +2216,84 @@ 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