Commit 2c2cd9ba authored by Dan Leinir Turthra Jensen's avatar Dan Leinir Turthra Jensen 🌈
Browse files

Add KNS adoption tools for Plasma, global, and cursor themes, color schemes, and wallpapers

This adds a small cli helper tool which allows you to list what Plasma
themes are available on the system (and which is the current one), as
well as letting you set a theme as the current. It handles being passed
a full path to the theme, though when that happens it just uses the last
bit of the path as the name of the theme.

The latter part allows it to be used by KNewStuff as an adoption
command, and the changes to the knsrc file makes that happen. There is
also a small change which fixes a discrepancy in the naming of the
Plasma Themes dialog, making it consistent with everywhere else that
references Plasma Themes (not sure why they were called styles here,
guessing a really old copy/paste thing).

Additionally, a similar tool for the color and cursor KCMs are also added by
this patch, and the lookandfeeltool binary built by the global themes kcm is
also modified slightly to accept a full path to a package, and added as
an adoption command for those.

The patch also adds a tool which sets wallpapers, which replaces the
failure-prone dbus command previously used as the kns adoption command.
parent 760e5329
......@@ -4,6 +4,7 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcm_colors\")
set(kcm_colors_SRCS
../krdb/krdb.cpp
colors.cpp
colorsapplicator.cpp
colorsmodel.cpp
filterproxymodel.cpp
)
......@@ -41,9 +42,35 @@ endif()
kcoreaddons_desktop_to_json(kcm_colors "kcm_colors.desktop")
set(plasma-apply-colorscheme_SRCS
plasma-apply-colorscheme.cpp
colorsapplicator.cpp
colorsmodel.cpp
../krdb/krdb.cpp
)
kconfig_add_kcfg_files(plasma-apply-colorscheme_SRCS colorssettings.kcfgc GENERATE_MOC)
add_executable(plasma-apply-colorscheme ${plasma-apply-colorscheme_SRCS})
target_link_libraries(plasma-apply-colorscheme
Qt::Core
Qt::DBus
Qt::Gui
Qt::X11Extras
KF5::GuiAddons
KF5::KCMUtils
KF5::I18n
KF5::WindowSystem
PW::KWorkspace
X11::X11
)
install(FILES colorssettings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
install(FILES kcm_colors.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
install(TARGETS kcm_colors DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
install(TARGETS plasma-apply-colorscheme DESTINATION ${KDE_INSTALL_BINDIR})
install(FILES colorschemes.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR})
kpackage_install_package(package kcm_colors kcms)
......
......@@ -53,6 +53,7 @@
#include "../krdb/krdb.h"
#include "colorsapplicator.h"
#include "colorsdata.h"
#include "colorsmodel.h"
#include "colorssettings.h"
......@@ -351,119 +352,9 @@ void KCMColors::save()
processPendingDeletions();
}
static void copyEntry(KConfigGroup &from, KConfigGroup &to, const QString &entry)
{
if (from.hasKey(entry)) {
to.writeEntry(entry, from.readEntry(entry));
}
}
void KCMColors::saveColors()
{
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes/%1.colors").arg(m_model->selectedScheme()));
// Using KConfig::SimpleConfig because otherwise Header colors won't be
// rewritten when a new color scheme is loaded.
KSharedConfigPtr config = KSharedConfig::openConfig(path, KConfig::SimpleConfig);
const QStringList colorSetGroupList{QStringLiteral("Colors:View"),
QStringLiteral("Colors:Window"),
QStringLiteral("Colors:Button"),
QStringLiteral("Colors:Selection"),
QStringLiteral("Colors:Tooltip"),
QStringLiteral("Colors:Complementary"),
QStringLiteral("Colors:Header")};
const QStringList colorSetKeyList{QStringLiteral("BackgroundNormal"),
QStringLiteral("BackgroundAlternate"),
QStringLiteral("ForegroundNormal"),
QStringLiteral("ForegroundInactive"),
QStringLiteral("ForegroundActive"),
QStringLiteral("ForegroundLink"),
QStringLiteral("ForegroundVisited"),
QStringLiteral("ForegroundNegative"),
QStringLiteral("ForegroundNeutral"),
QStringLiteral("ForegroundPositive"),
QStringLiteral("DecorationFocus"),
QStringLiteral("DecorationHover")};
for (auto item : colorSetGroupList) {
m_config->deleteGroup(item);
KConfigGroup sourceGroup(config, item);
KConfigGroup targetGroup(m_config, item);
for (auto entry : colorSetKeyList) {
copyEntry(sourceGroup, targetGroup, entry);
}
if (sourceGroup.hasGroup("Inactive")) {
sourceGroup = sourceGroup.group("Inactive");
targetGroup = targetGroup.group("Inactive");
for (auto entry : colorSetKeyList) {
copyEntry(sourceGroup, targetGroup, entry);
}
}
}
KConfigGroup groupWMTheme(config, "WM");
KConfigGroup groupWMOut(m_config, "WM");
KColorScheme inactiveHeaderColorScheme(QPalette::Inactive, KColorScheme::Header, config);
const QStringList colorItemListWM{QStringLiteral("activeBackground"),
QStringLiteral("activeForeground"),
QStringLiteral("inactiveBackground"),
QStringLiteral("inactiveForeground"),
QStringLiteral("activeBlend"),
QStringLiteral("inactiveBlend")};
const QVector<QColor> defaultWMColors{KColorScheme(QPalette::Normal, KColorScheme::Header, config).background().color(),
KColorScheme(QPalette::Normal, KColorScheme::Header, config).foreground().color(),
inactiveHeaderColorScheme.background().color(),
inactiveHeaderColorScheme.foreground().color(),
KColorScheme(QPalette::Normal, KColorScheme::Header, config).background().color(),
inactiveHeaderColorScheme.background().color()};
int i = 0;
for (const QString &coloritem : colorItemListWM) {
groupWMOut.writeEntry(coloritem, groupWMTheme.readEntry(coloritem, defaultWMColors.value(i)));
++i;
}
const QStringList groupNameList{QStringLiteral("ColorEffects:Inactive"), QStringLiteral("ColorEffects:Disabled")};
const QStringList effectList{QStringLiteral("Enable"),
QStringLiteral("ChangeSelectionColor"),
QStringLiteral("IntensityEffect"),
QStringLiteral("IntensityAmount"),
QStringLiteral("ColorEffect"),
QStringLiteral("ColorAmount"),
QStringLiteral("Color"),
QStringLiteral("ContrastEffect"),
QStringLiteral("ContrastAmount")};
for (const QString &groupName : groupNameList) {
KConfigGroup groupEffectOut(m_config, groupName);
KConfigGroup groupEffectTheme(config, groupName);
for (const QString &effect : effectList) {
groupEffectOut.writeEntry(effect, groupEffectTheme.readEntry(effect));
}
}
m_config->sync();
runRdb(KRdbExportQtColors | KRdbExportGtkTheme | (m_applyToAlien ? KRdbExportColors : 0));
QDBusMessage message =
QDBusMessage::createSignal(QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"));
message.setArguments({
0, // previous KGlobalSettings::PaletteChanged. This is now private API in khintsettings
0 // unused in palette changed but needed for the DBus signature
});
QDBusConnection::sessionBus().send(message);
applyScheme(colorsSettings(), m_model);
m_selectedSchemeDirty = false;
}
......
/*
Copyright (c) 2021 Dan Leinir Turthra Jensen <admin@leinir.dk>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "colorssettings.h"
#include "colorsmodel.h"
#include "../krdb/krdb.h"
#include <KColorScheme>
#include <KConfig>
#include <QDBusConnection>
#include <QDBusMessage>
static void copyEntry(KConfigGroup &from, KConfigGroup &to, const QString &entry)
{
if (from.hasKey(entry)) {
to.writeEntry(entry, from.readEntry(entry));
}
}
void applyScheme(ColorsSettings *settings, ColorsModel *model)
{
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes/%1.colors").arg(model->selectedScheme()));
// Using KConfig::SimpleConfig because otherwise Header colors won't be
// rewritten when a new color scheme is loaded.
KSharedConfigPtr config = KSharedConfig::openConfig(path, KConfig::SimpleConfig);
const QStringList colorSetGroupList{QStringLiteral("Colors:View"),
QStringLiteral("Colors:Window"),
QStringLiteral("Colors:Button"),
QStringLiteral("Colors:Selection"),
QStringLiteral("Colors:Tooltip"),
QStringLiteral("Colors:Complementary"),
QStringLiteral("Colors:Header")};
const QStringList colorSetKeyList{QStringLiteral("BackgroundNormal"),
QStringLiteral("BackgroundAlternate"),
QStringLiteral("ForegroundNormal"),
QStringLiteral("ForegroundInactive"),
QStringLiteral("ForegroundActive"),
QStringLiteral("ForegroundLink"),
QStringLiteral("ForegroundVisited"),
QStringLiteral("ForegroundNegative"),
QStringLiteral("ForegroundNeutral"),
QStringLiteral("ForegroundPositive"),
QStringLiteral("DecorationFocus"),
QStringLiteral("DecorationHover")};
for (auto item : colorSetGroupList) {
settings->config()->deleteGroup(item);
KConfigGroup sourceGroup(config, item);
KConfigGroup targetGroup(settings->config(), item);
for (auto entry : colorSetKeyList) {
copyEntry(sourceGroup, targetGroup, entry);
}
if (sourceGroup.hasGroup("Inactive")) {
sourceGroup = sourceGroup.group("Inactive");
targetGroup = targetGroup.group("Inactive");
for (auto entry : colorSetKeyList) {
copyEntry(sourceGroup, targetGroup, entry);
}
}
}
KConfigGroup groupWMTheme(config, "WM");
KConfigGroup groupWMOut(settings->config(), "WM");
KColorScheme inactiveHeaderColorScheme(QPalette::Inactive, KColorScheme::Header, config);
const QStringList colorItemListWM{QStringLiteral("activeBackground"),
QStringLiteral("activeForeground"),
QStringLiteral("inactiveBackground"),
QStringLiteral("inactiveForeground"),
QStringLiteral("activeBlend"),
QStringLiteral("inactiveBlend")};
const QVector<QColor> defaultWMColors{KColorScheme(QPalette::Normal, KColorScheme::Header, config).background().color(),
KColorScheme(QPalette::Normal, KColorScheme::Header, config).foreground().color(),
inactiveHeaderColorScheme.background().color(),
inactiveHeaderColorScheme.foreground().color(),
KColorScheme(QPalette::Normal, KColorScheme::Header, config).background().color(),
inactiveHeaderColorScheme.background().color()};
int i = 0;
for (const QString &coloritem : colorItemListWM) {
groupWMOut.writeEntry(coloritem, groupWMTheme.readEntry(coloritem, defaultWMColors.value(i)));
++i;
}
const QStringList groupNameList{QStringLiteral("ColorEffects:Inactive"), QStringLiteral("ColorEffects:Disabled")};
const QStringList effectList{QStringLiteral("Enable"),
QStringLiteral("ChangeSelectionColor"),
QStringLiteral("IntensityEffect"),
QStringLiteral("IntensityAmount"),
QStringLiteral("ColorEffect"),
QStringLiteral("ColorAmount"),
QStringLiteral("Color"),
QStringLiteral("ContrastEffect"),
QStringLiteral("ContrastAmount")};
for (const QString &groupName : groupNameList) {
KConfigGroup groupEffectOut(settings->config(), groupName);
KConfigGroup groupEffectTheme(config, groupName);
for (const QString &effect : effectList) {
groupEffectOut.writeEntry(effect, groupEffectTheme.readEntry(effect));
}
}
settings->config()->sync();
bool applyToAlien{true};
{
KConfig cfg(QStringLiteral("kcmdisplayrc"), KConfig::NoGlobals);
KConfigGroup group(settings->config(), "General");
group = KConfigGroup(&cfg, "X11");
applyToAlien = group.readEntry("exportKDEColors", applyToAlien);
}
runRdb(KRdbExportQtColors | KRdbExportGtkTheme | (applyToAlien ? KRdbExportColors : 0));
QDBusMessage message =
QDBusMessage::createSignal(QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"));
message.setArguments({
0, // previous KGlobalSettings::PaletteChanged. This is now private API in khintsettings
0 // unused in palette changed but needed for the DBus signature
});
QDBusConnection::sessionBus().send(message);
}
/*
Copyright (c) 2021 Dan Leinir Turthra Jensen <admin@leinir.dk>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef COLORSAPPLICATOR_H
#define COLORSAPPLICATOR_H
class ColorsSettings;
class ColorsModel;
/**
* Performs the task of actually applying a color scheme to the current session, based on
* what is currently set in the settings and model instances passed into the function.
* When using this function, you select the scheme to use by setting the model's selected scheme
* @param settings The settings instance which lets us update the system with the new colors
* @param model The model which holds the information on which scheme is currently selected, and what colors it contains
* @see ColorsModel::setSelectedScheme(QString)
*/
void applyScheme(ColorsSettings *settings, ColorsModel *model);
#endif//COLORSAPPLICATOR_H
......@@ -39,3 +39,4 @@ Uncompress=archive
Categories=KDE Color Scheme KDE4
UploadCategories=KDE Color Scheme KDE4
RemoveDeadEntries=true
AdoptionCommand=plasma-apply-colorscheme %f
/*
Copyright (c) 2021 Dan Leinir Turthra Jensen <admin@leinir.dk>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "colorsapplicator.h"
#include "colorssettings.h"
#include "colorsmodel.h"
#include "../krdb/krdb.h"
#include <KColorScheme>
#include <KConfig>
#include <KLocalizedString>
#include <QGuiApplication>
#include <QCommandLineParser>
#include <QDebug>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QFile>
#include <QTimer>
int main(int argc, char **argv)
{
// This is a CLI application, but we require at least a QGuiApplication to be able
// to use QColor, so let's just roll with one of these
QGuiApplication app(argc, argv);
QCoreApplication::setApplicationName(QStringLiteral("plasma-apply-colorscheme"));
QCoreApplication::setApplicationVersion(QStringLiteral("1.0"));
QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
KLocalizedString::setApplicationDomain("plasma-apply-colorscheme");
QCommandLineParser *parser = new QCommandLineParser;
parser->addHelpOption();
parser->setApplicationDescription(i18n("This tool allows you to set the color scheme for the current Plasma session, without accidentally setting it to one that is either not available, or which is already set."));
parser->addPositionalArgument(QStringLiteral("colorscheme"), i18n("The name of the color scheme you wish to set for your current Plasma session (passing a full path will only use the last part of the path)"));
parser->addOption(QCommandLineOption(QStringLiteral("list-schemes"), i18n("Show all the color schemes available on the system (and which is the current theme)")));
parser->process(app);
int exitCode{0};
ColorsSettings* settings = new ColorsSettings(&app);
QTextStream ts(stdout);
ColorsModel* model = new ColorsModel(&app);
model->load();
model->setSelectedScheme(settings->colorScheme());
if (!parser->positionalArguments().isEmpty()) {
QString requestedScheme{parser->positionalArguments().first()};
const QString dirSplit{"/"};
if (requestedScheme.contains(dirSplit)) {
QStringList splitScheme = requestedScheme.split(dirSplit, Qt::SkipEmptyParts);
requestedScheme = splitScheme.last();
if (requestedScheme.endsWith(QStringLiteral(".colors"))) {
requestedScheme = requestedScheme.left(requestedScheme.lastIndexOf(QStringLiteral(".")));
} else {
exitCode = -1;
}
}
if (exitCode == 0) {
if (settings->colorScheme() == requestedScheme) {
ts << i18n("The requested theme \"%1\" is already set as the theme for the current Plasma session.", requestedScheme) << endl;
// Not an error condition, no reason to set the theme, but basically this is fine
} else if (!requestedScheme.isEmpty()) {
int newSchemeIndex{-1};
QStringList availableThemes;
for (int i = 0 ; i < model->rowCount(QModelIndex()); ++i) {
QString schemeName = model->data(model->index(i, 0), ColorsModel::SchemeNameRole).toString();
availableThemes << schemeName;
if (schemeName == requestedScheme) {
newSchemeIndex = i;
// No breaking out, we're using the list of names if things fail, and
// it's not particularly expensive compared to what we've already done
}
}
if (newSchemeIndex > -1) {
model->setSelectedScheme(requestedScheme);
settings->setColorScheme(requestedScheme);
applyScheme(settings, model);
settings->save();
ts << i18n("Successfully applied the color scheme %1 to your current Plasma session", requestedScheme) << endl;
} else {
ts << i18n("Could not find theme \"%1\". The theme should be one of the following options: %2", requestedScheme, availableThemes.join(QLatin1String{", "})) << endl;
}
} else {
// This shouldn't happen, but let's catch it and make angry noises, just in case...
ts << i18n("You have managed to pass an empty color scheme name, which isn't supported behavior.") << endl;
exitCode = -1;
}
} else {
ts << i18n("The file you attempted to set as your scheme, %1, could not be identified as a color scheme.", parser->positionalArguments().first()) << endl;
exitCode = -1;
}
} else if (parser->isSet(QStringLiteral("list-schemes"))) {
ts << i18n("You have the following color schemes on your system:") << endl;
int currentThemeIndex = model->selectedSchemeIndex();
for (int i = 0 ; i < model->rowCount(QModelIndex()); ++i) {
const QString schemeName = model->data(model->index(i, 0), ColorsModel::SchemeNameRole).toString();
if (i == currentThemeIndex) {
ts << i18n(" * %1 (current color scheme)", schemeName) << endl;
} else {
ts << QString(" * %1").arg(schemeName) << endl;
}
}
} else {
parser->showHelp();
}
QTimer::singleShot(0, &app, [&app,&exitCode](){ app.exit(exitCode); });
return app.exec();
}
......@@ -6,6 +6,7 @@ include_directories( ${LIBUSB_INCLUDE_DIR} )
set( libnoinst_SRCS
xcursor/thememodel.cpp
xcursor/themeapplicator.cpp
xcursor/cursortheme.cpp
xcursor/xcursortheme.cpp
xcursor/previewwidget.cpp
......@@ -60,6 +61,42 @@ install(TARGETS kcm_cursortheme DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms )
kcoreaddons_desktop_to_json(kcm_cursortheme "kcm_cursortheme.desktop")
########### next target ###############
set(plasma-apply-cursortheme_SRCS
plasma-apply-cursortheme.cpp
xcursor/cursortheme.cpp
xcursor/themeapplicator.cpp
xcursor/thememodel.cpp
xcursor/xcursortheme.cpp
../krdb/krdb.cpp
)
kconfig_add_kcfg_files(plasma-applycursortheme_SRCS cursorthemesettings.kcfgc GENERATE_MOC)
add_executable(plasma-apply-cursortheme ${plasma-apply-cursortheme_SRCS})
target_link_libraries(plasma-apply-cursortheme
Qt::DBus
Qt::X11Extras
KF5::GuiAddons
KF5::I18n
KF5::KCMUtils
KF5::KDELibs4Support
KF5::WindowSystem
X11::X11
XCB::XCB
PW::KWorkspace
)
if (X11_Xcursor_FOUND)
target_link_libraries(plasma-apply-cursortheme X11::Xcursor)
endif ()
if (X11_Xfixes_FOUND)
target_link_libraries(plasma-apply-cursortheme X11::Xfixes)
endif ()
install(TARGETS plasma-apply-cursortheme DESTINATION ${KDE_BINDIR} )
########### install files ###############
install(FILES cursorthemesettings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
......
......@@ -26,6 +26,7 @@
#include "xcursor/cursortheme.h"
#include "xcursor/previewwidget.h"
#include "xcursor/sortproxymodel.h"
#include "xcursor/themeapplicator.h"
#include "xcursor/thememodel.h"
#include <KAboutData>
......@@ -280,88 +281,6 @@ void CursorThemeConfig::updateSizeComboBox()
emit cursorThemeSettings()->cursorSizeChanged();
}
bool CursorThemeConfig::applyTheme(const CursorTheme *theme, const int size)
{
// Require the Xcursor version that shipped with X11R6.9 or greater, since
// in previous versions the Xfixes code wasn't enabled due to a bug in the
// build system (freedesktop bug #975).
#if HAVE_XFIXES && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105
if (!theme) {
return false;
}
QByteArray themeName = QFile::encodeName(theme->name());
// Set up the proper launch environment for newly started apps
UpdateLaunchEnvJob launchEnvJob(QStringLiteral("XCURSOR_THEME"), themeName);
// Update the Xcursor X resources
runRdb(0);
// Reload the standard cursors
QStringList names;
if (CursorTheme::haveXfixes()) {
// Qt cursors
names << "left_ptr"
<< "up_arrow"
<< "cross"
<< "wait"
<< "left_ptr_watch"
<< "ibeam"
<< "size_ver"
<< "size_hor"
<< "size_bdiag"
<< "size_fdiag"
<< "size_all"
<< "split_v"
<< "split_h"
<< "pointing_hand"
<< "openhand"
<< "closedhand"
<< "forbidden"
<< "whats_this"
<< "copy"
<< "move"
<< "link";
// X core cursors
names << "X_cursor"
<< "right_ptr"
<< "hand1"
<< "hand2"
<< "watch"
<< "xterm"