Commit 6eb2b529 authored by Tanbir Jishan's avatar Tanbir Jishan 💬 Committed by Nate Graham
Browse files

kcms/colors: Add an option to apply accent color from wallpaper

This adds an option to set the accent color from the current wallpaper.

The way it works is that it exposes some DBus calls; if a wallpaper plugin
supports wallpaper accent colors then it should say so by this DBus call
when starting up and also should say which wallpaper is being currently
used (for the purpose of extracting an accent color from it) or set an
accent color itself. After that it should keep declaring the wallpaper or
set accent color whenever wallpaper changes or whenever the plugin author
thinks it is appropriate to say so.

There is already an implementation of the color extracting algorithm which
the plugin can either use, or else set whatever accent color it wants. The
necessary DBus calls for the official image and wallpaper plugin are
implemented, so they should work out of the box.

BUG: 444676
FIXED-IN: 5.25
parent 94b9a03c
Pipeline #166912 passed with stage
in 16 minutes and 11 seconds
......@@ -9,6 +9,30 @@ set(kcm_colors_SRCS
filterproxymodel.cpp
)
qt5_generate_dbus_interface(
accentColorService/accentColorService.h
org.kde.plasmashell.accentColor.xml
OPTIONS -M
)
set(plasma-accentcolor-service_SRCS
accentColorService/accentColorService.cpp
colorsapplicator.cpp
colorsmodel.cpp
../kcms-common.cpp
../krdb/krdb.cpp
)
set(plasma-accentcolor-service_SRCS
${plasma-accentcolor-service_SRCS}
${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.accentColor.xml
)
qt5_add_dbus_adaptor(plasma-accentcolor-service_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.plasmashell.accentColor.xml
accentColorService/accentColorService.h AccentColorService
accentcolor_service_adaptor AccentColorServiceAdaptor)
kcmutils_generate_module_data(
kcm_colors_SRCS
MODULE_DATA_HEADER colorsdata.h
......@@ -54,8 +78,10 @@ set(plasma-apply-colorscheme_SRCS
)
kconfig_add_kcfg_files(plasma-apply-colorscheme_SRCS colorssettings.kcfgc GENERATE_MOC)
kconfig_add_kcfg_files(plasma-accentcolor-service_SRCS colorssettings.kcfgc GENERATE_MOC)
add_executable(plasma-apply-colorscheme ${plasma-apply-colorscheme_SRCS})
kcoreaddons_add_plugin(plasma-accentcolor-service SOURCES ${plasma-accentcolor-service_SRCS} INSTALL_NAMESPACE "kf${QT_MAJOR_VERSION}/kded")
target_link_libraries(plasma-apply-colorscheme
Qt::Core
......@@ -68,12 +94,33 @@ target_link_libraries(plasma-apply-colorscheme
PW::KWorkspace
X11::X11
)
target_link_libraries(plasma-accentcolor-service
Qt::Core
Qt::DBus
KF5::GuiAddons
KF5::KCMUtils
KF5::I18n
KF5::WindowSystem
KF5::ConfigCore
KF5::DBusAddons
PW::KWorkspace
X11::X11
)
if (QT_MAJOR_VERSION EQUAL "5")
target_link_libraries(plasma-apply-colorscheme Qt::X11Extras)
else()
target_link_libraries(plasma-apply-colorscheme Qt::GuiPrivate)
endif()
if (QT_MAJOR_VERSION EQUAL "5")
target_link_libraries(plasma-accentcolor-service Qt::X11Extras)
else()
target_link_libraries(plasma-accentcolor-service Qt::GuiPrivate)
endif()
install(FILES colorssettings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
install(FILES kcm_colors.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(TARGETS plasma-apply-colorscheme DESTINATION ${KDE_INSTALL_BINDIR})
......
#include <KPluginFactory>
#include <QDBusConnection>
#include "../../kcms-common_p.h"
#include "accentColorService.h"
#include "accentcolor_service_adaptor.h"
#include "colorsapplicator.h"
K_PLUGIN_CLASS_WITH_JSON(AccentColorService, "accentColorService.json")
AccentColorService::AccentColorService(QObject *parent, const QList<QVariant> &)
: KDEDModule(parent)
, m_settings(new ColorsSettings(this))
, m_model(new ColorsModel(this))
{
new AccentColorServiceAdaptor(this);
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.registerObject("/AccentColor", this);
dbus.registerService("org.kde.plasmashell.accentColor");
}
void AccentColorService::setAccentColor(const QString &accentColor)
{
m_settings->load();
if (QColor::isValidColor(accentColor) && m_settings->accentColorFromWallpaper()) {
m_model->load();
m_model->setSelectedScheme(m_settings->colorScheme());
const QString path =
QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes/%1.colors").arg(m_model->selectedScheme()));
auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
QStringLiteral("/org/kde/KWin/BlendChanges"),
QStringLiteral("org.kde.KWin.BlendChanges"),
QStringLiteral("start"));
msg << 300;
// This is deliberately blocking so that we ensure Kwin has processed the
// animation start event before we potentially trigger client side changes
QDBusConnection::sessionBus().call(msg);
m_settings->setAccentColor(accentColor);
m_settings->save();
applyScheme(path, m_settings->config(), KConfig::Notify);
notifyKcmChange(GlobalChangeType::PaletteChanged);
}
}
#include "accentColorService.moc"
#pragma once
#include "colorsmodel.h"
#include "colorssettings.h"
#include <kdedmodule.h>
class AccentColorService : public KDEDModule
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.plasmashell.accentColor")
public:
explicit AccentColorService(QObject *parent, const QList<QVariant> &);
public Q_SLOTS:
void setAccentColor(const QString &color);
private:
ColorsSettings *m_settings;
ColorsModel *m_model;
};
{
"KPlugin": {
"Description": "Applies extracted accent color from wallpaper on dbus request",
"Name": "accentColorService"
},
"X-KDE-FactoryName": "accentColorService",
"X-KDE-Kded-autoload": true
}
......@@ -13,6 +13,8 @@
#include <QColor>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusReply>
#include <QFileInfo>
#include <QGuiApplication>
#include <QProcess>
......@@ -55,6 +57,7 @@ KCMColors::KCMColors(QObject *parent, const KPluginMetaData &data, const QVarian
, m_filteredModel(new FilterProxyModel(this))
, m_data(new ColorsData(this))
, m_config(KSharedConfig::openConfig(QStringLiteral("kdeglobals")))
, m_configWatcher(KConfigWatcher::create(m_config))
{
auto uri = "org.kde.private.kcms.colors";
qmlRegisterUncreatableType<KCMColors>(uri, 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM"));
......@@ -77,6 +80,14 @@ KCMColors::KCMColors(QObject *parent, const KPluginMetaData &data, const QVarian
connect(m_model, &ColorsModel::selectedSchemeChanged, m_filteredModel, &FilterProxyModel::setSelectedScheme);
m_filteredModel->setSourceModel(m_model);
// Since the accent color can now change from somewhere else, we need to update the view accordingly.
connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
if (group.name() == QLatin1String("General") && names.contains(QByteArrayLiteral("AccentColor"))) {
colorsSettings()->save(); // We need to first save the local changes, if any.
colorsSettings()->load();
}
});
}
KCMColors::~KCMColors()
......@@ -123,6 +134,16 @@ void KCMColors::setAccentColor(const QColor &accentColor)
Q_EMIT settingsChanged();
}
bool KCMColors::applyAccentColorFromWallpaper() const
{
return colorsSettings()->accentColorFromWallpaper();
}
void KCMColors::setApplyAccentColorFromWallpaper(bool boolean)
{
colorsSettings()->setAccentColorFromWallpaper(boolean);
Q_EMIT settingsChanged();
}
bool KCMColors::downloadingFile() const
{
return m_tempCopyJob;
......@@ -380,6 +401,7 @@ void KCMColors::saveColors()
auto setGlobals = [=]() {
globalConfig->group("General").writeEntry("AccentColor", QColor());
globalConfig->group("General").writeEntry("accentColorFromWallpaper", applyAccentColorFromWallpaper(), KConfig::Notify);
if (accentColor() != QColor(Qt::transparent)) {
globalConfig->group("General").writeEntry("AccentColor", accentColor(), KConfig::Notify);
} else {
......@@ -403,6 +425,25 @@ QColor KCMColors::accentForeground(const QColor &accent, const bool &isActive)
return ::accentForeground(accent, isActive);
}
void KCMColors::applyWallpaperAccentColor()
{
QDBusMessage accentColor = QDBusMessage::createMethodCall("org.kde.plasmashell", "/PlasmaShell", "org.kde.PlasmaShell", "color");
auto const connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, "accentColorBus");
QDBusPendingCall async = connection.asyncCall(accentColor);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(wallpaperAccentColorArrivedSlot(QDBusPendingCallWatcher *)));
}
void KCMColors::wallpaperAccentColorArrivedSlot(QDBusPendingCallWatcher *call)
{
QDBusPendingReply<QString> reply = *call;
if (!reply.isError()) {
setAccentColor(QColor(reply.value()));
}
call->deleteLater();
}
void KCMColors::processPendingDeletions()
{
const QStringList pendingDeletions = m_model->pendingDeletions();
......
......@@ -7,8 +7,10 @@
#pragma once
#include <KConfigWatcher>
#include <KNSCore/EntryWrapper>
#include <QColor>
#include <QDBusPendingCallWatcher>
#include <QPointer>
#include <QScopedPointer>
......@@ -40,8 +42,9 @@ class KCMColors : public KQuickAddons::ManagedConfigModule
Q_PROPERTY(FilterProxyModel *filteredModel READ filteredModel CONSTANT)
Q_PROPERTY(ColorsSettings *colorsSettings READ colorsSettings CONSTANT)
Q_PROPERTY(bool downloadingFile READ downloadingFile NOTIFY downloadingFileChanged)
Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged)
Q_PROPERTY(bool applyAccentColorFromWallpaper READ applyAccentColorFromWallpaper WRITE setApplyAccentColorFromWallpaper NOTIFY
applyAccentColorFromWallpaperChanged)
public:
KCMColors(QObject *parent, const KPluginMetaData &data, const QVariantList &args);
......@@ -66,6 +69,10 @@ public:
void setAccentColor(const QColor &accentColor);
void resetAccentColor();
Q_SIGNAL void accentColorChanged();
Q_SIGNAL void applyAccentColorFromWallpaperChanged();
bool applyAccentColorFromWallpaper() const;
void setApplyAccentColorFromWallpaper(bool boolean);
Q_INVOKABLE void installSchemeFromFile(const QUrl &url);
......@@ -76,11 +83,15 @@ public:
Q_INVOKABLE QColor tinted(const QColor& color, const QColor& accent, bool tints, qreal tintFactor);
Q_INVOKABLE QColor accentBackground(const QColor &accent, const QColor &background);
Q_INVOKABLE QColor accentForeground(const QColor &accent, const bool &isActive);
Q_INVOKABLE void applyWallpaperAccentColor();
public Q_SLOTS:
void load() override;
void save() override;
private Q_SLOTS:
void wallpaperAccentColorArrivedSlot(QDBusPendingCallWatcher *call);
Q_SIGNALS:
void downloadingFileChanged();
......@@ -107,6 +118,7 @@ private:
QProcess *m_editDialogProcess = nullptr;
KSharedConfigPtr m_config;
KConfigWatcher::Ptr m_configWatcher;
QScopedPointer<QTemporaryFile> m_tempInstallFile;
QPointer<KIO::FileCopyJob> m_tempCopyJob;
......
......@@ -13,5 +13,9 @@
<label>Accent color</label>
<default>transparent</default>
</entry>
<entry name="accentColorFromWallpaper" key="accentColorFromWallpaper" type="Bool">
<label>Whether accent color from wallpaper should be applied</label>
<default>false</default>
</entry>
</group>
</kcfg>
......@@ -4,4 +4,4 @@ Mutators=true
DefaultValueGetters=true
GenerateProperties=true
ParentInConstructor=true
Notifiers=colorScheme
Notifiers=colorScheme, accentColor
......@@ -39,6 +39,13 @@ KCM.GridViewKCM {
restoreMode: Binding.RestoreBinding
}
Binding {
target: kcm
property: "applyAccentColorFromWallpaper"
value: wallpaperAccentBox.checked
restoreMode: Binding.RestoreBinding
}
KCM.SettingStateBinding {
configObject: kcm.colorsSettings
settingName: "colorScheme"
......@@ -134,7 +141,7 @@ KCM.GridViewKCM {
Layout.fillWidth: true
QtControls.ButtonGroup {
buttons: [notAccentBox, accentBox]
buttons: [notAccentBox, wallpaperAccentBox, accentBox]
}
QtControls.RadioButton {
......@@ -142,7 +149,7 @@ KCM.GridViewKCM {
Kirigami.FormData.label: i18n("Use accent color:")
text: i18n("From current color scheme")
checked: Qt.colorEqual(kcm.accentColor, "transparent")
checked: Qt.colorEqual(kcm.accentColor, "transparent") && !kcm.applyAccentColorFromWallpaper
onToggled: {
if (enabled) {
......@@ -150,18 +157,45 @@ KCM.GridViewKCM {
}
}
}
QtControls.RadioButton {
id: wallpaperAccentBox
text: i18nc("@option:radio wallpaper accent color option", "From current wallpaper")
checked: kcm.applyAccentColorFromWallpaper
onToggled: {
if (enabled) {
kcm.applyWallpaperAccentColor();
}
}
}
RowLayout {
spacing: 0
QtControls.RadioButton {
id: accentBox
checked: !Qt.colorEqual(kcm.accentColor, "transparent")
checked: !Qt.colorEqual(kcm.accentColor, "transparent") && !kcm.applyAccentColorFromWallpaper
onToggled: {
if (enabled) {
kcm.accentColor = colorRepeater.model[0]
}
}
/* This required when someone directly clicks one of the accent color buttons or the color picker instead
* of first checking this button. Failing to call this function in those situations will leave this button unchecked
* which was the previous behaviour.
* NOTE: Currently user is able to dicrectly toggle the accentColor buttons or the color picker. If in future the situation
* becomes such that the user must toggle this button first then just remove all the occurrences of this function
*/
function restartBindingWithCheckedAsTrue() {
if(!accentBox.checked) {
accentBox.checked = true
accentBox.checked = Qt.binding(() => !Qt.colorEqual(kcm.accentColor, "transparent") && !kcm.applyAccentColorFromWallpaper)
}
}
}
RowLayout {
spacing: accentBox.spacing
component ColorRadioButton : T.RadioButton {
......@@ -198,7 +232,7 @@ KCM.GridViewKCM {
MouseArea {
enabled: false
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
cursorShape: accentBox.checked ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
......@@ -223,9 +257,10 @@ KCM.GridViewKCM {
checked: Qt.colorEqual(kcm.accentColor, modelData)
onToggled: {
accentBox.restartBindingWithCheckedAsTrue()
kcm.accentColor = modelData
checked = Qt.binding(() => Qt.colorEqual(kcm.accentColor, modelData));
}
}
}
}
......@@ -243,6 +278,7 @@ KCM.GridViewKCM {
modality: Qt.ApplicationModal
color: kcm.accentColor
onAccepted: {
accentBox.restartBindingWithCheckedAsTrue()
kcm.accentColor = colorDialog.color
}
}
......@@ -254,7 +290,7 @@ KCM.GridViewKCM {
!colorRepeater.model.some(color => Qt.colorEqual(color, root.accentColor))
: false
/* The qt binding will keep the binding alive as well as uncheck the button
/* The qt binding will keep the binding alive as well as uncheck the button
* we can't just disable the button because then the icon will become grey
* and also we have to provide a MouseArea for interaction. Both of these
* can be done with the button being disabled but it will become very
......@@ -265,7 +301,7 @@ KCM.GridViewKCM {
colorDialog.open()
}
color: isCustomColor ? kcm.accentColor : "transparent"
color: isCustomColor && accentBox.checked ? kcm.accentColor : "transparent"
checked: isCustomColor
onClicked: openColorDialog()
......
......@@ -57,6 +57,8 @@ if (TARGET KUserFeedbackCore)
)
endif()
kconfig_add_kcfg_files(plasma_shell_SRCS ../kcms/colors/colorssettings.kcfgc GENERATE_MOC)
add_executable(plasmashell)
if (QT_MAJOR_VERSION EQUAL "5")
ecm_add_qtwayland_client_protocol(plasma_shell_SRCS
......@@ -96,6 +98,7 @@ target_link_libraries(plasmashell
KF5::Package
KF5::WaylandClient
KF5::Notifications
KF5::GuiAddons
PW::KWorkspace
Wayland::Client
)
......
......@@ -15,6 +15,12 @@
<arg name="script" type="s" direction="in"/>
<arg name="output" type="s" direction="out"/>
</method>
<method name="color">
<arg name="output" type="s" direction="out"/>
</method>
<signal name="colorChanged">
<arg name="changedColor" type="s" direction="out"/>
</signal>
<method name="dumpCurrentLayoutJS">
<arg name="script" type="ay" direction="out"/>
</method>
......
......@@ -9,6 +9,8 @@
#include "krunner_interface.h"
#include "shellcorona.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
......@@ -34,6 +36,7 @@
DesktopView::DesktopView(Plasma::Corona *corona, QScreen *targetScreen)
: PlasmaQuick::ContainmentView(corona, nullptr)
, m_accentColor("transparent")
, m_windowType(Desktop)
, m_shellSurface(nullptr)
{
......@@ -55,6 +58,7 @@ DesktopView::DesktopView(Plasma::Corona *corona, QScreen *targetScreen)
setSource(corona->kPackage().fileUrl("views", QStringLiteral("Desktop.qml")));
connect(this, &QWindow::screenChanged, this, &DesktopView::adaptToScreen);
connect(this, &DesktopView::accentColorChanged, this, &DesktopView::setAccentColorFromWallpaper);
QObject::connect(corona, &Plasma::Corona::kPackageChanged, this, &DesktopView::coronaPackageChanged);
......@@ -125,6 +129,19 @@ void DesktopView::adaptToScreen()
m_oldScreen = m_screenToFollow;
}
QString DesktopView::accentColor() const
{
return m_accentColor;
}
void DesktopView::setAccentColor(const QString &accentColor)
{
if (accentColor != m_accentColor) {
m_accentColor = accentColor;
Q_EMIT accentColorChanged(m_accentColor);
}
}
DesktopView::WindowType DesktopView::windowType() const
{
return m_windowType;
......@@ -372,3 +389,14 @@ void DesktopView::setupWaylandIntegration()
m_shellSurface->setPosition(m_screenToFollow->geometry().topLeft());
}
}
void DesktopView::setAccentColorFromWallpaper(const QString &accentColor)
{
auto const notPrimaryDisplay = containment()->screen() != 0;
if (notPrimaryDisplay) {
return;
}
QDBusMessage applyAccentColor = QDBusMessage::createMethodCall("org.kde.plasmashell.accentColor", "/AccentColor", "", "setAccentColor");
applyAccentColor << accentColor;
QDBusConnection::sessionBus().send(applyAccentColor);
}
......@@ -30,6 +30,7 @@ class DesktopView : public PlasmaQuick::ContainmentView
Q_PROPERTY(SessionType sessionType READ sessionType CONSTANT)
Q_PROPERTY(QVariantMap candidateContainments READ candidateContainmentsGraphicItems NOTIFY candidateContainmentsChanged)
Q_PROPERTY(QString accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged)
public:
enum WindowType {
......@@ -57,6 +58,8 @@ public:
void adaptToScreen();
void showEvent(QShowEvent *) override;
QString accentColor() const;
void setAccentColor(const QString &);
WindowType windowType() const;
void setWindowType(WindowType type);
......@@ -85,13 +88,16 @@ Q_SIGNALS:
void windowTypeChanged();
void candidateContainmentsChanged();
void geometryChanged();
void accentColorChanged(const QString &accentColor);
private:
void coronaPackageChanged(const KPackage::Package &package);
void ensureWindowType();
void setupWaylandIntegration();
void setAccentColorFromWallpaper(const QString &accentColor);
bool handleKRunnerTextInput(QKeyEvent *e);
QString m_accentColor;
QPointer<PlasmaQuick::ConfigView> m_configView;
QPointer<QScreen> m_oldScreen;
QPointer<QScreen> m_screenToFollow;
......
......@@ -739,6 +739,7 @@ void ShellCorona::load()
connect(m_screenPool, &ScreenPool::screenAdded, this, &ShellCorona::addOutput, Qt::UniqueConnection);
connect(m_screenPool, &ScreenPool::screenRemoved, this, &ShellCorona::handleScreenRemoved, Qt::UniqueConnection);
connect(m_screenPool, &ScreenPool::primaryScreenChanged, this, &ShellCorona::primaryScreenChanged, Qt::UniqueConnection);
connect(m_desktopViewForScreen[m_screenPool->primaryScreen()], &DesktopView::accentColorChanged, this, &ShellCorona::colorChanged);
if (!m_waitingPanels.isEmpty()) {
m_waitingPanelsTimer.start();
......@@ -782,6 +783,9 @@ void ShellCorona::primaryScreenChanged(QScreen *oldPrimary, QScreen *newPrimary)
// can't do the screen invariant here as reconsideroutputs wasn't executed yet
// CHECK_SCREEN_INVARIANTS
// refresh the accent color signal binding
connect(m_desktopViewForScreen[m_screenPool->primaryScreen()], &DesktopView::accentColorChanged, this, &ShellCorona::colorChanged);
}
#ifndef NDEBUG
......@@ -1419,6 +1423,15 @@ void ShellCorona::toggleDashboard()
setDashboardShown(!KWindowSystem::showingDesktop());
}
QString ShellCorona::color() const
{
auto const primaryDesktopViewExists = m_desktopViewForScreen.contains(m_screenPool->primaryScreen());
if (primaryDesktopViewExists) {
return m_desktopViewForScreen[m_screenPool->primaryScreen()]->accentColor();
}
return QStringLiteral("#00FFFFFF");
}
QString ShellCorona::evaluateScript(const QString &script)
{
if (calledFromDBus()) {
......
......@@ -129,6 +129,7 @@ Q_SIGNALS:
void glInitializationFailed();
// A preview for this containment has been rendered and saved to disk
void containmentPreviewReady(Plasma::Containment *containment, const QString &path);
void colorChanged(const QString &color);
public Q_SLOTS:
/**
......@@ -153,6 +154,7 @@ public Q_SLOTS:
void toggleWidgetExplorer();
QString evaluateScript(const QString &string);
void activateLauncherMenu();
QString color() const;
QByteArray dumpCurrentLayoutJS() const;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment