Commit 0de2b733 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

Introduce transient constraints api in Workspace

Currently, the Workspace has no any api to constrain one window above
another. This results in having hacks such as keepDeletedTransientAbove()

This change introduces a basic api to constrain a given window above
another. It can be used for ensuring that transient windows are placed
above their parents. It also can be used for stacking the outline window
below the move-resize window.

Internal windows may also have transient parents. Because of that, this
change makes the workspace add internal clients to the stacking order by
default. The good thing about it is that it allows us unify some input
related code for "external" windows and internal windows.
parent 9b2b450f
......@@ -2009,11 +2009,38 @@ bool AbstractClient::isModal() const
return m_modal;
}
// check whether a transient should be actually kept above its mainwindow
// there may be some special cases where this rule shouldn't be enfored
static bool shouldKeepTransientAbove(const AbstractClient *parent, const AbstractClient *transient)
{
// #93832 - don't keep splashscreens above dialogs
if (transient->isSplash() && parent->isDialog()) {
return false;
}
// This is rather a hack for #76026. Don't keep non-modal dialogs above
// the mainwindow, but only if they're group transient (since only such dialogs
// have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker)
// needs to be found.
if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) {
return false;
}
// #63223 - don't keep transients above docks, because the dock is kept high,
// and e.g. dialogs for them would be too high too
// ignore this if the transient has a placement hint which indicates it should go above it's parent
if (parent->isDock() && !transient->hasTransientPlacementHint()) {
return false;
}
return true;
}
void AbstractClient::addTransient(AbstractClient *cl)
{
Q_ASSERT(!m_transients.contains(cl));
Q_ASSERT(cl != this);
m_transients.append(cl);
if (shouldKeepTransientAbove(this, cl)) {
workspace()->constrain(this, cl);
}
}
void AbstractClient::removeTransient(AbstractClient *cl)
......@@ -2022,6 +2049,7 @@ void AbstractClient::removeTransient(AbstractClient *cl)
if (cl->transientFor() == this) {
cl->setTransientFor(nullptr);
}
workspace()->unconstrain(this, cl);
}
void AbstractClient::removeTransientFromList(AbstractClient *cl)
......
......@@ -10,11 +10,10 @@
#include "deleted.h"
#include "workspace.h"
#include "x11client.h"
#include "abstract_client.h"
#include "group.h"
#include "netinfo.h"
#include "shadow.h"
#include "waylandclient.h"
#include "decorations/decoratedclient.h"
#include "decorations/decorationrenderer.h"
......@@ -35,10 +34,6 @@ Deleted::Deleted()
, m_fullscreen(false)
, m_keepAbove(false)
, m_keepBelow(false)
, m_wasActive(false)
, m_wasX11Client(false)
, m_wasWaylandClient(false)
, m_wasGroupTransient(false)
, m_wasPopupWindow(false)
, m_wasOutline(false)
, m_wasDecorated(false)
......@@ -53,14 +48,6 @@ Deleted::~Deleted()
if (workspace()) {
workspace()->removeDeleted(this);
}
for (Toplevel *toplevel : qAsConst(m_transientFor)) {
if (auto *deleted = qobject_cast<Deleted *>(toplevel)) {
deleted->removeTransient(this);
}
}
for (Deleted *transient : qAsConst(m_transients)) {
transient->removeTransientFor(this);
}
deleteEffectWindow();
}
......@@ -117,17 +104,12 @@ void Deleted::copyToDeleted(Toplevel* c)
m_modal = client->isModal();
m_mainClients = client->mainClients();
foreach (AbstractClient *c, m_mainClients) {
addTransientFor(c);
connect(c, &AbstractClient::windowClosed, this, &Deleted::mainClientClosed);
}
m_fullscreen = client->isFullScreen();
m_keepAbove = client->keepAbove();
m_keepBelow = client->keepBelow();
m_caption = client->caption();
m_wasActive = client->isActive();
m_wasGroupTransient = client->groupTransient();
}
for (auto vd : m_desktops) {
......@@ -136,8 +118,6 @@ void Deleted::copyToDeleted(Toplevel* c)
});
}
m_wasWaylandClient = qobject_cast<WaylandClient *>(c) != nullptr;
m_wasX11Client = qobject_cast<X11Client *>(c) != nullptr;
m_wasPopupWindow = c->isPopupWindow();
m_wasOutline = c->isOutline();
}
......@@ -224,22 +204,6 @@ void Deleted::mainClientClosed(Toplevel *client)
m_mainClients.removeAll(c);
}
void Deleted::transientForClosed(Toplevel *toplevel, Deleted *deleted)
{
if (deleted == nullptr) {
m_transientFor.removeAll(toplevel);
return;
}
const int index = m_transientFor.indexOf(toplevel);
if (index == -1) {
return;
}
m_transientFor[index] = deleted;
deleted->addTransient(this);
}
xcb_window_t Deleted::frameId() const
{
return m_frame;
......@@ -264,26 +228,5 @@ QVector<uint> Deleted::x11DesktopIds() const
return x11Ids;
}
void Deleted::addTransient(Deleted *transient)
{
m_transients.append(transient);
}
void Deleted::removeTransient(Deleted *transient)
{
m_transients.removeAll(transient);
}
void Deleted::addTransientFor(AbstractClient *parent)
{
m_transientFor.append(parent);
connect(parent, &AbstractClient::windowClosed, this, &Deleted::transientForClosed);
}
void Deleted::removeTransientFor(Deleted *parent)
{
m_transientFor.removeAll(parent);
}
} // namespace
......@@ -83,72 +83,6 @@ public:
return m_caption;
}
/**
* Returns whether the client was active.
*
* @returns @c true if the client was active at the time when it was closed,
* @c false otherwise
*/
bool wasActive() const {
return m_wasActive;
}
/**
* Returns whether this was an X11 client.
*
* @returns @c true if it was an X11 client, @c false otherwise.
*/
bool wasX11Client() const {
return m_wasX11Client;
}
/**
* Returns whether this was a Wayland client.
*
* @returns @c true if it was a Wayland client, @c false otherwise.
*/
bool wasWaylandClient() const {
return m_wasWaylandClient;
}
/**
* Returns whether the client was a transient.
*
* @returns @c true if it was a transient, @c false otherwise.
*/
bool wasTransient() const {
return !m_transientFor.isEmpty();
}
/**
* Returns whether the client was a group transient.
*
* @returns @c true if it was a group transient, @c false otherwise.
* @note This is relevant only for X11 clients.
*/
bool wasGroupTransient() const {
return m_wasGroupTransient;
}
/**
* Checks whether this client was a transient for given toplevel.
*
* @param toplevel Toplevel against which we are testing.
* @returns @c true if it was a transient for given toplevel, @c false otherwise.
*/
bool wasTransientFor(const Toplevel *toplevel) const {
return m_transientFor.contains(const_cast<Toplevel *>(toplevel));
}
/**
* Returns the list of transients.
*
* Because the window is Deleted, it can have only Deleted child transients.
*/
QList<Deleted *> transients() const {
return m_transients;
}
/**
* Returns whether the client was a popup.
*
......@@ -169,18 +103,12 @@ public:
private Q_SLOTS:
void mainClientClosed(KWin::Toplevel *client);
void transientForClosed(Toplevel *toplevel, Deleted *deleted);
private:
Deleted(); // use create()
void copyToDeleted(Toplevel* c);
~Deleted() override; // deleted only using unrefWindow()
void addTransient(Deleted *transient);
void removeTransient(Deleted *transient);
void addTransientFor(AbstractClient *parent);
void removeTransientFor(Deleted *parent);
QRect m_bufferGeometry;
QMargins m_frameMargins;
......@@ -209,12 +137,6 @@ private:
bool m_keepAbove;
bool m_keepBelow;
QString m_caption;
bool m_wasActive;
bool m_wasX11Client;
bool m_wasWaylandClient;
bool m_wasGroupTransient;
QList<Toplevel *> m_transientFor;
QList<Deleted *> m_transients;
bool m_wasPopupWindow;
bool m_wasOutline;
bool m_wasDecorated;
......
......@@ -2580,44 +2580,10 @@ Toplevel *InputRedirection::findToplevel(const QPoint &pos)
return u;
}
}
if (Toplevel *window = findInternal(pos)) {
return window;
}
}
return findManagedToplevel(pos);
}
Toplevel *InputRedirection::findInternal(const QPoint &pos) const
{
const QList<InternalClient *> &internalClients = workspace()->internalClients();
if (internalClients.isEmpty()) {
return nullptr;
}
auto it = internalClients.end();
do {
--it;
QWindow *w = (*it)->internalWindow();
if (!w || !w->isVisible()) {
continue;
}
if (!(*it)->frameGeometry().contains(pos)) {
continue;
}
// check input mask
const QRegion mask = w->mask().translated(w->geometry().topLeft());
if (!mask.isEmpty() && !mask.contains(pos)) {
continue;
}
if (w->property("outputOnly").toBool()) {
continue;
}
return *it;
} while (it != internalClients.begin());
return nullptr;
}
Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos)
{
if (!Workspace::self()) {
......
......@@ -316,7 +316,6 @@ private:
void reconfigure();
void setupInputFilters();
void installInputEventFilter(InputEventFilter *filter);
Toplevel *findInternal(const QPoint &pos) const;
KeyboardInputRedirection *m_keyboard;
PointerInputRedirection *m_pointer;
TabletInputRedirection *m_tablet;
......
......@@ -68,6 +68,22 @@ InternalClient::~InternalClient()
{
}
bool InternalClient::hitTest(const QPoint &point) const
{
if (!AbstractClient::hitTest(point)) {
return false;
}
const QRegion mask = m_internalWindow->mask();
if (!mask.isEmpty() && !mask.contains(mapToLocal(point))) {
return false;
} else if (m_internalWindow->property("outputOnly").toBool()) {
return false;
}
return true;
}
bool InternalClient::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_internalWindow && event->type() == QEvent::DynamicPropertyChange) {
......
......@@ -60,6 +60,7 @@ public:
void destroyClient() override;
bool hasPopupGrab() const override;
void popupDone() override;
bool hitTest(const QPoint &point) const override;
void present(const QSharedPointer<QOpenGLFramebufferObject> fbo);
void present(const QImage &image, const QRegion &damage);
......
......@@ -86,6 +86,7 @@
#include "internal_client.h"
#include <QDebug>
#include <QQueue>
namespace KWin
{
......@@ -505,91 +506,43 @@ QList<Toplevel *> Workspace::constrainedStackingOrder()
for (int lay = FirstLayer; lay < NumLayers; ++lay) {
stacking += layer[lay];
}
// now keep transients above their mainwindows
// TODO this could(?) use some optimization
for (int i = stacking.size() - 1; i >= 0;) {
// Index of the main window for the current transient window.
int i2 = -1;
// If the current transient has "child" transients, we'd like to restart
// construction of the constrained stacking order from the position where
// the current transient will be moved.
bool hasTransients = false;
// Find topmost client this one is transient for.
if (auto *client = qobject_cast<AbstractClient *>(stacking[i])) {
if (!client->isTransient()) {
--i;
continue;
}
for (i2 = stacking.size() - 1; i2 >= 0; --i2) {
auto *c2 = qobject_cast<AbstractClient *>(stacking[i2]);
if (!c2) {
continue;
}
if (c2 == client) {
i2 = -1; // Don't reorder, already on top of its main window.
break;
}
if (c2->hasTransient(client, true)
&& keepTransientAbove(c2, client)) {
break;
}
}
hasTransients = !client->transients().isEmpty();
// If the current transient doesn't have any "alive" transients, check
// whether it has deleted transients that have to be raised.
const bool searchForDeletedTransients = !hasTransients
&& !deletedList().isEmpty();
if (searchForDeletedTransients) {
for (int j = i + 1; j < stacking.count(); ++j) {
auto *deleted = qobject_cast<Deleted *>(stacking[j]);
if (!deleted) {
continue;
}
if (deleted->wasTransientFor(client)) {
hasTransients = true;
break;
}
}
}
} else if (auto *deleted = qobject_cast<Deleted *>(stacking[i])) {
if (!deleted->wasTransient()) {
--i;
continue;
}
for (i2 = stacking.size() - 1; i2 >= 0; --i2) {
Toplevel *c2 = stacking[i2];
if (c2 == deleted) {
i2 = -1; // Don't reorder, already on top of its main window.
break;
}
if (deleted->wasTransientFor(c2)
&& keepDeletedTransientAbove(c2, deleted)) {
break;
}
}
hasTransients = !deleted->transients().isEmpty();
// Apply the stacking order constraints. First, we enqueue the root constraints, i.e.
// the ones that are not affected by other constraints.
QQueue<Constraint *> constraints;
constraints.reserve(m_constraints.count());
for (Constraint *constraint : qAsConst(m_constraints)) {
if (constraint->parents.isEmpty()) {
constraint->enqueued = true;
constraints.enqueue(constraint);
} else {
constraint->enqueued = false;
}
}
// Once we've enqueued all the root constraints, we traverse the constraints tree in
// the breadth-first search fashion. A constraint is applied only if its condition is
// not met.
while (!constraints.isEmpty()) {
Constraint *constraint = constraints.dequeue();
if (i2 == -1) {
--i;
const int belowIndex = stacking.indexOf(constraint->below);
const int aboveIndex = stacking.indexOf(constraint->above);
if (belowIndex == -1 || aboveIndex == -1) {
continue;
} else if (aboveIndex < belowIndex) {
stacking.removeAt(aboveIndex);
stacking.insert(belowIndex, constraint->above);
}
Toplevel *current = stacking[i];
stacking.removeAt(i);
--i; // move onto the next item (for next for () iteration)
--i2; // adjust index of the mainwindow after the remove above
if (hasTransients) { // this one now can be possibly above its transients,
i = i2; // so go again higher in the stack order and possibly move those transients again
for (Constraint *child : qAsConst(constraint->children)) {
if (!child->enqueued) {
child->enqueued = true;
constraints.enqueue(child);
}
}
++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end()
stacking.insert(i2, current);
}
return stacking;
}
......@@ -643,61 +596,6 @@ QList<AbstractClient*> Workspace::ensureStackingOrder(const QList<AbstractClient
return ensureStackingOrderInList(stacking_order, list);
}
// check whether a transient should be actually kept above its mainwindow
// there may be some special cases where this rule shouldn't be enfored
bool Workspace::keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient)
{
// #93832 - don't keep splashscreens above dialogs
if (transient->isSplash() && mainwindow->isDialog())
return false;
// This is rather a hack for #76026. Don't keep non-modal dialogs above
// the mainwindow, but only if they're group transient (since only such dialogs
// have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker)
// needs to be found.
if (transient->isDialog() && !transient->isModal() && transient->groupTransient())
return false;
// #63223 - don't keep transients above docks, because the dock is kept high,
// and e.g. dialogs for them would be too high too
// ignore this if the transient has a placement hint which indicates it should go above it's parent
if (mainwindow->isDock() && !transient->hasTransientPlacementHint())
return false;
return true;
}
bool Workspace::keepDeletedTransientAbove(const Toplevel *mainWindow, const Deleted *transient) const
{
// #93832 - Don't keep splashscreens above dialogs.
if (transient->isSplash() && mainWindow->isDialog()) {
return false;
}
if (transient->wasX11Client()) {
// If a group transient was active, we should keep it above no matter
// what, because at the time when the transient was closed, it was above
// the main window.
if (transient->wasGroupTransient() && transient->wasActive()) {
return true;
}
// This is rather a hack for #76026. Don't keep non-modal dialogs above
// the mainwindow, but only if they're group transient (since only such
// dialogs have taskbar entry in Kicker). A proper way of doing this
// (both kwin and kicker) needs to be found.
if (transient->wasGroupTransient() && transient->isDialog()
&& !transient->isModal()) {
return false;
}
// #63223 - Don't keep transients above docks, because the dock is kept
// high, and e.g. dialogs for them would be too high too.
if (mainWindow->isDock()) {
return false;
}
}
return true;
}
// Returns all windows in their stacking order on the root window.
QList<Toplevel *> Workspace::xStackingOrder() const
{
......@@ -734,12 +632,6 @@ void Workspace::updateXStackingOrder()
}
}
for (InternalClient *client : workspace()->internalClients()) {
if (client->isShown(false)) {
x_stacking.append(client);
}
}
m_xStackingDirty = false;
}
......
......@@ -464,16 +464,14 @@ void Workspace::cleanupX11()
const QList<X11Client *> orderedClients = ensureStackingOrder(clients);
for (X11Client *client : orderedClients) {
client->releaseWindow(true);
unconstrained_stacking_order.removeOne(client);
stacking_order.removeOne(client);
removeFromStack(client);
}
// We need a shadow copy because windows get removed as we go through them.
const QList<Unmanaged *> unmanaged = m_unmanaged;
for (Unmanaged *overrideRedirect : unmanaged) {
overrideRedirect->release(ReleaseReason::KWinShutsDown);
unconstrained_stacking_order.removeOne(overrideRedirect);
stacking_order.removeOne(overrideRedirect);
removeFromStack(overrideRedirect);
}
manual_overlays.clear();
......@@ -538,6 +536,138 @@ void Workspace::setupClientConnections(AbstractClient *c)
connect(c, &AbstractClient::minimizedChanged, this, std::bind(&Workspace::clientMinimizedChanged, this, c));
}
void Workspace::constrain(AbstractClient *below, AbstractClient *above)
{
if (below == above) {
return;
}
QList<Constraint *> parents;
QList<Constraint *> children;
for (Constraint *constraint : qAsConst(m_constraints)) {
if (constraint->below == below && constraint->above == above) {
return;
}
if (constraint->below == above) {
children << constraint;
} else if (constraint->above == below) {
parents << constraint;
}
}
Constraint *constraint = new Constraint();
constraint->parents = parents;
constraint->below = below;
constraint->above = above;
constraint->children = children;
m_constraints << constraint;
for (Constraint *parent : qAsConst(parents)) {
parent->children << constraint;
}
for (Constraint *child : qAsConst(children)) {
child->parents << constraint;
}
markXStackingOrderAsDirty();
updateXStackingOrder();
}
void Workspace::unconstrain(AbstractClient *below, AbstractClient *above)
{
Constraint *constraint = nullptr;
for (int i = 0; i < m_constraints.count(); ++i) {
if (m_constraints[i]->below == below && m_constraints[i]->above == above) {
constraint = m_constraints.takeAt(i);
break;
}
}