kwin/src/rules.cpp

1110 lines
35 KiB
C++
Raw Normal View History

2020-08-02 22:22:19 +00:00
/*
KWin - the KDE window manager
This file is part of the KDE project.
2020-08-02 22:22:19 +00:00
SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org>
2020-08-02 22:22:19 +00:00
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "rules.h"
#include <kconfig.h>
#include <KXMessages>
#include <QRegExp>
#include <QTemporaryFile>
#include <QFile>
#include <QFileInfo>
#include <QDebug>
#include <QDir>
#ifndef KCMRULES
#include "x11client.h"
Improved resolving whether a window is on local machine Most windows use the hostname in WM_CLIENT_MACHINE, but there are windows using the FQDN (for example libreoffice). So instead of "foo" it is "foo.local.net" or similar. The logic so far has been unable to properly determine whether windows with FQDN are on the local system. In order to solve this problem the handling is split out into an own class which stores the information of hostname and whether it is a local machine. This is to not query multiple times. To determine whether the Client is on the local system getaddrinfo is used for the own hostname and the FQDN provided in WM_CLIENT_MACHINE. If one of the queried names matches, we know that it is on the local machine. The old logic to compare the hostname is still used and getaddrinfo is only a fallback in case hostname does not match. The problem with getaddrinfo is, that it accesses the network and by that could block. To circumvent this problem the calls are moved into threads by using QtConcurrent::run. Obviously this brings disadvantages. When trying to resolve whether a Client is on the local machine and a FQDN is used, the information is initially wrong. The new ClientMachine class emits a signal when the information that the system is local becomes available, but for some things this is just too late: * window rules are already gathered * Session Management has already taken place In both cases this is an acceptable loss. For window rules it just needs a proper matching of the machine in case of localhost (remote hosts are not affected). And the case of session management is very academic as it is unlikely that a restoring session contains remote windows. BUG: 308391 FIXED-IN: 4.11 REVIEW: 108235
2013-01-07 07:07:27 +00:00
#include "client_machine.h"
#include "screens.h"
#include "workspace.h"
#endif
#include "rulesettings.h"
#include "rulebooksettings.h"
namespace KWin
{
Rules::Rules()
2011-01-30 14:34:42 +00:00
: temporary_state(0)
, wmclassmatch(UnimportantMatch)
, wmclasscomplete(UnimportantMatch)
, windowrolematch(UnimportantMatch)
, titlematch(UnimportantMatch)
, clientmachinematch(UnimportantMatch)
, types(NET::AllTypesMask)
, placementrule(UnusedForceRule)
, positionrule(UnusedSetRule)
, sizerule(UnusedSetRule)
, minsizerule(UnusedForceRule)
, maxsizerule(UnusedForceRule)
, opacityactiverule(UnusedForceRule)
, opacityinactiverule(UnusedForceRule)
, ignoregeometryrule(UnusedSetRule)
2011-01-30 14:34:42 +00:00
, desktoprule(UnusedSetRule)
2012-08-24 16:48:50 +00:00
, screenrule(UnusedSetRule)
, activityrule(UnusedSetRule)
2011-01-30 14:34:42 +00:00
, typerule(UnusedForceRule)
, maximizevertrule(UnusedSetRule)
, maximizehorizrule(UnusedSetRule)
, minimizerule(UnusedSetRule)
, shaderule(UnusedSetRule)
, skiptaskbarrule(UnusedSetRule)
, skippagerrule(UnusedSetRule)
, skipswitcherrule(UnusedSetRule)
, aboverule(UnusedSetRule)
, belowrule(UnusedSetRule)
, fullscreenrule(UnusedSetRule)
, noborderrule(UnusedSetRule)
, decocolorrule(UnusedForceRule)
, blockcompositingrule(UnusedForceRule)
2011-01-30 14:34:42 +00:00
, fsplevelrule(UnusedForceRule)
, fpplevelrule(UnusedForceRule)
2011-01-30 14:34:42 +00:00
, acceptfocusrule(UnusedForceRule)
, closeablerule(UnusedForceRule)
, autogrouprule(UnusedForceRule)
, autogroupfgrule(UnusedForceRule)
, autogroupidrule(UnusedForceRule)
, strictgeometryrule(UnusedForceRule)
, shortcutrule(UnusedSetRule)
, disableglobalshortcutsrule(UnusedForceRule)
, desktopfilerule(UnusedSetRule)
2011-01-30 14:34:42 +00:00
{
}
2011-01-30 14:34:42 +00:00
Rules::Rules(const QString& str, bool temporary)
: temporary_state(temporary ? 2 : 0)
{
QTemporaryFile file;
2011-01-30 14:34:42 +00:00
if (file.open()) {
QByteArray s = str.toUtf8();
2011-01-30 14:34:42 +00:00
file.write(s.data(), s.length());
}
file.flush();
auto cfg = KSharedConfig::openConfig(file.fileName(), KConfig::SimpleConfig);
RuleSettings settings(cfg, QString());
readFromSettings(&settings);
2011-01-30 14:34:42 +00:00
if (description.isEmpty())
description = QStringLiteral("temporary");
2011-01-30 14:34:42 +00:00
}
#define READ_MATCH_STRING(var, func) \
var = settings->var() func; \
var##match = static_cast<StringMatch>(settings->var##match())
#define READ_SET_RULE(var) \
var = settings->var(); \
var##rule = static_cast<SetRule>(settings->var##rule())
#define READ_FORCE_RULE(var, func) \
var = func(settings->var()); \
var##rule = convertForceRule(settings->var##rule())
Rules::Rules(const RuleSettings *settings)
2011-01-30 14:34:42 +00:00
: temporary_state(0)
{
readFromSettings(settings);
2011-01-30 14:34:42 +00:00
}
void Rules::readFromSettings(const RuleSettings *settings)
2011-01-30 14:34:42 +00:00
{
description = settings->description();
if (description.isEmpty()) {
description = settings->descriptionLegacy();
}
2011-01-30 14:34:42 +00:00
READ_MATCH_STRING(wmclass, .toLower().toLatin1());
wmclasscomplete = settings->wmclasscomplete();
READ_MATCH_STRING(windowrole, .toLower().toLatin1());
2011-01-30 14:34:42 +00:00
READ_MATCH_STRING(title,);
READ_MATCH_STRING(clientmachine, .toLower().toLatin1());
types = NET::WindowTypeMask(settings->types());
READ_FORCE_RULE(placement,);
READ_SET_RULE(position);
READ_SET_RULE(size);
if (size.isEmpty() && sizerule != static_cast<SetRule>(Remember))
sizerule = UnusedSetRule;
READ_FORCE_RULE(minsize,);
2011-01-30 14:34:42 +00:00
if (!minsize.isValid())
minsize = QSize(1, 1);
READ_FORCE_RULE(maxsize,);
2011-01-30 14:34:42 +00:00
if (maxsize.isEmpty())
maxsize = QSize(32767, 32767);
READ_FORCE_RULE(opacityactive,);
READ_FORCE_RULE(opacityinactive,);
READ_SET_RULE(ignoregeometry);
READ_SET_RULE(desktop);
READ_SET_RULE(screen);
READ_SET_RULE(activity);
READ_FORCE_RULE(type, static_cast<NET::WindowType>);
if (type == NET::Unknown)
typerule = UnusedForceRule;
READ_SET_RULE(maximizevert);
READ_SET_RULE(maximizehoriz);
READ_SET_RULE(minimize);
READ_SET_RULE(shade);
READ_SET_RULE(skiptaskbar);
READ_SET_RULE(skippager);
READ_SET_RULE(skipswitcher);
READ_SET_RULE(above);
READ_SET_RULE(below);
READ_SET_RULE(fullscreen);
READ_SET_RULE(noborder);
READ_FORCE_RULE(decocolor, getDecoColor);
if (decocolor.isEmpty())
decocolorrule = UnusedForceRule;
READ_FORCE_RULE(blockcompositing,);
READ_FORCE_RULE(fsplevel,);
READ_FORCE_RULE(fpplevel,);
READ_FORCE_RULE(acceptfocus,);
READ_FORCE_RULE(closeable,);
READ_FORCE_RULE(autogroup,);
READ_FORCE_RULE(autogroupfg,);
READ_FORCE_RULE(autogroupid,);
READ_FORCE_RULE(strictgeometry,);
READ_SET_RULE(shortcut);
READ_FORCE_RULE(disableglobalshortcuts,);
READ_SET_RULE(desktopfile);
2011-01-30 14:34:42 +00:00
}
#undef READ_MATCH_STRING
#undef READ_SET_RULE
#undef READ_FORCE_RULE
#undef READ_FORCE_RULE2
#define WRITE_MATCH_STRING(var, capital, force) \
settings->set##capital##match(var##match); \
if (!var.isEmpty() || force) \
2011-01-30 14:34:42 +00:00
{ \
settings->set##capital(var); \
2011-01-30 14:34:42 +00:00
}
#define WRITE_SET_RULE(var, capital, func) \
settings->set##capital##rule(var##rule); \
if (var##rule != UnusedSetRule) \
2011-01-30 14:34:42 +00:00
{ \
settings->set##capital(func(var)); \
2011-01-30 14:34:42 +00:00
}
#define WRITE_FORCE_RULE(var, capital, func) \
settings->set##capital##rule(var##rule); \
2011-01-30 14:34:42 +00:00
if ( var##rule != UnusedForceRule ) \
{ \
settings->set##capital(func(var)); \
2011-01-30 14:34:42 +00:00
}
void Rules::write(RuleSettings *settings) const
2011-01-30 14:34:42 +00:00
{
settings->setDescription(description);
// always write wmclass
WRITE_MATCH_STRING(wmclass, Wmclass, true);
settings->setWmclasscomplete(wmclasscomplete);
WRITE_MATCH_STRING(windowrole, Windowrole, false);
WRITE_MATCH_STRING(title, Title, false);
WRITE_MATCH_STRING(clientmachine, Clientmachine, false);
settings->setTypes(types);
WRITE_FORCE_RULE(placement, Placement,);
WRITE_SET_RULE(position, Position,);
WRITE_SET_RULE(size, Size,);
WRITE_FORCE_RULE(minsize, Minsize,);
WRITE_FORCE_RULE(maxsize, Maxsize,);
WRITE_FORCE_RULE(opacityactive, Opacityactive,);
WRITE_FORCE_RULE(opacityinactive, Opacityinactive,);
WRITE_SET_RULE(ignoregeometry, Ignoregeometry,);
WRITE_SET_RULE(desktop, Desktop,);
WRITE_SET_RULE(screen, Screen,);
WRITE_SET_RULE(activity, Activity,);
WRITE_FORCE_RULE(type, Type,);
WRITE_SET_RULE(maximizevert, Maximizevert,);
WRITE_SET_RULE(maximizehoriz, Maximizehoriz,);
WRITE_SET_RULE(minimize, Minimize,);
WRITE_SET_RULE(shade, Shade,);
WRITE_SET_RULE(skiptaskbar, Skiptaskbar,);
WRITE_SET_RULE(skippager, Skippager,);
WRITE_SET_RULE(skipswitcher, Skipswitcher,);
WRITE_SET_RULE(above, Above,);
WRITE_SET_RULE(below, Below,);
WRITE_SET_RULE(fullscreen, Fullscreen,);
WRITE_SET_RULE(noborder, Noborder,);
auto colorToString = [](const QString &value) -> QString {
if (value.endsWith(QLatin1String(".colors"))) {
return QFileInfo(value).baseName();
} else {
return value;
}
};
WRITE_FORCE_RULE(decocolor, Decocolor, colorToString);
WRITE_FORCE_RULE(blockcompositing, Blockcompositing,);
WRITE_FORCE_RULE(fsplevel, Fsplevel,);
WRITE_FORCE_RULE(fpplevel, Fpplevel,);
WRITE_FORCE_RULE(acceptfocus, Acceptfocus,);
WRITE_FORCE_RULE(closeable, Closeable,);
WRITE_FORCE_RULE(autogroup, Autogroup,);
WRITE_FORCE_RULE(autogroupfg, Autogroupfg,);
WRITE_FORCE_RULE(autogroupid, Autogroupid,);
WRITE_FORCE_RULE(strictgeometry, Strictgeometry,);
WRITE_SET_RULE(shortcut, Shortcut,);
WRITE_FORCE_RULE(disableglobalshortcuts, Disableglobalshortcuts,);
WRITE_SET_RULE(desktopfile, Desktopfile,);
2011-01-30 14:34:42 +00:00
}
#undef WRITE_MATCH_STRING
#undef WRITE_SET_RULE
#undef WRITE_FORCE_RULE
// returns true if it doesn't affect anything
bool Rules::isEmpty() const
2011-01-30 14:34:42 +00:00
{
return(placementrule == UnusedForceRule
&& positionrule == UnusedSetRule
&& sizerule == UnusedSetRule
&& minsizerule == UnusedForceRule
&& maxsizerule == UnusedForceRule
&& opacityactiverule == UnusedForceRule
&& opacityinactiverule == UnusedForceRule
&& ignoregeometryrule == UnusedSetRule
2011-01-30 14:34:42 +00:00
&& desktoprule == UnusedSetRule
2012-08-24 16:48:50 +00:00
&& screenrule == UnusedSetRule
&& activityrule == UnusedSetRule
2011-01-30 14:34:42 +00:00
&& typerule == UnusedForceRule
&& maximizevertrule == UnusedSetRule
&& maximizehorizrule == UnusedSetRule
&& minimizerule == UnusedSetRule
&& shaderule == UnusedSetRule
&& skiptaskbarrule == UnusedSetRule
&& skippagerrule == UnusedSetRule
&& skipswitcherrule == UnusedSetRule
&& aboverule == UnusedSetRule
&& belowrule == UnusedSetRule
&& fullscreenrule == UnusedSetRule
&& noborderrule == UnusedSetRule
&& decocolorrule == UnusedForceRule
&& blockcompositingrule == UnusedForceRule
2011-01-30 14:34:42 +00:00
&& fsplevelrule == UnusedForceRule
&& fpplevelrule == UnusedForceRule
2011-01-30 14:34:42 +00:00
&& acceptfocusrule == UnusedForceRule
&& closeablerule == UnusedForceRule
&& autogrouprule == UnusedForceRule
&& autogroupfgrule == UnusedForceRule
&& autogroupidrule == UnusedForceRule
&& strictgeometryrule == UnusedForceRule
&& shortcutrule == UnusedSetRule
&& disableglobalshortcutsrule == UnusedForceRule
&& desktopfilerule == UnusedSetRule);
2011-01-30 14:34:42 +00:00
}
Rules::ForceRule Rules::convertForceRule(int v)
2011-01-30 14:34:42 +00:00
{
if (v == DontAffect || v == Force || v == ForceTemporarily)
return static_cast<ForceRule>(v);
return UnusedForceRule;
2011-01-30 14:34:42 +00:00
}
QString Rules::getDecoColor(const QString &themeName)
{
if (themeName.isEmpty()) {
return QString();
}
// find the actual scheme file
return QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QLatin1String("color-schemes/") + themeName + QLatin1String(".colors"));
}
2011-01-30 14:34:42 +00:00
bool Rules::matchType(NET::WindowType match_type) const
{
if (types != NET::AllTypesMask) {
if (match_type == NET::Unknown)
match_type = NET::Normal; // NET::Unknown->NET::Normal is only here for matching
2011-01-30 14:34:42 +00:00
if (!NET::typeMatchesMask(match_type, types))
return false;
}
2011-01-30 14:34:42 +00:00
return true;
}
2011-01-30 14:34:42 +00:00
bool Rules::matchWMClass(const QByteArray& match_class, const QByteArray& match_name) const
{
if (wmclassmatch != UnimportantMatch) {
// TODO optimize?
QByteArray cwmclass = wmclasscomplete
2011-01-30 14:34:42 +00:00
? match_name + ' ' + match_class : match_class;
if (wmclassmatch == RegExpMatch && QRegExp(QString::fromUtf8(wmclass)).indexIn(QString::fromUtf8(cwmclass)) == -1)
return false;
2011-01-30 14:34:42 +00:00
if (wmclassmatch == ExactMatch && wmclass != cwmclass)
return false;
2011-01-30 14:34:42 +00:00
if (wmclassmatch == SubstringMatch && !cwmclass.contains(wmclass))
return false;
}
2011-01-30 14:34:42 +00:00
return true;
}
2011-01-30 14:34:42 +00:00
bool Rules::matchRole(const QByteArray& match_role) const
{
if (windowrolematch != UnimportantMatch) {
if (windowrolematch == RegExpMatch && QRegExp(QString::fromUtf8(windowrole)).indexIn(QString::fromUtf8(match_role)) == -1)
return false;
2011-01-30 14:34:42 +00:00
if (windowrolematch == ExactMatch && windowrole != match_role)
return false;
2011-01-30 14:34:42 +00:00
if (windowrolematch == SubstringMatch && !match_role.contains(windowrole))
return false;
}
2011-01-30 14:34:42 +00:00
return true;
}
2011-01-30 14:34:42 +00:00
bool Rules::matchTitle(const QString& match_title) const
{
if (titlematch != UnimportantMatch) {
if (titlematch == RegExpMatch && QRegExp(title).indexIn(match_title) == -1)
return false;
2011-01-30 14:34:42 +00:00
if (titlematch == ExactMatch && title != match_title)
return false;
2011-01-30 14:34:42 +00:00
if (titlematch == SubstringMatch && !match_title.contains(title))
return false;
}
2011-01-30 14:34:42 +00:00
return true;
}
Improved resolving whether a window is on local machine Most windows use the hostname in WM_CLIENT_MACHINE, but there are windows using the FQDN (for example libreoffice). So instead of "foo" it is "foo.local.net" or similar. The logic so far has been unable to properly determine whether windows with FQDN are on the local system. In order to solve this problem the handling is split out into an own class which stores the information of hostname and whether it is a local machine. This is to not query multiple times. To determine whether the Client is on the local system getaddrinfo is used for the own hostname and the FQDN provided in WM_CLIENT_MACHINE. If one of the queried names matches, we know that it is on the local machine. The old logic to compare the hostname is still used and getaddrinfo is only a fallback in case hostname does not match. The problem with getaddrinfo is, that it accesses the network and by that could block. To circumvent this problem the calls are moved into threads by using QtConcurrent::run. Obviously this brings disadvantages. When trying to resolve whether a Client is on the local machine and a FQDN is used, the information is initially wrong. The new ClientMachine class emits a signal when the information that the system is local becomes available, but for some things this is just too late: * window rules are already gathered * Session Management has already taken place In both cases this is an acceptable loss. For window rules it just needs a proper matching of the machine in case of localhost (remote hosts are not affected). And the case of session management is very academic as it is unlikely that a restoring session contains remote windows. BUG: 308391 FIXED-IN: 4.11 REVIEW: 108235
2013-01-07 07:07:27 +00:00
bool Rules::matchClientMachine(const QByteArray& match_machine, bool local) const
2011-01-30 14:34:42 +00:00
{
if (clientmachinematch != UnimportantMatch) {
// if it's localhost, check also "localhost" before checking hostname
Improved resolving whether a window is on local machine Most windows use the hostname in WM_CLIENT_MACHINE, but there are windows using the FQDN (for example libreoffice). So instead of "foo" it is "foo.local.net" or similar. The logic so far has been unable to properly determine whether windows with FQDN are on the local system. In order to solve this problem the handling is split out into an own class which stores the information of hostname and whether it is a local machine. This is to not query multiple times. To determine whether the Client is on the local system getaddrinfo is used for the own hostname and the FQDN provided in WM_CLIENT_MACHINE. If one of the queried names matches, we know that it is on the local machine. The old logic to compare the hostname is still used and getaddrinfo is only a fallback in case hostname does not match. The problem with getaddrinfo is, that it accesses the network and by that could block. To circumvent this problem the calls are moved into threads by using QtConcurrent::run. Obviously this brings disadvantages. When trying to resolve whether a Client is on the local machine and a FQDN is used, the information is initially wrong. The new ClientMachine class emits a signal when the information that the system is local becomes available, but for some things this is just too late: * window rules are already gathered * Session Management has already taken place In both cases this is an acceptable loss. For window rules it just needs a proper matching of the machine in case of localhost (remote hosts are not affected). And the case of session management is very academic as it is unlikely that a restoring session contains remote windows. BUG: 308391 FIXED-IN: 4.11 REVIEW: 108235
2013-01-07 07:07:27 +00:00
if (match_machine != "localhost" && local
&& matchClientMachine("localhost", true))
return true;
2011-01-30 14:34:42 +00:00
if (clientmachinematch == RegExpMatch
&& QRegExp(QString::fromUtf8(clientmachine)).indexIn(QString::fromUtf8(match_machine)) == -1)
return false;
2011-01-30 14:34:42 +00:00
if (clientmachinematch == ExactMatch
&& clientmachine != match_machine)
return false;
2011-01-30 14:34:42 +00:00
if (clientmachinematch == SubstringMatch
&& !match_machine.contains(clientmachine))
return false;
}
2011-01-30 14:34:42 +00:00
return true;
}
#ifndef KCMRULES
bool Rules::match(const AbstractClient* c) const
2011-01-30 14:34:42 +00:00
{
if (!matchType(c->windowType(true)))
return false;
2011-01-30 14:34:42 +00:00
if (!matchWMClass(c->resourceClass(), c->resourceName()))
return false;
if (!matchRole(c->windowRole().toLower()))
return false;
Improved resolving whether a window is on local machine Most windows use the hostname in WM_CLIENT_MACHINE, but there are windows using the FQDN (for example libreoffice). So instead of "foo" it is "foo.local.net" or similar. The logic so far has been unable to properly determine whether windows with FQDN are on the local system. In order to solve this problem the handling is split out into an own class which stores the information of hostname and whether it is a local machine. This is to not query multiple times. To determine whether the Client is on the local system getaddrinfo is used for the own hostname and the FQDN provided in WM_CLIENT_MACHINE. If one of the queried names matches, we know that it is on the local machine. The old logic to compare the hostname is still used and getaddrinfo is only a fallback in case hostname does not match. The problem with getaddrinfo is, that it accesses the network and by that could block. To circumvent this problem the calls are moved into threads by using QtConcurrent::run. Obviously this brings disadvantages. When trying to resolve whether a Client is on the local machine and a FQDN is used, the information is initially wrong. The new ClientMachine class emits a signal when the information that the system is local becomes available, but for some things this is just too late: * window rules are already gathered * Session Management has already taken place In both cases this is an acceptable loss. For window rules it just needs a proper matching of the machine in case of localhost (remote hosts are not affected). And the case of session management is very academic as it is unlikely that a restoring session contains remote windows. BUG: 308391 FIXED-IN: 4.11 REVIEW: 108235
2013-01-07 07:07:27 +00:00
if (!matchClientMachine(c->clientMachine()->hostName(), c->clientMachine()->isLocal()))
return false;
if (titlematch != UnimportantMatch) // track title changes to rematch rules
QObject::connect(c, &AbstractClient::captionChanged, c, &AbstractClient::evaluateWindowRules,
// QueuedConnection, because title may change before
// the client is ready (could segfault!)
static_cast<Qt::ConnectionType>(Qt::QueuedConnection|Qt::UniqueConnection));
if (!matchTitle(c->captionNormal()))
return false;
return true;
2011-01-30 14:34:42 +00:00
}
#define NOW_REMEMBER(_T_, _V_) ((selection & _T_) && (_V_##rule == (SetRule)Remember))
bool Rules::update(AbstractClient* c, int selection)
2011-01-30 14:34:42 +00:00
{
// TODO check this setting is for this client ?
bool updated = false;
if NOW_REMEMBER(Position, position) {
2011-01-30 14:34:42 +00:00
if (!c->isFullScreen()) {
QPoint new_pos = position;
// don't use the position in the direction which is maximized
2011-01-30 14:34:42 +00:00
if ((c->maximizeMode() & MaximizeHorizontal) == 0)
new_pos.setX(c->pos().x());
if ((c->maximizeMode() & MaximizeVertical) == 0)
new_pos.setY(c->pos().y());
updated = updated || position != new_pos;
position = new_pos;
}
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(Size, size) {
2011-01-30 14:34:42 +00:00
if (!c->isFullScreen()) {
QSize new_size = size;
// don't use the position in the direction which is maximized
2011-01-30 14:34:42 +00:00
if ((c->maximizeMode() & MaximizeHorizontal) == 0)
new_size.setWidth(c->size().width());
if ((c->maximizeMode() & MaximizeVertical) == 0)
new_size.setHeight(c->size().height());
updated = updated || size != new_size;
size = new_size;
}
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(Desktop, desktop) {
updated = updated || desktop != c->desktop();
desktop = c->desktop();
2011-01-30 14:34:42 +00:00
}
2012-08-24 16:48:50 +00:00
if NOW_REMEMBER(Screen, screen) {
updated = updated || screen != c->screen();
screen = c->screen();
}
if NOW_REMEMBER(Activity, activity) {
updated = updated || activity != c->activities();
activity = c->activities();
}
if NOW_REMEMBER(MaximizeVert, maximizevert) {
2011-01-30 14:34:42 +00:00
updated = updated || maximizevert != bool(c->maximizeMode() & MaximizeVertical);
maximizevert = c->maximizeMode() & MaximizeVertical;
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(MaximizeHoriz, maximizehoriz) {
2011-01-30 14:34:42 +00:00
updated = updated || maximizehoriz != bool(c->maximizeMode() & MaximizeHorizontal);
maximizehoriz = c->maximizeMode() & MaximizeHorizontal;
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(Minimize, minimize) {
updated = updated || minimize != c->isMinimized();
minimize = c->isMinimized();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(Shade, shade) {
2011-01-30 14:34:42 +00:00
updated = updated || (shade != (c->shadeMode() != ShadeNone));
shade = c->shadeMode() != ShadeNone;
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(SkipTaskbar, skiptaskbar) {
updated = updated || skiptaskbar != c->skipTaskbar();
skiptaskbar = c->skipTaskbar();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(SkipPager, skippager) {
updated = updated || skippager != c->skipPager();
skippager = c->skipPager();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(SkipSwitcher, skipswitcher) {
updated = updated || skipswitcher != c->skipSwitcher();
skipswitcher = c->skipSwitcher();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(Above, above) {
updated = updated || above != c->keepAbove();
above = c->keepAbove();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(Below, below) {
updated = updated || below != c->keepBelow();
below = c->keepBelow();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(Fullscreen, fullscreen) {
updated = updated || fullscreen != c->isFullScreen();
fullscreen = c->isFullScreen();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(NoBorder, noborder) {
updated = updated || noborder != c->noBorder();
noborder = c->noBorder();
2011-01-30 14:34:42 +00:00
}
if NOW_REMEMBER(DesktopFile, desktopfile) {
updated = updated || desktopfile != c->desktopFileName();
desktopfile = c->desktopFileName();
}
2011-01-30 14:34:42 +00:00
return updated;
}
#undef NOW_REMEMBER
#define APPLY_RULE( var, name, type ) \
2011-01-30 14:34:42 +00:00
bool Rules::apply##name( type& arg, bool init ) const \
{ \
2011-01-30 14:34:42 +00:00
if ( checkSetRule( var##rule, init )) \
arg = this->var; \
return checkSetStop( var##rule ); \
}
#define APPLY_FORCE_RULE( var, name, type ) \
2011-01-30 14:34:42 +00:00
bool Rules::apply##name( type& arg ) const \
{ \
2011-01-30 14:34:42 +00:00
if ( checkForceRule( var##rule )) \
arg = this->var; \
return checkForceStop( var##rule ); \
}
2011-01-30 14:34:42 +00:00
APPLY_FORCE_RULE(placement, Placement, Placement::Policy)
2011-01-30 14:34:42 +00:00
bool Rules::applyGeometry(QRect& rect, bool init) const
{
QPoint p = rect.topLeft();
QSize s = rect.size();
bool ret = false; // no short-circuiting
2011-01-30 14:34:42 +00:00
if (applyPosition(p, init)) {
rect.moveTopLeft(p);
ret = true;
2011-01-30 14:34:42 +00:00
}
if (applySize(s, init)) {
rect.setSize(s);
ret = true;
}
2011-01-30 14:34:42 +00:00
return ret;
}
2011-01-30 14:34:42 +00:00
bool Rules::applyPosition(QPoint& pos, bool init) const
{
if (this->position != invalidPoint && checkSetRule(positionrule, init))
pos = this->position;
2011-01-30 14:34:42 +00:00
return checkSetStop(positionrule);
}
2011-01-30 14:34:42 +00:00
bool Rules::applySize(QSize& s, bool init) const
{
if (this->size.isValid() && checkSetRule(sizerule, init))
s = this->size;
2011-01-30 14:34:42 +00:00
return checkSetStop(sizerule);
}
2011-01-30 14:34:42 +00:00
APPLY_FORCE_RULE(minsize, MinSize, QSize)
APPLY_FORCE_RULE(maxsize, MaxSize, QSize)
APPLY_FORCE_RULE(opacityactive, OpacityActive, int)
APPLY_FORCE_RULE(opacityinactive, OpacityInactive, int)
APPLY_RULE(ignoregeometry, IgnoreGeometry, bool)
2011-01-30 14:34:42 +00:00
APPLY_RULE(desktop, Desktop, int)
2012-08-24 16:48:50 +00:00
APPLY_RULE(screen, Screen, int)
APPLY_RULE(activity, Activity, QStringList)
2011-01-30 14:34:42 +00:00
APPLY_FORCE_RULE(type, Type, NET::WindowType)
2011-01-30 14:34:42 +00:00
bool Rules::applyMaximizeHoriz(MaximizeMode& mode, bool init) const
{
if (checkSetRule(maximizehorizrule, init))
mode = static_cast< MaximizeMode >((maximizehoriz ? MaximizeHorizontal : 0) | (mode & MaximizeVertical));
return checkSetStop(maximizehorizrule);
}
2011-01-30 14:34:42 +00:00
bool Rules::applyMaximizeVert(MaximizeMode& mode, bool init) const
{
if (checkSetRule(maximizevertrule, init))
mode = static_cast< MaximizeMode >((maximizevert ? MaximizeVertical : 0) | (mode & MaximizeHorizontal));
return checkSetStop(maximizevertrule);
}
2011-01-30 14:34:42 +00:00
APPLY_RULE(minimize, Minimize, bool)
2011-01-30 14:34:42 +00:00
bool Rules::applyShade(ShadeMode& sh, bool init) const
{
if (checkSetRule(shaderule, init)) {
if (!this->shade)
sh = ShadeNone;
2011-01-30 14:34:42 +00:00
if (this->shade && sh == ShadeNone)
sh = ShadeNormal;
}
2011-01-30 14:34:42 +00:00
return checkSetStop(shaderule);
}
APPLY_RULE(skiptaskbar, SkipTaskbar, bool)
APPLY_RULE(skippager, SkipPager, bool)
APPLY_RULE(skipswitcher, SkipSwitcher, bool)
APPLY_RULE(above, KeepAbove, bool)
APPLY_RULE(below, KeepBelow, bool)
APPLY_RULE(fullscreen, FullScreen, bool)
APPLY_RULE(noborder, NoBorder, bool)
APPLY_FORCE_RULE(decocolor, DecoColor, QString)
APPLY_FORCE_RULE(blockcompositing, BlockCompositing, bool)
2011-01-30 14:34:42 +00:00
APPLY_FORCE_RULE(fsplevel, FSP, int)
APPLY_FORCE_RULE(fpplevel, FPP, int)
2011-01-30 14:34:42 +00:00
APPLY_FORCE_RULE(acceptfocus, AcceptFocus, bool)
APPLY_FORCE_RULE(closeable, Closeable, bool)
APPLY_FORCE_RULE(autogroup, Autogrouping, bool)
APPLY_FORCE_RULE(autogroupfg, AutogroupInForeground, bool)
APPLY_FORCE_RULE(autogroupid, AutogroupById, QString)
APPLY_FORCE_RULE(strictgeometry, StrictGeometry, bool)
APPLY_RULE(shortcut, Shortcut, QString)
APPLY_FORCE_RULE(disableglobalshortcuts, DisableGlobalShortcuts, bool)
APPLY_RULE(desktopfile, DesktopFile, QString)
#undef APPLY_RULE
#undef APPLY_FORCE_RULE
bool Rules::isTemporary() const
2011-01-30 14:34:42 +00:00
{
return temporary_state > 0;
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
bool Rules::discardTemporary(bool force)
{
if (temporary_state == 0) // not temporary
return false;
2011-01-30 14:34:42 +00:00
if (force || --temporary_state == 0) { // too old
delete this;
return true;
}
2011-01-30 14:34:42 +00:00
return false;
}
#define DISCARD_USED_SET_RULE( var ) \
do { \
if ( var##rule == ( SetRule ) ApplyNow || ( withdrawn && var##rule == ( SetRule ) ForceTemporarily )) { \
2011-01-30 14:34:42 +00:00
var##rule = UnusedSetRule; \
changed = true; \
} \
2011-01-30 14:34:42 +00:00
} while ( false )
#define DISCARD_USED_FORCE_RULE( var ) \
do { \
if ( withdrawn && var##rule == ( ForceRule ) ForceTemporarily ) { \
2011-01-30 14:34:42 +00:00
var##rule = UnusedForceRule; \
changed = true; \
} \
2011-01-30 14:34:42 +00:00
} while ( false )
bool Rules::discardUsed(bool withdrawn)
2011-01-30 14:34:42 +00:00
{
bool changed = false;
2011-01-30 14:34:42 +00:00
DISCARD_USED_FORCE_RULE(placement);
DISCARD_USED_SET_RULE(position);
DISCARD_USED_SET_RULE(size);
DISCARD_USED_FORCE_RULE(minsize);
DISCARD_USED_FORCE_RULE(maxsize);
DISCARD_USED_FORCE_RULE(opacityactive);
DISCARD_USED_FORCE_RULE(opacityinactive);
DISCARD_USED_SET_RULE(ignoregeometry);
2011-01-30 14:34:42 +00:00
DISCARD_USED_SET_RULE(desktop);
2012-08-24 16:48:50 +00:00
DISCARD_USED_SET_RULE(screen);
DISCARD_USED_SET_RULE(activity);
2011-01-30 14:34:42 +00:00
DISCARD_USED_FORCE_RULE(type);
DISCARD_USED_SET_RULE(maximizevert);
DISCARD_USED_SET_RULE(maximizehoriz);
DISCARD_USED_SET_RULE(minimize);
DISCARD_USED_SET_RULE(shade);
DISCARD_USED_SET_RULE(skiptaskbar);
DISCARD_USED_SET_RULE(skippager);
DISCARD_USED_SET_RULE(skipswitcher);
DISCARD_USED_SET_RULE(above);
DISCARD_USED_SET_RULE(below);
DISCARD_USED_SET_RULE(fullscreen);
DISCARD_USED_SET_RULE(noborder);
DISCARD_USED_FORCE_RULE(decocolor);
DISCARD_USED_FORCE_RULE(blockcompositing);
2011-01-30 14:34:42 +00:00
DISCARD_USED_FORCE_RULE(fsplevel);
DISCARD_USED_FORCE_RULE(fpplevel);
2011-01-30 14:34:42 +00:00
DISCARD_USED_FORCE_RULE(acceptfocus);
DISCARD_USED_FORCE_RULE(closeable);
DISCARD_USED_FORCE_RULE(autogroup);
DISCARD_USED_FORCE_RULE(autogroupfg);
DISCARD_USED_FORCE_RULE(autogroupid);
DISCARD_USED_FORCE_RULE(strictgeometry);
DISCARD_USED_SET_RULE(shortcut);
DISCARD_USED_FORCE_RULE(disableglobalshortcuts);
DISCARD_USED_SET_RULE(desktopfile);
return changed;
2011-01-30 14:34:42 +00:00
}
#undef DISCARD_USED_SET_RULE
#undef DISCARD_USED_FORCE_RULE
#endif
2011-01-30 14:34:42 +00:00
QDebug& operator<<(QDebug& stream, const Rules* r)
{
return stream << "[" << r->description << ":" << r->wmclass << "]" ;
2011-01-30 14:34:42 +00:00
}
#ifndef KCMRULES
void WindowRules::discardTemporary()
2011-01-30 14:34:42 +00:00
{
QVector< Rules* >::Iterator it2 = rules.begin();
2011-01-30 14:34:42 +00:00
for (QVector< Rules* >::Iterator it = rules.begin();
it != rules.end();
) {
if ((*it)->discardTemporary(true))
++it;
2011-01-30 14:34:42 +00:00
else {
*it2++ = *it++;
}
}
2011-01-30 14:34:42 +00:00
rules.erase(it2, rules.end());
}
void WindowRules::update(AbstractClient* c, int selection)
2011-01-30 14:34:42 +00:00
{
bool updated = false;
2011-01-30 14:34:42 +00:00
for (QVector< Rules* >::ConstIterator it = rules.constBegin();
it != rules.constEnd();
++it)
if ((*it)->update(c, selection)) // no short-circuiting here
updated = true;
2011-01-30 14:34:42 +00:00
if (updated)
RuleBook::self()->requestDiskStorage();
2011-01-30 14:34:42 +00:00
}
#define CHECK_RULE( rule, type ) \
2011-01-30 14:34:42 +00:00
type WindowRules::check##rule( type arg, bool init ) const \
{ \
2011-01-30 14:34:42 +00:00
if ( rules.count() == 0 ) \
return arg; \
type ret = arg; \
for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); \
it != rules.constEnd(); \
++it ) \
{ \
2011-01-30 14:34:42 +00:00
if ( (*it)->apply##rule( ret, init )) \
break; \
} \
2011-01-30 14:34:42 +00:00
return ret; \
}
#define CHECK_FORCE_RULE( rule, type ) \
2011-01-30 14:34:42 +00:00
type WindowRules::check##rule( type arg ) const \
{ \
2011-01-30 14:34:42 +00:00
if ( rules.count() == 0 ) \
return arg; \
type ret = arg; \
for ( QVector< Rules* >::ConstIterator it = rules.begin(); \
it != rules.end(); \
++it ) \
{ \
2011-01-30 14:34:42 +00:00
if ( (*it)->apply##rule( ret )) \
break; \
} \
2011-01-30 14:34:42 +00:00
return ret; \
}
2011-01-30 14:34:42 +00:00
CHECK_FORCE_RULE(Placement, Placement::Policy)
2011-01-30 14:34:42 +00:00
QRect WindowRules::checkGeometry(QRect rect, bool init) const
{
return QRect(checkPosition(rect.topLeft(), init), checkSize(rect.size(), init));
}
CHECK_RULE(Position, QPoint)
CHECK_RULE(Size, QSize)
CHECK_FORCE_RULE(MinSize, QSize)
CHECK_FORCE_RULE(MaxSize, QSize)
CHECK_FORCE_RULE(OpacityActive, int)
CHECK_FORCE_RULE(OpacityInactive, int)
CHECK_RULE(IgnoreGeometry, bool)
2011-01-30 14:34:42 +00:00
CHECK_RULE(Desktop, int)
CHECK_RULE(Activity, QStringList)
2011-01-30 14:34:42 +00:00
CHECK_FORCE_RULE(Type, NET::WindowType)
CHECK_RULE(MaximizeVert, MaximizeMode)
CHECK_RULE(MaximizeHoriz, MaximizeMode)
MaximizeMode WindowRules::checkMaximize(MaximizeMode mode, bool init) const
2011-01-30 14:34:42 +00:00
{
bool vert = checkMaximizeVert(mode, init) & MaximizeVertical;
bool horiz = checkMaximizeHoriz(mode, init) & MaximizeHorizontal;
return static_cast< MaximizeMode >((vert ? MaximizeVertical : 0) | (horiz ? MaximizeHorizontal : 0));
}
2012-08-24 16:48:50 +00:00
int WindowRules::checkScreen(int screen, bool init) const
{
if ( rules.count() == 0 )
return screen;
int ret = screen;
for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it ) {
if ( (*it)->applyScreen( ret, init ))
break;
}
if (ret >= Screens::self()->count())
2012-08-24 16:48:50 +00:00
ret = screen;
return ret;
}
2011-01-30 14:34:42 +00:00
CHECK_RULE(Minimize, bool)
CHECK_RULE(Shade, ShadeMode)
CHECK_RULE(SkipTaskbar, bool)
CHECK_RULE(SkipPager, bool)
CHECK_RULE(SkipSwitcher, bool)
CHECK_RULE(KeepAbove, bool)
CHECK_RULE(KeepBelow, bool)
CHECK_RULE(FullScreen, bool)
CHECK_RULE(NoBorder, bool)
CHECK_FORCE_RULE(DecoColor, QString)
CHECK_FORCE_RULE(BlockCompositing, bool)
2011-01-30 14:34:42 +00:00
CHECK_FORCE_RULE(FSP, int)
CHECK_FORCE_RULE(FPP, int)
2011-01-30 14:34:42 +00:00
CHECK_FORCE_RULE(AcceptFocus, bool)
CHECK_FORCE_RULE(Closeable, bool)
CHECK_FORCE_RULE(Autogrouping, bool)
CHECK_FORCE_RULE(AutogroupInForeground, bool)
CHECK_FORCE_RULE(AutogroupById, QString)
CHECK_FORCE_RULE(StrictGeometry, bool)
CHECK_RULE(Shortcut, QString)
CHECK_FORCE_RULE(DisableGlobalShortcuts, bool)
CHECK_RULE(DesktopFile, QString)
#undef CHECK_RULE
#undef CHECK_FORCE_RULE
// Client
void AbstractClient::setupWindowRules(bool ignore_temporary)
2011-01-30 14:34:42 +00:00
{
disconnect(this, &AbstractClient::captionChanged, this, &AbstractClient::evaluateWindowRules);
m_rules = RuleBook::self()->find(this, ignore_temporary);
// check only after getting the rules, because there may be a rule forcing window type
2011-01-30 14:34:42 +00:00
}
// Applies Force, ForceTemporarily and ApplyNow rules
// Used e.g. after the rules have been modified using the kcm.
void AbstractClient::applyWindowRules()
2011-01-30 14:34:42 +00:00
{
// apply force rules
// Placement - does need explicit update, just like some others below
// Geometry : setGeometry() doesn't check rules
auto client_rules = rules();
QRect orig_geom = QRect(pos(), adjustedSize()); // handle shading
QRect geom = client_rules->checkGeometry(orig_geom);
2011-01-30 14:34:42 +00:00
if (geom != orig_geom)
Rework async geometry updates Window management features were written with synchronous geometry updates in mind. Currently, this poses a big problem on Wayland because geometry updates are done in asynchronous fashion there. At the moment, geometry is updated in a so called pseudo-asynchronous fashion, meaning that the frame geometry will be reset to the old value once geometry updates are unblocked. The main drawback of this approach is that it is too error prone, the data flow is hard to comprehend, etc. It is worth noting that there is already a machinery to perform async geometry which is used during interactive move/resize operations. This change extends the move/resize geometry usage beyond interactive move/resize to make asynchronous geometry updates less error prone and easier to comprehend. With the proposed solution, all geometry updates must be done on the move/resize geometry first. After that, the new geometry is passed on to the Client-specific implementation of moveResizeInternal(). To be more specific, the frameGeometry() returns the current frame geometry, it is primarily useful only to the scene. If you want to move or resize a window, you need to use moveResizeGeometry() because it corresponds to the last requested frame geometry. It is worth noting that the moveResizeGeometry() returns the desired bounding geometry. The client may commit the xdg_toplevel surface with a slightly smaller window geometry, for example to enforce a specific aspect ratio. The client is not allowed to resize beyond the size as indicated in moveResizeGeometry(). The data flow is very simple: moveResize() updates the move/resize geometry and calls the client-specific implementation of the moveResizeInternal() method. Based on whether a configure event is needed, moveResizeInternal() will update the frameGeometry() either immediately or after the client commits a new buffer. Unfortunately, both the compositor and xdg-shell clients try to update the window geometry. It means that it's possible to have conflicts between the two. With this change, the compositor's move resize geometry will be synced only if there are no pending configure events, meaning that the user doesn't try to resize the window.
2021-04-30 18:26:09 +00:00
moveResize(geom);
// MinSize, MaxSize handled by Geometry
// IgnoreGeometry
2011-01-30 14:34:42 +00:00
setDesktop(desktop());
2012-08-24 16:48:50 +00:00
workspace()->sendClientToScreen(this, screen());
setOnActivities(activities());
// Type
2011-01-30 14:34:42 +00:00
maximize(maximizeMode());
// Minimize : functions don't check, and there are two functions
if (client_rules->checkMinimize(isMinimized()))
minimize();
else
unminimize();
2011-01-30 14:34:42 +00:00
setShade(shadeMode());
setOriginalSkipTaskbar(skipTaskbar());
2011-01-30 14:34:42 +00:00
setSkipPager(skipPager());
setSkipSwitcher(skipSwitcher());
setKeepAbove(keepAbove());
setKeepBelow(keepBelow());
setFullScreen(isFullScreen(), true);
setNoBorder(noBorder());
updateColorScheme();
// FSP
// AcceptFocus :
2011-01-30 14:34:42 +00:00
if (workspace()->mostRecentlyActivatedClient() == this
&& !client_rules->checkAcceptFocus(true))
2011-01-30 14:34:42 +00:00
workspace()->activateNextClient(this);
// Closeable
QSize s = adjustedSize();
if (s != size() && s.isValid())
2011-01-30 14:34:42 +00:00
resizeWithChecks(s);
// Autogrouping : Only checked on window manage
// AutogroupInForeground : Only checked on window manage
// AutogroupById : Only checked on window manage
// StrictGeometry
2011-01-30 14:34:42 +00:00
setShortcut(rules()->checkShortcut(shortcut().toString()));
// see also X11Client::setActive()
2011-01-30 14:34:42 +00:00
if (isActive()) {
setOpacity(rules()->checkOpacityActive(qRound(opacity() * 100.0)) / 100.0);
workspace()->disableGlobalShortcutsForClient(rules()->checkDisableGlobalShortcuts(false));
} else
setOpacity(rules()->checkOpacityInactive(qRound(opacity() * 100.0)) / 100.0);
setDesktopFileName(rules()->checkDesktopFile(desktopFileName()).toUtf8());
2011-01-30 14:34:42 +00:00
}
void X11Client::updateWindowRules(Rules::Types selection)
2011-01-30 14:34:42 +00:00
{
if (!isManaged()) // not fully setup yet
return;
AbstractClient::updateWindowRules(selection);
}
void AbstractClient::updateWindowRules(Rules::Types selection)
{
if (RuleBook::self()->areUpdatesDisabled())
return;
m_rules.update(this, selection);
2011-01-30 14:34:42 +00:00
}
void AbstractClient::finishWindowRules()
2011-01-30 14:34:42 +00:00
{
updateWindowRules(Rules::All);
m_rules = WindowRules();
2011-01-30 14:34:42 +00:00
}
// Workspace
KWIN_SINGLETON_FACTORY(RuleBook)
RuleBook::RuleBook(QObject *parent)
: QObject(parent)
, m_updateTimer(new QTimer(this))
, m_updatesDisabled(false)
, m_temporaryRulesMessages()
{
initializeX11();
connect(kwinApp(), &Application::x11ConnectionChanged, this, &RuleBook::initializeX11);
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &RuleBook::cleanupX11);
connect(m_updateTimer, &QTimer::timeout, this, &RuleBook::save);
m_updateTimer->setInterval(1000);
m_updateTimer->setSingleShot(true);
}
RuleBook::~RuleBook()
{
save();
deleteAll();
}
void RuleBook::initializeX11()
{
auto c = kwinApp()->x11Connection();
if (!c) {
return;
}
m_temporaryRulesMessages.reset(new KXMessages(c, kwinApp()->x11RootWindow(), "_KDE_NET_WM_TEMPORARY_RULES", nullptr));
connect(m_temporaryRulesMessages.data(), &KXMessages::gotMessage, this, &RuleBook::temporaryRulesMessage);
}
void RuleBook::cleanupX11()
{
m_temporaryRulesMessages.reset();
}
void RuleBook::deleteAll()
{
qDeleteAll(m_rules);
m_rules.clear();
}
WindowRules RuleBook::find(const AbstractClient* c, bool ignore_temporary)
2011-01-30 14:34:42 +00:00
{
QVector< Rules* > ret;
for (QList< Rules* >::Iterator it = m_rules.begin();
it != m_rules.end();
2011-01-30 14:34:42 +00:00
) {
if (ignore_temporary && (*it)->isTemporary()) {
++it;
continue;
2011-01-30 14:34:42 +00:00
}
if ((*it)->match(c)) {
Rules* rule = *it;
qCDebug(KWIN_CORE) << "Rule found:" << rule << ":" << c;
2011-01-30 14:34:42 +00:00
if (rule->isTemporary())
it = m_rules.erase(it);
else
++it;
2011-01-30 14:34:42 +00:00
ret.append(rule);
continue;
}
2011-01-30 14:34:42 +00:00
++it;
}
2011-01-30 14:34:42 +00:00
return WindowRules(ret);
}
void RuleBook::edit(AbstractClient* c, bool whole_app)
2011-01-30 14:34:42 +00:00
{
save();
QStringList args;
args << QStringLiteral("--uuid") << c->internalId().toString();
2011-01-30 14:34:42 +00:00
if (whole_app)
args << QStringLiteral("--whole-app");
QProcess *p = new Process(this);
p->setArguments(args);
p->setProcessEnvironment(kwinApp()->processStartupEnvironment());
const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_rules_dialog")};
p->setProgram(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_RULES_DIALOG_BIN));
p->setProcessChannelMode(QProcess::MergedChannels);
connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), p, &QProcess::deleteLater);
connect(p, &QProcess::errorOccurred, this, [p](QProcess::ProcessError e) {
if (e == QProcess::FailedToStart) {
qCDebug(KWIN_CORE) << "Failed to start" << p->program();
}
});
p->start();
2011-01-30 14:34:42 +00:00
}
void RuleBook::load()
2011-01-30 14:34:42 +00:00
{
deleteAll();
if (!m_config) {
m_config = KSharedConfig::openConfig(QStringLiteral(KWIN_NAME "rulesrc"), KConfig::NoGlobals);
} else {
m_config->reparseConfiguration();
}
m_rules = RuleBookSettings(m_config).rules().toList();
2011-01-30 14:34:42 +00:00
}
void RuleBook::save()
2011-01-30 14:34:42 +00:00
{
m_updateTimer->stop();
if (!m_config) {
2019-11-26 17:48:29 +00:00
qCWarning(KWIN_CORE) << "RuleBook::save invoked without prior invocation of RuleBook::load";
return;
}
QVector<Rules *> filteredRules;
for (const auto &rule : qAsConst(m_rules)) {
if (!rule->isTemporary()) {
filteredRules.append(rule);
}
}
RuleBookSettings settings(m_config);
settings.setRules(filteredRules);
settings.save();
2011-01-30 14:34:42 +00:00
}
void RuleBook::temporaryRulesMessage(const QString& message)
2011-01-30 14:34:42 +00:00
{
bool was_temporary = false;
for (QList< Rules* >::ConstIterator it = m_rules.constBegin();
it != m_rules.constEnd();
2011-01-30 14:34:42 +00:00
++it)
if ((*it)->isTemporary())
was_temporary = true;
2011-01-30 14:34:42 +00:00
Rules* rule = new Rules(message, true);
m_rules.prepend(rule); // highest priority first
2011-01-30 14:34:42 +00:00
if (!was_temporary)
QTimer::singleShot(60000, this, &RuleBook::cleanupTemporaryRules);
2011-01-30 14:34:42 +00:00
}
void RuleBook::cleanupTemporaryRules()
2011-01-30 14:34:42 +00:00
{
bool has_temporary = false;
for (QList< Rules* >::Iterator it = m_rules.begin();
it != m_rules.end();
2011-01-30 14:34:42 +00:00
) {
if ((*it)->discardTemporary(false)) { // deletes (*it)
it = m_rules.erase(it);
} else {
2011-01-30 14:34:42 +00:00
if ((*it)->isTemporary())
has_temporary = true;
++it;
}
}
2011-01-30 14:34:42 +00:00
if (has_temporary)
QTimer::singleShot(60000, this, &RuleBook::cleanupTemporaryRules);
2011-01-30 14:34:42 +00:00
}
void RuleBook::discardUsed(AbstractClient* c, bool withdrawn)
2011-01-30 14:34:42 +00:00
{
bool updated = false;
for (QList< Rules* >::Iterator it = m_rules.begin();
it != m_rules.end();
2011-01-30 14:34:42 +00:00
) {
if (c->rules()->contains(*it)) {
if ((*it)->discardUsed(withdrawn)) {
updated = true;
}
2011-01-30 14:34:42 +00:00
if ((*it)->isEmpty()) {
c->removeRule(*it);
Rules* r = *it;
it = m_rules.erase(it);
delete r;
continue;
}
}
2011-01-30 14:34:42 +00:00
++it;
}
2011-01-30 14:34:42 +00:00
if (updated)
requestDiskStorage();
2011-01-30 14:34:42 +00:00
}
void RuleBook::requestDiskStorage()
2011-01-30 14:34:42 +00:00
{
m_updateTimer->start();
2011-01-30 14:34:42 +00:00
}
void RuleBook::setUpdatesDisabled(bool disable)
2011-01-30 14:34:42 +00:00
{
m_updatesDisabled = disable;
if (!disable) {
foreach (X11Client *c, Workspace::self()->clientList())
c->updateWindowRules(Rules::All);
}
2011-01-30 14:34:42 +00:00
}
#endif
} // namespace