[kwinrules] Launch full KCM when editing from window menu
When rules configuration is invoked from window `Alt+F3` menu, we call a custom binary `kwin_rules_dialog` which currently provides only the rule edition dialog by embedding `RulesEditor.qml` within a QQuickView. This MR changes that behavior to call the full KCM from the menu. The code to match previous rules, or compose a new one based on window properties has been ported to the KCM from the dialog, so the overall interaction is similar. It has several advantages: - uses only one entry-point to the code - adds discoverability to the full KCM (I guess many users know how to create a rule, but not where to delete it later) And a drawback: - only one instance of the KCM can be called at a time, so it will show an error when calling it from two different windows, or if the KCM is open in System Settings This drawback can be solved after adding argument passing via dBus in KCM infraestructure. BUG: 433837 CCBUG: 417923
This commit is contained in:
parent
38a15996f4
commit
a82be242ea
9 changed files with 298 additions and 341 deletions
|
@ -16,6 +16,7 @@ set(kwinrules_SRCS
|
|||
optionsmodel.cpp
|
||||
ruleitem.cpp
|
||||
rulesmodel.cpp
|
||||
rulebookmodel.cpp
|
||||
)
|
||||
|
||||
kconfig_add_kcfg_files(kwinrules_SRCS ../../rulesettings.kcfgc)
|
||||
|
@ -33,6 +34,7 @@ set(kcm_libs
|
|||
Qt::Quick
|
||||
Qt::QuickWidgets
|
||||
|
||||
KF5::KCMUtils
|
||||
KF5::I18n
|
||||
KF5::QuickAddons
|
||||
KF5::WindowSystem
|
||||
|
@ -44,11 +46,11 @@ if (KWIN_BUILD_ACTIVITIES)
|
|||
endif()
|
||||
target_link_libraries(KWinRulesObjects ${kcm_libs} ${kwin_kcm_rules_XCB_LIBS})
|
||||
|
||||
add_executable(kwin_rules_dialog main.cpp rulesdialog.cpp)
|
||||
add_executable(kwin_rules_dialog main.cpp)
|
||||
target_link_libraries(kwin_rules_dialog KWinRulesObjects)
|
||||
install(TARGETS kwin_rules_dialog DESTINATION ${KDE_INSTALL_LIBEXECDIR})
|
||||
|
||||
add_library(kcm_kwinrules MODULE kcmrules.cpp rulebookmodel.cpp)
|
||||
add_library(kcm_kwinrules MODULE kcmrules.cpp)
|
||||
target_link_libraries(kcm_kwinrules KWinRulesObjects)
|
||||
kcoreaddons_desktop_to_json(kcm_kwinrules "kcm_kwinrules.desktop" SERVICE_TYPES kcmodule.desktop)
|
||||
|
||||
|
|
|
@ -40,6 +40,12 @@ KCMKWinRules::KCMKWinRules(QObject *parent, const QVariantList &arguments)
|
|||
" KWin as your window manager. If you do use a different window manager, please refer to its documentation"
|
||||
" for how to customize window behavior.</p>"));
|
||||
|
||||
QStringList argList;
|
||||
for (const QVariant &arg : arguments) {
|
||||
argList << arg.toString();
|
||||
}
|
||||
parseArguments(argList);
|
||||
|
||||
connect(m_rulesModel, &RulesModel::descriptionChanged, this, [this]{
|
||||
if (m_editIndex.isValid()) {
|
||||
m_ruleBookModel->setDescriptionAt(m_editIndex.row(), m_rulesModel->description());
|
||||
|
@ -49,14 +55,69 @@ KCMKWinRules::KCMKWinRules(QObject *parent, const QVariantList &arguments)
|
|||
connect(m_ruleBookModel, &RulesModel::dataChanged, this, &KCMKWinRules::updateNeedsSave);
|
||||
}
|
||||
|
||||
void KCMKWinRules::parseArguments(const QStringList &args)
|
||||
{
|
||||
// When called from window menu, "uuid" and "whole-app" are set in arguments list
|
||||
bool nextArgIsUuid = false;
|
||||
QUuid uuid = QUuid();
|
||||
|
||||
// TODO: Use a better argument parser
|
||||
for (const QString &arg : args) {
|
||||
if (arg == QLatin1String("uuid")) {
|
||||
nextArgIsUuid = true;
|
||||
} else if (nextArgIsUuid) {
|
||||
uuid = QUuid(arg);
|
||||
nextArgIsUuid = false;
|
||||
} else if (arg.startsWith("uuid=")) {
|
||||
uuid = arg.mid(strlen("uuid="));
|
||||
} else if (arg == QLatin1String("whole-app")) {
|
||||
m_wholeApp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (uuid.isNull()) {
|
||||
qDebug() << "Invalid window uuid.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Window properties
|
||||
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
|
||||
QStringLiteral("/KWin"),
|
||||
QStringLiteral("org.kde.KWin"),
|
||||
QStringLiteral("getWindowInfo"));
|
||||
message.setArguments({uuid.toString()});
|
||||
QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
|
||||
|
||||
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
|
||||
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, uuid](QDBusPendingCallWatcher *self) {
|
||||
QDBusPendingReply<QVariantMap> reply = *self;
|
||||
self->deleteLater();
|
||||
if (!reply.isValid() || reply.value().isEmpty()) {
|
||||
qDebug() << "Error retrieving properties for window" << uuid;
|
||||
return;
|
||||
}
|
||||
qDebug() << "Retrieved properties for window" << uuid;
|
||||
m_winProperties = reply.value();
|
||||
|
||||
if (m_alreadyLoaded) {
|
||||
createRuleFromProperties();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void KCMKWinRules::load()
|
||||
{
|
||||
m_ruleBookModel->load();
|
||||
|
||||
m_editIndex = QModelIndex();
|
||||
emit editIndexChanged();
|
||||
|
||||
setNeedsSave(false);
|
||||
|
||||
if (!m_winProperties.isEmpty() && !m_alreadyLoaded) {
|
||||
createRuleFromProperties();
|
||||
} else {
|
||||
m_editIndex = QModelIndex();
|
||||
emit editIndexChanged();
|
||||
}
|
||||
|
||||
m_alreadyLoaded = true;
|
||||
}
|
||||
|
||||
void KCMKWinRules::save()
|
||||
|
@ -76,6 +137,26 @@ void KCMKWinRules::updateNeedsSave()
|
|||
emit needsSaveChanged();
|
||||
}
|
||||
|
||||
void KCMKWinRules::createRuleFromProperties()
|
||||
{
|
||||
if (m_winProperties.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QModelIndex matchedIndex = m_ruleBookModel->findRuleWithProperties(m_winProperties, m_wholeApp);
|
||||
if (!matchedIndex.isValid()) {
|
||||
m_ruleBookModel->insertRow(0);
|
||||
m_ruleBookModel->setRuleAt(0, ruleForProperties(m_winProperties, m_wholeApp));
|
||||
matchedIndex = m_ruleBookModel->index(0);
|
||||
updateNeedsSave();
|
||||
}
|
||||
|
||||
editRule(matchedIndex.row());
|
||||
m_rulesModel->setSuggestedProperties(m_winProperties);
|
||||
|
||||
m_winProperties.clear();
|
||||
}
|
||||
|
||||
void KCMKWinRules::saveCurrentRule()
|
||||
{
|
||||
if (m_editIndex.isValid() && needsSave()) {
|
||||
|
@ -249,6 +330,84 @@ void KCMKWinRules::importFromFile(const QUrl &path)
|
|||
updateNeedsSave();
|
||||
}
|
||||
|
||||
// Code adapted from original `findRule()` method in `kwin_rules_dialog::main.cpp`
|
||||
Rules *KCMKWinRules::ruleForProperties(const QVariantMap &windowProperties, bool wholeApp) const
|
||||
{
|
||||
const QByteArray wmclass_class = windowProperties.value("resourceClass").toByteArray().toLower();
|
||||
const QByteArray wmclass_name = windowProperties.value("resourceName").toByteArray().toLower();
|
||||
const QByteArray role = windowProperties.value("role").toByteArray().toLower();
|
||||
const NET::WindowType type = static_cast<NET::WindowType>(windowProperties.value("type").toInt());
|
||||
const QString title = windowProperties.value("caption").toString();
|
||||
const QByteArray machine = windowProperties.value("clientMachine").toByteArray();
|
||||
|
||||
Rules *rule = new Rules();
|
||||
|
||||
if (wholeApp) {
|
||||
rule->description = i18n("Application settings for %1", QString::fromLatin1(wmclass_class));
|
||||
// TODO maybe exclude some types? If yes, then also exclude them when searching.
|
||||
rule->types = NET::AllTypesMask;
|
||||
rule->titlematch = Rules::UnimportantMatch;
|
||||
rule->clientmachine = machine; // set, but make unimportant
|
||||
rule->clientmachinematch = Rules::UnimportantMatch;
|
||||
rule->windowrolematch = Rules::UnimportantMatch;
|
||||
if (wmclass_name == wmclass_class) {
|
||||
rule->wmclasscomplete = false;
|
||||
rule->wmclass = wmclass_class;
|
||||
rule->wmclassmatch = Rules::ExactMatch;
|
||||
} else {
|
||||
// WM_CLASS components differ - perhaps the app got -name argument
|
||||
rule->wmclasscomplete = true;
|
||||
rule->wmclass = wmclass_name + ' ' + wmclass_class;
|
||||
rule->wmclassmatch = Rules::ExactMatch;
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
rule->description = i18n("Window settings for %1", QString::fromLatin1(wmclass_class));
|
||||
if (type == NET::Unknown) {
|
||||
rule->types = NET::NormalMask;
|
||||
} else {
|
||||
rule->types = NET::WindowTypeMask(1 << type); // convert type to its mask
|
||||
}
|
||||
rule->title = title; // set, but make unimportant
|
||||
rule->titlematch = Rules::UnimportantMatch;
|
||||
rule->clientmachine = machine; // set, but make unimportant
|
||||
rule->clientmachinematch = Rules::UnimportantMatch;
|
||||
if (!role.isEmpty() && role != "unknown" && role != "unnamed") { // Qt sets this if not specified
|
||||
rule->windowrole = role;
|
||||
rule->windowrolematch = Rules::ExactMatch;
|
||||
if (wmclass_name == wmclass_class) {
|
||||
rule->wmclasscomplete = false;
|
||||
rule->wmclass = wmclass_class;
|
||||
rule->wmclassmatch = Rules::ExactMatch;
|
||||
} else {
|
||||
// WM_CLASS components differ - perhaps the app got -name argument
|
||||
rule->wmclasscomplete = true;
|
||||
rule->wmclass = wmclass_name + ' ' + wmclass_class;
|
||||
rule->wmclassmatch = Rules::ExactMatch;
|
||||
}
|
||||
} else { // no role set
|
||||
if (wmclass_name != wmclass_class) {
|
||||
rule->wmclasscomplete = true;
|
||||
rule->wmclass = wmclass_name + ' ' + wmclass_class;
|
||||
rule->wmclassmatch = Rules::ExactMatch;
|
||||
} else {
|
||||
// This is a window that has no role set, and both components of WM_CLASS
|
||||
// match (possibly only differing in case), which most likely means either
|
||||
// the application doesn't give a damn about distinguishing its various
|
||||
// windows, or it's an app that uses role for that, but this window
|
||||
// lacks it for some reason. Use non-complete WM_CLASS matching, also
|
||||
// include window title in the matching, and pray it causes many more positive
|
||||
// matches than negative matches.
|
||||
rule->titlematch = Rules::ExactMatch;
|
||||
rule->wmclasscomplete = false;
|
||||
rule->wmclass = wmclass_class;
|
||||
rule->wmclassmatch = Rules::ExactMatch;
|
||||
}
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
K_PLUGIN_CLASS_WITH_JSON(KCMKWinRules, "kcm_kwinrules.json");
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -51,12 +51,19 @@ private slots:
|
|||
private:
|
||||
int editIndex() const;
|
||||
void saveCurrentRule();
|
||||
void parseArguments(const QStringList &args);
|
||||
void createRuleFromProperties();
|
||||
Rules *ruleForProperties(const QVariantMap &windowProperties, bool wholeApp) const;
|
||||
|
||||
private:
|
||||
RuleBookModel *m_ruleBookModel;
|
||||
RulesModel* m_rulesModel;
|
||||
|
||||
QPersistentModelIndex m_editIndex;
|
||||
|
||||
bool m_alreadyLoaded = false;
|
||||
QVariantMap m_winProperties;
|
||||
bool m_wholeApp = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -1,191 +1,18 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QApplication>
|
||||
#include <kconfig.h>
|
||||
#include <KLocalizedString>
|
||||
#include <kwindowsystem.h>
|
||||
|
||||
#include "rulebooksettings.h"
|
||||
#include "rulesdialog.h"
|
||||
#include "../../rules.h"
|
||||
#include <QByteArray>
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusMessage>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusPendingReply>
|
||||
#include <QCommandLineParser>
|
||||
#include <QIcon>
|
||||
#include <QUuid>
|
||||
|
||||
Q_DECLARE_METATYPE(NET::WindowType)
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
static Rules *findRule(const QVector<Rules *> &rules, const QVariantMap &data, bool whole_app)
|
||||
{
|
||||
QByteArray wmclass_class = data.value("resourceClass").toByteArray().toLower();
|
||||
QByteArray wmclass_name = data.value("resourceName").toByteArray().toLower();
|
||||
QByteArray role = data.value("role").toByteArray().toLower();
|
||||
NET::WindowType type = data.value("type").value<NET::WindowType>();
|
||||
QString title = data.value("caption").toString();
|
||||
QByteArray machine = data.value("clientMachine").toByteArray();
|
||||
Rules* best_match = nullptr;
|
||||
int match_quality = 0;
|
||||
for (const auto rule : rules) {
|
||||
// try to find an exact match, i.e. not a generic rule
|
||||
int quality = 0;
|
||||
bool generic = true;
|
||||
if (rule->wmclassmatch != Rules::ExactMatch)
|
||||
continue; // too generic
|
||||
if (!rule->matchWMClass(wmclass_class, wmclass_name))
|
||||
continue;
|
||||
// from now on, it matches the app - now try to match for a specific window
|
||||
if (rule->wmclasscomplete) {
|
||||
quality += 1;
|
||||
generic = false; // this can be considered specific enough (old X apps)
|
||||
}
|
||||
if (!whole_app) {
|
||||
if (rule->windowrolematch != Rules::UnimportantMatch) {
|
||||
quality += rule->windowrolematch == Rules::ExactMatch ? 5 : 1;
|
||||
generic = false;
|
||||
}
|
||||
if (rule->titlematch != Rules::UnimportantMatch) {
|
||||
quality += rule->titlematch == Rules::ExactMatch ? 3 : 1;
|
||||
generic = false;
|
||||
}
|
||||
if (rule->types != NET::AllTypesMask) {
|
||||
int bits = 0;
|
||||
for (unsigned int bit = 1;
|
||||
bit < 1U << 31;
|
||||
bit <<= 1)
|
||||
if (rule->types & bit)
|
||||
++bits;
|
||||
if (bits == 1)
|
||||
quality += 2;
|
||||
}
|
||||
if (generic) // ignore generic rules, use only the ones that are for this window
|
||||
continue;
|
||||
} else {
|
||||
if (rule->types == NET::AllTypesMask)
|
||||
quality += 2;
|
||||
}
|
||||
if (!rule->matchType(type)
|
||||
|| !rule->matchRole(role)
|
||||
|| !rule->matchTitle(title)
|
||||
|| !rule->matchClientMachine(machine, data.value("localhost").toBool()))
|
||||
continue;
|
||||
if (quality > match_quality) {
|
||||
best_match = rule;
|
||||
match_quality = quality;
|
||||
}
|
||||
}
|
||||
if (best_match != nullptr)
|
||||
return best_match;
|
||||
Rules* ret = new Rules;
|
||||
if (whole_app) {
|
||||
ret->description = i18n("Application settings for %1", QString::fromLatin1(wmclass_class));
|
||||
// TODO maybe exclude some types? If yes, then also exclude them above
|
||||
// when searching.
|
||||
ret->types = NET::AllTypesMask;
|
||||
ret->titlematch = Rules::UnimportantMatch;
|
||||
ret->clientmachine = machine; // set, but make unimportant
|
||||
ret->clientmachinematch = Rules::UnimportantMatch;
|
||||
ret->windowrolematch = Rules::UnimportantMatch;
|
||||
if (wmclass_name == wmclass_class) {
|
||||
ret->wmclasscomplete = false;
|
||||
ret->wmclass = wmclass_class;
|
||||
ret->wmclassmatch = Rules::ExactMatch;
|
||||
} else {
|
||||
// WM_CLASS components differ - perhaps the app got -name argument
|
||||
ret->wmclasscomplete = true;
|
||||
ret->wmclass = wmclass_name + ' ' + wmclass_class;
|
||||
ret->wmclassmatch = Rules::ExactMatch;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ret->description = i18n("Window settings for %1", QString::fromLatin1(wmclass_class));
|
||||
if (type == NET::Unknown)
|
||||
ret->types = NET::NormalMask;
|
||||
else
|
||||
ret->types = NET::WindowTypeMask( 1 << type); // convert type to its mask
|
||||
ret->title = title; // set, but make unimportant
|
||||
ret->titlematch = Rules::UnimportantMatch;
|
||||
ret->clientmachine = machine; // set, but make unimportant
|
||||
ret->clientmachinematch = Rules::UnimportantMatch;
|
||||
if (!role.isEmpty()
|
||||
&& role != "unknown" && role != "unnamed") { // Qt sets this if not specified
|
||||
ret->windowrole = role;
|
||||
ret->windowrolematch = Rules::ExactMatch;
|
||||
if (wmclass_name == wmclass_class) {
|
||||
ret->wmclasscomplete = false;
|
||||
ret->wmclass = wmclass_class;
|
||||
ret->wmclassmatch = Rules::ExactMatch;
|
||||
} else {
|
||||
// WM_CLASS components differ - perhaps the app got -name argument
|
||||
ret->wmclasscomplete = true;
|
||||
ret->wmclass = wmclass_name + ' ' + wmclass_class;
|
||||
ret->wmclassmatch = Rules::ExactMatch;
|
||||
}
|
||||
} else { // no role set
|
||||
if (wmclass_name != wmclass_class) {
|
||||
ret->wmclasscomplete = true;
|
||||
ret->wmclass = wmclass_name + ' ' + wmclass_class;
|
||||
ret->wmclassmatch = Rules::ExactMatch;
|
||||
} else {
|
||||
// This is a window that has no role set, and both components of WM_CLASS
|
||||
// match (possibly only differing in case), which most likely means either
|
||||
// the application doesn't give a damn about distinguishing its various
|
||||
// windows, or it's an app that uses role for that, but this window
|
||||
// lacks it for some reason. Use non-complete WM_CLASS matching, also
|
||||
// include window title in the matching, and pray it causes many more positive
|
||||
// matches than negative matches.
|
||||
ret->titlematch = Rules::ExactMatch;
|
||||
ret->wmclasscomplete = false;
|
||||
ret->wmclass = wmclass_class;
|
||||
ret->wmclassmatch = Rules::ExactMatch;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void edit(const QVariantMap &data, bool whole_app)
|
||||
{
|
||||
RuleBookSettings settings(KConfig::NoGlobals);
|
||||
QVector<Rules *> rules = settings.rules();
|
||||
Rules *orig_rule = findRule(rules, data, whole_app);
|
||||
RulesDialog dlg;
|
||||
if (whole_app)
|
||||
dlg.setWindowTitle(i18nc("Window caption for the application wide rules dialog", "Edit Application-Specific Settings"));
|
||||
// dlg.edit() creates new Rules instance if edited
|
||||
Rules* edited_rule = dlg.edit(orig_rule, data, true);
|
||||
if (edited_rule == nullptr || edited_rule->isEmpty()) {
|
||||
rules.removeAll(orig_rule);
|
||||
delete orig_rule;
|
||||
if (orig_rule != edited_rule)
|
||||
delete edited_rule;
|
||||
} else if (edited_rule != orig_rule) {
|
||||
int pos = rules.indexOf(orig_rule);
|
||||
if (pos != -1)
|
||||
rules[ pos ] = edited_rule;
|
||||
else
|
||||
rules.prepend(edited_rule);
|
||||
delete orig_rule;
|
||||
}
|
||||
settings.setRules(rules);
|
||||
settings.save();
|
||||
// Send signal to all kwin instances
|
||||
QDBusMessage message =
|
||||
QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig");
|
||||
QDBusConnection::sessionBus().send(message);
|
||||
qApp->quit();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#include <KCMultiDialog>
|
||||
#include <KLocalizedString>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
|
@ -194,48 +21,39 @@ int main(int argc, char* argv[])
|
|||
KLocalizedString::setApplicationDomain("kcm_kwinrules");
|
||||
|
||||
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
|
||||
app.setApplicationDisplayName(i18n("KWin"));
|
||||
app.setApplicationName("kwin_rules_dialog");
|
||||
app.setApplicationVersion("1.0");
|
||||
bool whole_app = false;
|
||||
QUuid uuid;
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(i18n("KWin helper utility"));
|
||||
parser.addOption(QCommandLineOption("uuid", i18n("KWin id of the window for special window settings."), "uuid"));
|
||||
parser.addOption(QCommandLineOption("whole-app", i18n("Whether the settings should affect all windows of the application.")));
|
||||
parser.process(app);
|
||||
app.setWindowIcon(QIcon::fromTheme("preferences-system-windows-actions"));
|
||||
app.setApplicationVersion("2.0");
|
||||
|
||||
uuid = QUuid::fromString(parser.value("uuid"));
|
||||
whole_app = parser.isSet("whole-app");
|
||||
}
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(i18n("KWinRules KCM launcher"));
|
||||
parser.addOption(QCommandLineOption("uuid", i18n("KWin id of the window for special window settings."), "uuid"));
|
||||
parser.addOption(QCommandLineOption("whole-app", i18n("Whether the settings should affect all windows of the application.")));
|
||||
parser.process(app);
|
||||
|
||||
const QUuid uuid = QUuid::fromString(parser.value("uuid"));
|
||||
const bool whole_app = parser.isSet("whole-app");
|
||||
|
||||
if (uuid.isNull()) {
|
||||
printf("%s\n", qPrintable(i18n("This helper utility is not supposed to be called directly.")));
|
||||
return 1;
|
||||
}
|
||||
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
|
||||
QStringLiteral("/KWin"),
|
||||
QStringLiteral("org.kde.KWin"),
|
||||
QStringLiteral("getWindowInfo"));
|
||||
message.setArguments({uuid.toString()});
|
||||
QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
|
||||
|
||||
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, &app);
|
||||
QObject::connect(callWatcher, &QDBusPendingCallWatcher::finished, &app,
|
||||
[&whole_app] (QDBusPendingCallWatcher *self) {
|
||||
QDBusPendingReply<QVariantMap> reply = *self;
|
||||
self->deleteLater();
|
||||
if (!reply.isValid() || reply.value().isEmpty()) {
|
||||
qApp->quit();
|
||||
return;
|
||||
}
|
||||
KWin::edit(reply.value(), whole_app);
|
||||
}
|
||||
);
|
||||
app.setApplicationDisplayName((whole_app) ? i18nc("Window caption for the application wide rules dialog", "Edit Application-Specific Settings")
|
||||
: i18n("Edit Window-Specific Settings"));
|
||||
|
||||
QStringList kcm_args;
|
||||
kcm_args << QStringLiteral("uuid=%1").arg(uuid.toString());
|
||||
if (whole_app) {
|
||||
kcm_args << QStringLiteral("whole-app");
|
||||
}
|
||||
|
||||
KCMultiDialog *dialog = new KCMultiDialog;
|
||||
dialog->addModule(QStringLiteral("kcm_kwinrules"), kcm_args);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->show();
|
||||
|
||||
app.setQuitOnLastWindowClosed(true);
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
|
|
@ -16,9 +16,7 @@ import org.kde.kcms.kwinrules 1.0
|
|||
ScrollViewKCM {
|
||||
id: rulesEditor
|
||||
|
||||
property var rulesModel: kcm.rulesModel
|
||||
|
||||
title: rulesModel.description
|
||||
title: kcm.rulesModel.description
|
||||
|
||||
view: ListView {
|
||||
id: rulesView
|
||||
|
@ -73,7 +71,7 @@ ScrollViewKCM {
|
|||
visible: warningList.count > 0
|
||||
Repeater {
|
||||
id: warningList
|
||||
model: rulesModel.warningMessages
|
||||
model: kcm.rulesModel.warningMessages
|
||||
|
||||
delegate: Kirigami.InlineMessage {
|
||||
text: modelData
|
||||
|
@ -104,8 +102,8 @@ ScrollViewKCM {
|
|||
enabled: !propertySheet.sheetOpen && !errorSheet.sheetOpen
|
||||
onClicked: {
|
||||
overlayModel.onlySuggestions = true;
|
||||
rulesModel.detectWindowProperties(Math.max(delaySpin.value * 1000,
|
||||
Kirigami.Units.shortDuration));
|
||||
kcm.rulesModel.detectWindowProperties(Math.max(delaySpin.value * 1000,
|
||||
Kirigami.Units.shortDuration));
|
||||
}
|
||||
}
|
||||
QQC2.SpinBox {
|
||||
|
@ -123,7 +121,7 @@ ScrollViewKCM {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: rulesModel
|
||||
target: kcm.rulesModel
|
||||
function onShowSuggestions() {
|
||||
overlayModel.onlySuggestions = true;
|
||||
propertySheet.sheetOpen = true;
|
||||
|
@ -268,7 +266,7 @@ ScrollViewKCM {
|
|||
|
||||
KSortFilterProxyModel {
|
||||
id: enabledRulesModel
|
||||
sourceModel: rulesModel
|
||||
sourceModel: kcm.rulesModel
|
||||
filterRowCallback: (source_row, source_parent) => {
|
||||
var index = sourceModel.index(source_row, 0, source_parent);
|
||||
return sourceModel.data(index, RulesModel.EnabledRole);
|
||||
|
@ -277,7 +275,7 @@ ScrollViewKCM {
|
|||
|
||||
KSortFilterProxyModel {
|
||||
id: overlayModel
|
||||
sourceModel: rulesModel
|
||||
sourceModel: kcm.rulesModel
|
||||
|
||||
property bool onlySuggestions: false
|
||||
onOnlySuggestionsChanged: {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
@ -6,6 +7,7 @@
|
|||
|
||||
#include "rulebookmodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
@ -170,4 +172,88 @@ void RuleBookModel::save()
|
|||
m_ruleBook->save();
|
||||
}
|
||||
|
||||
// Code adapted from original `findRule()` method in `kwin_rules_dialog::main.cpp`
|
||||
QModelIndex RuleBookModel::findRuleWithProperties(const QVariantMap &info, bool wholeApp) const
|
||||
{
|
||||
const QByteArray wmclass_class = info.value("resourceClass").toByteArray().toLower();
|
||||
const QByteArray wmclass_name = info.value("resourceName").toByteArray().toLower();
|
||||
const QByteArray role = info.value("role").toByteArray().toLower();
|
||||
const NET::WindowType type = static_cast<NET::WindowType>(info.value("type").toInt());
|
||||
const QString title = info.value("caption").toString();
|
||||
const QByteArray machine = info.value("clientMachine").toByteArray();
|
||||
const bool isLocalHost = info.value("localhost").toBool();
|
||||
|
||||
int bestMatchRow = -1;
|
||||
int match_quality = 0;
|
||||
|
||||
for (int row = 0; row < m_rules.count(); row++) {
|
||||
Rules *rule = m_rules.at(row);
|
||||
|
||||
/* clang-format off */
|
||||
// If the rule doesn't match try the next one
|
||||
if (!rule->matchWMClass(wmclass_class, wmclass_name)
|
||||
|| !rule->matchType(type)
|
||||
|| !rule->matchRole(role)
|
||||
|| !rule->matchTitle(title)
|
||||
|| !rule->matchClientMachine(machine, isLocalHost)) {
|
||||
continue;
|
||||
}
|
||||
/* clang-format on */
|
||||
|
||||
if (rule->wmclassmatch != Rules::ExactMatch) {
|
||||
continue; // too generic
|
||||
}
|
||||
|
||||
// Now that the rule matches the window, check the quality of the match
|
||||
// It stablishes a quality depending on the match policy of the rule
|
||||
int quality = 0;
|
||||
bool generic = true;
|
||||
|
||||
// from now on, it matches the app - now try to match for a specific window
|
||||
if (rule->wmclasscomplete) {
|
||||
quality += 1;
|
||||
generic = false; // this can be considered specific enough (old X apps)
|
||||
}
|
||||
if (!wholeApp) {
|
||||
if (rule->windowrolematch != Rules::UnimportantMatch) {
|
||||
quality += rule->windowrolematch == Rules::ExactMatch ? 5 : 1;
|
||||
generic = false;
|
||||
}
|
||||
if (rule->titlematch != Rules::UnimportantMatch) {
|
||||
quality += rule->titlematch == Rules::ExactMatch ? 3 : 1;
|
||||
generic = false;
|
||||
}
|
||||
if (rule->types != NET::AllTypesMask) {
|
||||
// Checks that type fits the mask, and only one of the types
|
||||
int bits = 0;
|
||||
for (unsigned int bit = 1; bit < 1U << 31; bit <<= 1) {
|
||||
if (rule->types & bit) {
|
||||
++bits;
|
||||
}
|
||||
}
|
||||
if (bits == 1) {
|
||||
quality += 2;
|
||||
}
|
||||
}
|
||||
if (generic) { // ignore generic rules, use only the ones that are for this window
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (rule->types == NET::AllTypesMask) {
|
||||
quality += 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (quality > match_quality) {
|
||||
bestMatchRow = row;
|
||||
match_quality = quality;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatchRow < 0) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return index(bestMatchRow);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -42,6 +42,8 @@ public:
|
|||
void load();
|
||||
void save();
|
||||
|
||||
QModelIndex findRuleWithProperties(const QVariantMap &info, bool wholeApp) const;
|
||||
|
||||
private:
|
||||
RuleBookSettings *m_ruleBook;
|
||||
QVector<Rules *> m_rules;
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "rulesdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickView>
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
RulesDialog::RulesDialog(QWidget* parent, const char* name)
|
||||
: QDialog(parent)
|
||||
, m_rulesModel(new RulesModel(this))
|
||||
{
|
||||
setObjectName(name);
|
||||
setModal(true);
|
||||
setWindowTitle(i18n("Edit Window-Specific Settings"));
|
||||
setWindowIcon(QIcon::fromTheme("preferences-system-windows-actions"));
|
||||
setLayout(new QVBoxLayout);
|
||||
|
||||
// Init RuleEditor QML QuickView
|
||||
QQuickView *quickView = new QQuickView();
|
||||
quickView->setSource(QUrl::fromLocalFile(QStandardPaths::locate(
|
||||
QStandardPaths::GenericDataLocation,
|
||||
QStringLiteral("kpackage/kcms/kcm_kwinrules/contents/ui/RulesEditor.qml"))));
|
||||
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
|
||||
quickView->rootObject()->setProperty("rulesModel", QVariant::fromValue(m_rulesModel));
|
||||
|
||||
m_quickWidget = QWidget::createWindowContainer(quickView, this);
|
||||
m_quickWidget->setMinimumSize(QSize(650, 575));
|
||||
layout()->addWidget(m_quickWidget);
|
||||
|
||||
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
layout()->addWidget(buttons);
|
||||
}
|
||||
|
||||
// window is set only for Alt+F3/Window-specific settings, because the dialog
|
||||
// is then related to one specific window
|
||||
Rules* RulesDialog::edit(Rules* r, const QVariantMap& info, bool show_hints)
|
||||
{
|
||||
Q_UNUSED(show_hints);
|
||||
|
||||
m_rules = r;
|
||||
|
||||
m_rulesModel->importFromRules(m_rules);
|
||||
if (!info.isEmpty()) {
|
||||
m_rulesModel->setSuggestedProperties(info);
|
||||
}
|
||||
|
||||
exec();
|
||||
|
||||
return m_rules;
|
||||
}
|
||||
|
||||
void RulesDialog::accept()
|
||||
{
|
||||
m_rules = m_rulesModel->exportToRules();
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2004 Lubos Lunak <l.lunak@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KWIN_RULESDIALOG_H
|
||||
#define KWIN_RULESDIALOG_H
|
||||
|
||||
#include "rulesmodel.h"
|
||||
#include "../../rules.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
class Rules;
|
||||
|
||||
class RulesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RulesDialog(QWidget* parent = nullptr, const char* name = nullptr);
|
||||
|
||||
Rules* edit(Rules* r, const QVariantMap& info, bool show_hints);
|
||||
|
||||
protected:
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
RulesModel* m_rulesModel;
|
||||
QWidget *m_quickWidget;
|
||||
Rules* m_rules;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // KWIN_RULESDIALOG_H
|
Loading…
Reference in a new issue