[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
This commit is contained in:
Vlad Zahorodnii 2019-10-08 11:46:59 +03:00
parent 4fbb777a30
commit 84d75cb567
12 changed files with 378 additions and 113 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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

View file

@ -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<QMetaObject::Connection> 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)