Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,40 @@
# KI18N Translation Domain for this library
add_definitions(-DTRANSLATION_DOMAIN=\"kcmkwincommon\")
set(kcmkwincommon_SRC
effectsmodel.cpp
)
qt_add_dbus_interface(kcmkwincommon_SRC
${KWin_SOURCE_DIR}/src/org.kde.kwin.Effects.xml kwin_effects_interface
)
add_library(kcmkwincommon SHARED ${kcmkwincommon_SRC})
target_link_libraries(kcmkwincommon
Qt::Core
Qt::DBus
KF6::CoreAddons
KF6::ConfigCore
KF6::I18n
KF6::Package
KF6::KCMUtils
)
set_target_properties(kcmkwincommon PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 6
)
install(TARGETS kcmkwincommon ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP)
set(kcm_kwin4_genericscripted_SRCS genericscriptedconfig.cpp)
qt_add_dbus_interface(kcm_kwin4_genericscripted_SRCS ${kwin_effects_dbus_xml} kwineffects_interface)
add_library(kcm_kwin4_genericscripted MODULE ${kcm_kwin4_genericscripted_SRCS})
target_link_libraries(kcm_kwin4_genericscripted
KF6::KCMUtils #KCModule
KF6::I18n
Qt::DBus
Qt::UiTools
)
install(TARGETS kcm_kwin4_genericscripted DESTINATION ${KDE_INSTALL_PLUGINDIR}/kwin/effects/configs)
@@ -0,0 +1,2 @@
#! /usr/bin/env bash
$XGETTEXT `find . -name \*.cpp` -o $podir/kcmkwincommon.pot
@@ -0,0 +1,610 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Antonis Tsiapaliokas <kok3rs@gmail.com>
SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "effectsmodel.h"
#include "config-kwin.h"
#include <kwin_effects_interface.h>
#include <KAboutData>
#include <KCMultiDialog>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPackage/PackageLoader>
#include <KPluginMetaData>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDirIterator>
#include <QStandardPaths>
namespace KWin
{
static QString translatedCategory(const QString &category)
{
static const QList<QString> knownCategories = {
QStringLiteral("Accessibility"),
QStringLiteral("Appearance"),
QStringLiteral("Focus"),
QStringLiteral("Show Desktop Animation"),
QStringLiteral("Tools"),
QStringLiteral("Virtual Desktop Switching Animation"),
QStringLiteral("Window Management"),
QStringLiteral("Window Open/Close Animation")};
static const QList<QString> translatedCategories = {
i18nc("Category of Desktop Effects, used as section header", "Accessibility"),
i18nc("Category of Desktop Effects, used as section header", "Appearance"),
i18nc("Category of Desktop Effects, used as section header", "Focus"),
i18nc("Category of Desktop Effects, used as section header", "Peek at Desktop Animation"),
i18nc("Category of Desktop Effects, used as section header", "Tools"),
i18nc("Category of Desktop Effects, used as section header", "Virtual Desktop Switching Animation"),
i18nc("Category of Desktop Effects, used as section header", "Window Management"),
i18nc("Category of Desktop Effects, used as section header", "Window Open/Close Animation")};
const int index = knownCategories.indexOf(category);
if (index == -1) {
qDebug() << "Unknown category '" << category << "' and thus not translated";
return category;
}
return translatedCategories[index];
}
static EffectsModel::Status effectStatus(bool enabled)
{
return enabled ? EffectsModel::Status::Enabled : EffectsModel::Status::Disabled;
}
EffectsModel::EffectsModel(QObject *parent)
: QAbstractItemModel(parent)
{
}
QHash<int, QByteArray> EffectsModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[NameRole] = "NameRole";
roleNames[DescriptionRole] = "DescriptionRole";
roleNames[AuthorNameRole] = "AuthorNameRole";
roleNames[AuthorEmailRole] = "AuthorEmailRole";
roleNames[LicenseRole] = "LicenseRole";
roleNames[VersionRole] = "VersionRole";
roleNames[CategoryRole] = "CategoryRole";
roleNames[ServiceNameRole] = "ServiceNameRole";
roleNames[IconNameRole] = "IconNameRole";
roleNames[StatusRole] = "StatusRole";
roleNames[VideoRole] = "VideoRole";
roleNames[WebsiteRole] = "WebsiteRole";
roleNames[SupportedRole] = "SupportedRole";
roleNames[ExclusiveRole] = "ExclusiveRole";
roleNames[ConfigurableRole] = "ConfigurableRole";
roleNames[EnabledByDefaultRole] = "EnabledByDefaultRole";
roleNames[EnabledByDefaultFunctionRole] = "EnabledByDefaultFunctionRole";
roleNames[ConfigModuleRole] = "ConfigModuleRole";
return roleNames;
}
QModelIndex EffectsModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid() || column > 0 || column < 0 || row < 0 || row >= m_effects.count()) {
return {};
}
return createIndex(row, column);
}
QModelIndex EffectsModel::parent(const QModelIndex &child) const
{
return {};
}
int EffectsModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
int EffectsModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_effects.count();
}
QVariant EffectsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
const EffectData effect = m_effects.at(index.row());
switch (role) {
case Qt::DisplayRole:
case NameRole:
return effect.name;
case DescriptionRole:
return effect.description;
case AuthorNameRole:
return effect.authorName;
case AuthorEmailRole:
return effect.authorEmail;
case LicenseRole:
return effect.license;
case VersionRole:
return effect.version;
case CategoryRole:
return effect.category;
case ServiceNameRole:
return effect.serviceName;
case IconNameRole:
return effect.iconName;
case StatusRole:
return static_cast<int>(effect.status);
case VideoRole:
return effect.video;
case WebsiteRole:
return effect.website;
case SupportedRole:
return effect.supported;
case ExclusiveRole:
return effect.exclusiveGroup;
case InternalRole:
return effect.internal;
case ConfigurableRole:
return !effect.configModule.isEmpty();
case EnabledByDefaultRole:
return effect.enabledByDefault;
case EnabledByDefaultFunctionRole:
return effect.enabledByDefaultFunction;
case ConfigModuleRole:
return effect.configModule;
default:
return {};
}
}
bool EffectsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid()) {
return QAbstractItemModel::setData(index, value, role);
}
if (role == StatusRole) {
// note: whenever the StatusRole is modified (even to the same value) the entry
// gets marked as changed and will get saved to the config file. This means the
// config file could get polluted
EffectData &data = m_effects[index.row()];
data.status = Status(value.toInt());
data.changed = data.status != data.originalStatus;
Q_EMIT dataChanged(index, index);
if (data.status == Status::Enabled && !data.exclusiveGroup.isEmpty()) {
// need to disable all other exclusive effects in the same category
for (int i = 0; i < m_effects.size(); ++i) {
if (i == index.row()) {
continue;
}
EffectData &otherData = m_effects[i];
if (otherData.exclusiveGroup == data.exclusiveGroup) {
otherData.status = Status::Disabled;
otherData.changed = otherData.status != otherData.originalStatus;
Q_EMIT dataChanged(this->index(i, 0), this->index(i, 0));
}
}
}
return true;
}
return QAbstractItemModel::setData(index, value, role);
}
void EffectsModel::loadBuiltInEffects(const KConfigGroup &kwinConfig)
{
const QString rootDirectory = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QStringLiteral("kwin/builtin-effects"),
QStandardPaths::LocateDirectory);
const QStringList nameFilters{QStringLiteral("*.json")};
QDirIterator it(rootDirectory, nameFilters, QDir::Files);
while (it.hasNext()) {
it.next();
const KPluginMetaData metaData = KPluginMetaData::fromJsonFile(it.filePath());
if (!metaData.isValid()) {
continue;
}
EffectData effect;
effect.name = metaData.name();
effect.description = metaData.description();
effect.authorName = i18n("KWin development team");
effect.authorEmail = QString(); // not used at all
effect.license = metaData.license();
effect.version = metaData.version();
effect.untranslatedCategory = metaData.category();
effect.category = translatedCategory(metaData.category());
effect.serviceName = metaData.pluginId();
effect.iconName = metaData.iconName();
effect.enabledByDefault = metaData.isEnabledByDefault();
effect.supported = true;
effect.enabledByDefaultFunction = false;
effect.internal = false;
effect.configModule = metaData.value(QStringLiteral("X-KDE-ConfigModule"));
effect.website = QUrl(metaData.website());
if (metaData.rawData().contains("org.kde.kwin.effect")) {
const QJsonObject d(metaData.rawData().value("org.kde.kwin.effect").toObject());
effect.exclusiveGroup = d.value("exclusiveGroup").toString();
effect.video = QUrl::fromUserInput(d.value("video").toString());
effect.enabledByDefaultFunction = d.value("enabledByDefaultMethod").toBool();
effect.internal = d.value("internal").toBool();
}
const QString enabledKey = QStringLiteral("%1Enabled").arg(effect.serviceName);
if (kwinConfig.hasKey(enabledKey)) {
effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
} else if (effect.enabledByDefaultFunction) {
effect.status = Status::EnabledUndeterminded;
} else {
effect.status = effectStatus(effect.enabledByDefault);
}
effect.originalStatus = effect.status;
if (shouldStore(effect)) {
m_pendingEffects << effect;
}
}
}
void EffectsModel::loadJavascriptEffects(const KConfigGroup &kwinConfig)
{
const auto plugins = KPackage::PackageLoader::self()->listPackages(
QStringLiteral("KWin/Effect"),
QStringLiteral("kwin/effects"));
for (const KPluginMetaData &plugin : plugins) {
EffectData effect;
effect.name = plugin.name();
effect.description = plugin.description();
const auto authors = plugin.authors();
effect.authorName = !authors.isEmpty() ? authors.first().name() : QString();
effect.authorEmail = !authors.isEmpty() ? authors.first().emailAddress() : QString();
effect.license = plugin.license();
effect.version = plugin.version();
effect.untranslatedCategory = plugin.category();
effect.category = translatedCategory(plugin.category());
effect.serviceName = plugin.pluginId();
effect.iconName = plugin.iconName();
effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", plugin.isEnabledByDefault()));
effect.originalStatus = effect.status;
effect.enabledByDefault = plugin.isEnabledByDefault();
effect.enabledByDefaultFunction = false;
effect.video = QUrl(plugin.value(QStringLiteral("X-KWin-Video-Url")));
effect.website = QUrl(plugin.website());
effect.supported = true;
effect.exclusiveGroup = plugin.value(QStringLiteral("X-KWin-Exclusive-Category"));
effect.internal = plugin.value(QStringLiteral("X-KWin-Internal"), false);
if (const QString configModule = plugin.value(QStringLiteral("X-KDE-ConfigModule")); !configModule.isEmpty()) {
if (configModule == QLatin1StringView("kcm_kwin4_genericscripted")) {
const QString xmlFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kwin/effects/") + plugin.pluginId() + QLatin1String("/contents/config/main.xml"));
const QString uiFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kwin/effects/") + plugin.pluginId() + QLatin1String("/contents/ui/config.ui"));
if (QFileInfo::exists(xmlFile) && QFileInfo::exists(uiFile)) {
effect.configModule = configModule;
effect.configArgs = QVariantList{plugin.pluginId(), QStringLiteral("KWin/Effect")};
}
} else {
effect.configModule = configModule;
}
}
if (shouldStore(effect)) {
m_pendingEffects << effect;
}
}
}
void EffectsModel::loadPluginEffects(const KConfigGroup &kwinConfig)
{
const auto pluginEffects = KPluginMetaData::findPlugins(QStringLiteral("kwin/effects/plugins"));
for (const KPluginMetaData &pluginEffect : pluginEffects) {
if (!pluginEffect.isValid()) {
continue;
}
EffectData effect;
effect.name = pluginEffect.name();
effect.description = pluginEffect.description();
effect.license = pluginEffect.license();
effect.version = pluginEffect.version();
effect.untranslatedCategory = pluginEffect.category();
effect.category = translatedCategory(pluginEffect.category());
effect.serviceName = pluginEffect.pluginId();
effect.iconName = pluginEffect.iconName();
effect.enabledByDefault = pluginEffect.isEnabledByDefault();
effect.supported = true;
effect.enabledByDefaultFunction = false;
effect.internal = false;
effect.configModule = pluginEffect.value(QStringLiteral("X-KDE-ConfigModule"));
for (int i = 0; i < pluginEffect.authors().count(); ++i) {
effect.authorName.append(pluginEffect.authors().at(i).name());
effect.authorEmail.append(pluginEffect.authors().at(i).emailAddress());
if (i + 1 < pluginEffect.authors().count()) {
effect.authorName.append(", ");
effect.authorEmail.append(", ");
}
}
if (pluginEffect.rawData().contains("org.kde.kwin.effect")) {
const QJsonObject d(pluginEffect.rawData().value("org.kde.kwin.effect").toObject());
effect.exclusiveGroup = d.value("exclusiveGroup").toString();
effect.video = QUrl::fromUserInput(d.value("video").toString());
effect.enabledByDefaultFunction = d.value("enabledByDefaultMethod").toBool();
}
effect.website = QUrl(pluginEffect.website());
const QString enabledKey = QStringLiteral("%1Enabled").arg(effect.serviceName);
if (kwinConfig.hasKey(enabledKey)) {
effect.status = effectStatus(kwinConfig.readEntry(effect.serviceName + "Enabled", effect.enabledByDefault));
} else if (effect.enabledByDefaultFunction) {
effect.status = Status::EnabledUndeterminded;
} else {
effect.status = effectStatus(effect.enabledByDefault);
}
effect.originalStatus = effect.status;
if (shouldStore(effect)) {
m_pendingEffects << effect;
}
}
}
void EffectsModel::load(LoadOptions options)
{
KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), QStringLiteral("Plugins"));
m_pendingEffects.clear();
loadBuiltInEffects(kwinConfig);
loadJavascriptEffects(kwinConfig);
loadPluginEffects(kwinConfig);
std::sort(m_pendingEffects.begin(), m_pendingEffects.end(),
[](const EffectData &a, const EffectData &b) {
if (a.category == b.category) {
if (a.exclusiveGroup == b.exclusiveGroup) {
return a.name < b.name;
}
return a.exclusiveGroup < b.exclusiveGroup;
}
return a.category < b.category;
});
auto commit = [this, options] {
if (options == LoadOptions::KeepDirty) {
for (const EffectData &oldEffect : std::as_const(m_effects)) {
if (!oldEffect.changed) {
continue;
}
auto effectIt = std::find_if(m_pendingEffects.begin(), m_pendingEffects.end(),
[oldEffect](const EffectData &data) {
return data.serviceName == oldEffect.serviceName;
});
if (effectIt == m_pendingEffects.end()) {
continue;
}
effectIt->status = oldEffect.status;
effectIt->changed = effectIt->status != effectIt->originalStatus;
}
}
beginResetModel();
m_effects = m_pendingEffects;
m_pendingEffects.clear();
endResetModel();
Q_EMIT loaded();
};
OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
QStringLiteral("/Effects"),
QDBusConnection::sessionBus());
if (interface.isValid()) {
QStringList effectNames;
effectNames.reserve(m_pendingEffects.count());
for (const EffectData &data : std::as_const(m_pendingEffects)) {
effectNames.append(data.serviceName);
}
const int serial = ++m_lastSerial;
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(interface.areEffectsSupported(effectNames), this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [=, this](QDBusPendingCallWatcher *self) {
self->deleteLater();
if (m_lastSerial != serial) {
return;
}
const QDBusPendingReply<QList<bool>> reply = *self;
if (reply.isError()) {
commit();
return;
}
const QList<bool> supportedValues = reply.value();
if (supportedValues.count() != effectNames.count()) {
return;
}
for (int i = 0; i < effectNames.size(); ++i) {
const bool supported = supportedValues.at(i);
const QString effectName = effectNames.at(i);
auto it = std::find_if(m_pendingEffects.begin(), m_pendingEffects.end(),
[effectName](const EffectData &data) {
return data.serviceName == effectName;
});
if (it == m_pendingEffects.end()) {
continue;
}
if ((*it).supported != supported) {
(*it).supported = supported;
}
}
commit();
});
} else {
commit();
}
}
void EffectsModel::updateEffectStatus(const QModelIndex &rowIndex, Status effectState)
{
setData(rowIndex, static_cast<int>(effectState), StatusRole);
}
void EffectsModel::save()
{
KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), QStringLiteral("Plugins"));
QList<EffectData> dirtyEffects;
for (EffectData &effect : m_effects) {
if (!effect.changed) {
continue;
}
effect.changed = false;
effect.originalStatus = effect.status;
const QString key = effect.serviceName + QStringLiteral("Enabled");
const bool shouldEnable = (effect.status != Status::Disabled);
const bool restoreToDefault = effect.enabledByDefaultFunction
? effect.status == Status::EnabledUndeterminded
: shouldEnable == effect.enabledByDefault;
if (restoreToDefault) {
kwinConfig.deleteEntry(key);
} else {
kwinConfig.writeEntry(key, shouldEnable);
}
dirtyEffects.append(effect);
}
if (dirtyEffects.isEmpty()) {
return;
}
kwinConfig.sync();
OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
QStringLiteral("/Effects"),
QDBusConnection::sessionBus());
if (!interface.isValid()) {
return;
}
// Unload effects first, it's need to ensure that switching between mutually exclusive
// effects works as expected, for example so global shortcuts are handed over, etc.
auto split = std::partition(dirtyEffects.begin(), dirtyEffects.end(), [](const EffectData &data) {
return data.status == Status::Disabled;
});
for (auto it = dirtyEffects.begin(); it != split; ++it) {
interface.unloadEffect(it->serviceName);
}
for (auto it = split; it != dirtyEffects.end(); ++it) {
interface.loadEffect(it->serviceName);
}
}
void EffectsModel::defaults()
{
for (int i = 0; i < m_effects.count(); ++i) {
const auto &effect = m_effects.at(i);
if (effect.enabledByDefaultFunction && effect.status != Status::EnabledUndeterminded) {
updateEffectStatus(index(i, 0), Status::EnabledUndeterminded);
} else if (static_cast<bool>(effect.status) != effect.enabledByDefault) {
updateEffectStatus(index(i, 0), effect.enabledByDefault ? Status::Enabled : Status::Disabled);
}
}
}
bool EffectsModel::isDefaults() const
{
return std::all_of(m_effects.constBegin(), m_effects.constEnd(), [](const EffectData &effect) {
if (effect.enabledByDefaultFunction && effect.status != Status::EnabledUndeterminded) {
return false;
}
if (static_cast<bool>(effect.status) != effect.enabledByDefault) {
return false;
}
return true;
});
}
bool EffectsModel::needsSave() const
{
return std::any_of(m_effects.constBegin(), m_effects.constEnd(),
[](const EffectData &data) {
return data.changed;
});
}
QModelIndex EffectsModel::findByPluginId(const QString &pluginId) const
{
auto it = std::find_if(m_effects.constBegin(), m_effects.constEnd(),
[pluginId](const EffectData &data) {
return data.serviceName == pluginId;
});
if (it == m_effects.constEnd()) {
return {};
}
return index(std::distance(m_effects.constBegin(), it), 0);
}
void EffectsModel::requestConfigure(const QModelIndex &index, QWindow *transientParent)
{
if (!index.isValid()) {
return;
}
const EffectData &effect = m_effects.at(index.row());
Q_ASSERT(!effect.configModule.isEmpty());
KCMultiDialog *dialog = new KCMultiDialog();
dialog->addModule(KPluginMetaData(QStringLiteral("kwin/effects/configs/") + effect.configModule), effect.configArgs);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->winId();
dialog->windowHandle()->setTransientParent(transientParent);
dialog->show();
}
bool EffectsModel::shouldStore(const EffectData &data) const
{
return !data.internal;
}
}
#include "moc_effectsmodel.cpp"
@@ -0,0 +1,262 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Antonis Tsiapaliokas <kok3rs@gmail.com>
SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <kwin_export.h>
#include <KSharedConfig>
#include <QAbstractItemModel>
#include <QString>
#include <QUrl>
#include <QWindow>
namespace KWin
{
class KWIN_EXPORT EffectsModel : public QAbstractItemModel
{
Q_OBJECT
public:
/**
* This enum type is used to specify data roles.
*/
enum AdditionalRoles {
/**
* The user-friendly name of the effect.
*/
NameRole = Qt::UserRole + 1,
/**
* The description of the effect.
*/
DescriptionRole,
/**
* The name of the effect's author. If there are several authors, they
* will be comma separated.
*/
AuthorNameRole,
/**
* The email of the effect's author. If there are several authors, the
* emails will be comma separated.
*/
AuthorEmailRole,
/**
* The license of the effect.
*/
LicenseRole,
/**
* The version of the effect.
*/
VersionRole,
/**
* The category of the effect.
*/
CategoryRole,
/**
* The service name(plugin name) of the effect.
*/
ServiceNameRole,
/**
* The icon name of the effect.
*/
IconNameRole,
/**
* Whether the effect is enabled or disabled.
*/
StatusRole,
/**
* Link to a video demonstration of the effect.
*/
VideoRole,
/**
* Link to the home page of the effect.
*/
WebsiteRole,
/**
* Whether the effect is supported.
*/
SupportedRole,
/**
* The exclusive group of the effect.
*/
ExclusiveRole,
/**
* Whether the effect is internal.
*/
InternalRole,
/**
* Whether the effect has a KCM.
*/
ConfigurableRole,
/**
* Whether the effect is enabled by default.
*/
EnabledByDefaultRole,
/**
* Id of the effect's config module, empty if the effect has no config.
*/
ConfigModuleRole,
/**
* Whether the effect has a function to determine if the effect is enabled by default.
*/
EnabledByDefaultFunctionRole,
};
/**
* This enum type is used to specify the status of a given effect.
*/
enum class Status {
/**
* The effect is disabled.
*/
Disabled = Qt::Unchecked,
/**
* An enable function is used to determine whether the effect is enabled.
* For example, such function can be useful to disable the blur effect
* when running in a virtual machine.
*/
EnabledUndeterminded = Qt::PartiallyChecked,
/**
* The effect is enabled.
*/
Enabled = Qt::Checked
};
explicit EffectsModel(QObject *parent = nullptr);
// Reimplemented from QAbstractItemModel.
QHash<int, QByteArray> roleNames() const override;
QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
/**
* Changes the status of a given effect.
*
* @param rowIndex An effect represented by the given index.
* @param effectState The new state.
* @note In order to actually apply the change, you have to call save().
*/
void updateEffectStatus(const QModelIndex &rowIndex, Status effectState);
/**
* This enum type is used to specify load options.
*/
enum class LoadOptions {
None,
/**
* Do not discard unsaved changes when reloading the model.
*/
KeepDirty
};
/**
* Loads effects.
*
* You have to call this method in order to populate the model.
*/
void load(LoadOptions options = LoadOptions::None);
/**
* Saves status of each modified effect.
*/
void save();
/**
* Resets the status of each effect to the default state.
*
* @note In order to actually apply the change, you have to call save().
*/
void defaults();
/**
* Whether the status of each effect is its default state.
*/
bool isDefaults() const;
/**
* Whether the model has unsaved changes.
*/
bool needsSave() const;
/**
* Finds an effect with the given plugin id.
*/
QModelIndex findByPluginId(const QString &pluginId) const;
/**
* Shows a configuration dialog for a given effect.
*
* @param index An effect represented by the given index.
* @param transientParent The transient parent of the configuration dialog.
*/
void requestConfigure(const QModelIndex &index, QWindow *transientParent);
Q_SIGNALS:
/**
* This signal is emitted when the model is loaded or reloaded.
*
* @see load
*/
void loaded();
protected:
struct EffectData
{
QString name;
QString description;
QString authorName;
QString authorEmail;
QString license;
QString version;
QString untranslatedCategory;
QString category;
QString serviceName;
QString iconName;
Status status;
Status originalStatus;
bool enabledByDefault;
bool enabledByDefaultFunction;
QUrl video;
QUrl website;
bool supported;
QString exclusiveGroup;
bool internal;
bool changed = false;
QString configModule;
QVariantList configArgs;
};
/**
* Returns whether the given effect should be stored in the model.
*
* @param data The effect.
* @returns @c true if the effect should be stored, otherwise @c false.
*/
virtual bool shouldStore(const EffectData &data) const;
private:
void loadBuiltInEffects(const KConfigGroup &kwinConfig);
void loadJavascriptEffects(const KConfigGroup &kwinConfig);
void loadPluginEffects(const KConfigGroup &kwinConfig);
QList<EffectData> m_effects;
QList<EffectData> m_pendingEffects;
int m_lastSerial = -1;
Q_DISABLE_COPY(EffectsModel)
};
}
@@ -0,0 +1,185 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "genericscriptedconfig.h"
#include "config-kwin.h"
#include <kwineffects_interface.h>
#include <KLocalizedString>
#include <KLocalizedTranslator>
#include <kconfigloader.h>
#include <QFile>
#include <QLabel>
#include <QStandardPaths>
#include <QUiLoader>
#include <QVBoxLayout>
namespace KWin
{
QObject *GenericScriptedConfigFactory::create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args)
{
if (qstrcmp(iface, "KCModule") == 0) {
if (args.count() < 2) {
qWarning() << Q_FUNC_INFO << "expects two arguments (plugin id, package type)";
return nullptr;
}
const QString pluginId = args.at(0).toString();
const QString packageType = args.at(1).toString();
if (packageType == QLatin1StringView("KWin/Effect")) {
return new ScriptedEffectConfig(pluginId, parentWidget, args);
} else if (packageType == QLatin1StringView("KWin/Script")) {
return new ScriptingConfig(pluginId, parentWidget, args);
} else {
qWarning() << Q_FUNC_INFO << "got unknown package type:" << packageType;
}
}
return nullptr;
}
GenericScriptedConfig::GenericScriptedConfig(const QString &keyword, QWidget *parent, const QVariantList &args)
: KCModule(parent, KPluginMetaData())
, m_packageName(keyword)
, m_translator(new KLocalizedTranslator(this))
{
QCoreApplication::instance()->installTranslator(m_translator);
}
GenericScriptedConfig::~GenericScriptedConfig()
{
}
void GenericScriptedConfig::createUi()
{
QVBoxLayout *layout = new QVBoxLayout(widget());
const QString packageRoot = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QLatin1String("kwin/") + typeName() + QLatin1Char('/') + m_packageName,
QStandardPaths::LocateDirectory);
if (packageRoot.isEmpty()) {
layout->addWidget(new QLabel(i18nc("Error message", "Could not locate package metadata")));
return;
}
const KPluginMetaData metaData = KPluginMetaData::fromJsonFile(packageRoot + QLatin1String("/metadata.json"));
if (!metaData.isValid()) {
layout->addWidget(new QLabel(i18nc("Required file does not exist", "%1 does not contain a valid metadata.json file", qPrintable(packageRoot))));
return;
}
const QString kconfigXTFile = packageRoot + QLatin1String("/contents/config/main.xml");
if (!QFileInfo::exists(kconfigXTFile)) {
layout->addWidget(new QLabel(i18nc("Required file does not exist", "%1 does not exist", qPrintable(kconfigXTFile))));
return;
}
const QString uiPath = packageRoot + QLatin1String("/contents/ui/config.ui");
if (!QFileInfo::exists(uiPath)) {
layout->addWidget(new QLabel(i18nc("Required file does not exist", "%1 does not exist", qPrintable(uiPath))));
return;
}
const QString localePath = packageRoot + QLatin1String("/contents/locale");
if (QFileInfo::exists(localePath)) {
KLocalizedString::addDomainLocaleDir(metaData.value("X-KWin-Config-TranslationDomain").toUtf8(), localePath);
}
QFile xmlFile(kconfigXTFile);
KConfigGroup cg = configGroup();
KConfigLoader *configLoader = new KConfigLoader(cg, &xmlFile, this);
// load the ui file
QUiLoader *loader = new QUiLoader(this);
loader->setLanguageChangeEnabled(true);
QFile uiFile(uiPath);
m_translator->setTranslationDomain(metaData.value("X-KWin-Config-TranslationDomain"));
uiFile.open(QFile::ReadOnly);
QWidget *customConfigForm = loader->load(&uiFile, widget());
m_translator->addContextToMonitor(customConfigForm->objectName());
uiFile.close();
// send a custom event to the translator to retranslate using our translator
QEvent le(QEvent::LanguageChange);
QCoreApplication::sendEvent(customConfigForm, &le);
layout->addWidget(customConfigForm);
addConfig(configLoader, customConfigForm);
}
void GenericScriptedConfig::save()
{
KCModule::save();
reload();
}
void GenericScriptedConfig::reload()
{
}
ScriptedEffectConfig::ScriptedEffectConfig(const QString &keyword, QWidget *parent, const QVariantList &args)
: GenericScriptedConfig(keyword, parent, args)
{
createUi();
}
ScriptedEffectConfig::~ScriptedEffectConfig()
{
}
QString ScriptedEffectConfig::typeName() const
{
return QStringLiteral("effects");
}
KConfigGroup ScriptedEffectConfig::configGroup()
{
return KSharedConfig::openConfig(KWIN_CONFIG)->group(QLatin1String("Effect-") + packageName());
}
void ScriptedEffectConfig::reload()
{
OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
QStringLiteral("/Effects"),
QDBusConnection::sessionBus());
interface.reconfigureEffect(packageName());
}
ScriptingConfig::ScriptingConfig(const QString &keyword, QWidget *parent, const QVariantList &args)
: GenericScriptedConfig(keyword, parent, args)
{
createUi();
}
ScriptingConfig::~ScriptingConfig()
{
}
KConfigGroup ScriptingConfig::configGroup()
{
return KSharedConfig::openConfig(KWIN_CONFIG)->group(QLatin1String("Script-") + packageName());
}
QString ScriptingConfig::typeName() const
{
return QStringLiteral("scripts");
}
void ScriptingConfig::reload()
{
// TODO: what to call
}
} // namespace
#include "moc_genericscriptedconfig.cpp"
@@ -0,0 +1,85 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <KCModule>
#include <KConfigGroup>
#include <KPluginFactory>
class KLocalizedTranslator;
namespace KWin
{
class GenericScriptedConfigFactory : public KPluginFactory
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.KPluginFactory" FILE "genericscriptedconfig.json")
Q_INTERFACES(KPluginFactory)
protected:
QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args) override;
};
class GenericScriptedConfig : public KCModule
{
Q_OBJECT
public:
GenericScriptedConfig(const QString &keyword, QWidget *parent, const QVariantList &args);
~GenericScriptedConfig() override;
public Q_SLOTS:
void save() override;
protected:
const QString &packageName() const;
void createUi();
virtual QString typeName() const = 0;
virtual KConfigGroup configGroup() = 0;
virtual void reload();
private:
QString m_packageName;
KLocalizedTranslator *m_translator;
};
class ScriptedEffectConfig : public GenericScriptedConfig
{
Q_OBJECT
public:
ScriptedEffectConfig(const QString &keyword, QWidget *parent, const QVariantList &args);
~ScriptedEffectConfig() override;
protected:
QString typeName() const override;
KConfigGroup configGroup() override;
void reload() override;
};
class ScriptingConfig : public GenericScriptedConfig
{
Q_OBJECT
public:
ScriptingConfig(const QString &keyword, QWidget *parent, const QVariantList &args);
~ScriptingConfig() override;
protected:
QString typeName() const override;
KConfigGroup configGroup() override;
void reload() override;
};
inline const QString &GenericScriptedConfig::packageName() const
{
return m_packageName;
}
}
@@ -0,0 +1,4 @@
{
"Type": "Service",
"X-KDE-Library": "kcm_kwin4_genericscripted"
}