Commit a18bc820 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

Add applet for controlling Night Color

Summary:
The new applet allows to temporarily disable Night Color, for example if
one wants to edit a video or an image.

BUG: 400418

Reviewers: #plasma, #vdg, ngraham, davidedmundson

Reviewed By: #plasma, #vdg, ngraham, davidedmundson

Subscribers: davidedmundson, ngraham, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D25947
parent 7ed37a8a
......@@ -16,6 +16,7 @@ add_subdirectory(fifteenPuzzle)
add_subdirectory(fuzzy-clock)
add_subdirectory(grouping)
add_subdirectory(mediaframe)
add_subdirectory(nightcolor)
add_subdirectory(notes)
add_subdirectory(quicklaunch)
add_subdirectory(quickshare)
......
add_subdirectory(plugin)
plasma_install_package(package org.kde.plasma.nightcolorcontrol)
#! /usr/bin/env bash
$XGETTEXT `find . -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.nightcolorcontrol.pot
/*
* Copyright 2019 Vlad Zahorodnii <vladzzag@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.5
import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.private.nightcolorcontrol 1.0
Item {
id: root
Plasmoid.icon: monitor.running ? 'redshift-status-on' : 'redshift-status-off'
Plasmoid.status: {
// Display the applet in the system tray when Night Color is active.
if (monitor.enabled && monitor.targetTemperature != 6500) {
return PlasmaCore.Types.ActiveStatus;
}
if (inhibitor.state != Inhibitor.Uninhibited) {
return PlasmaCore.Types.ActiveStatus;
}
return PlasmaCore.Types.PassiveStatus;
}
Plasmoid.toolTipMainText: i18n('Night Color Control')
Plasmoid.toolTipSubText: {
if (inhibitor.state == Inhibitor.Inhibited) {
return i18n("Night Color is inhibited");
}
if (!monitor.available) {
return i18n("Night Color is unavailable");
}
if (!monitor.enabled) {
return i18n("Night Color is disabled");
}
if (!monitor.running) {
return i18n("Night Color is not running");
}
return i18n('Night Color is active (%1K)', monitor.currentTemperature);
}
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation
Plasmoid.compactRepresentation: PlasmaCore.IconItem {
active: compactMouseArea.containsMouse
source: plasmoid.icon
MouseArea {
id: compactMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: toggleInhibition()
}
}
Plasmoid.onActivated: toggleInhibition()
function toggleInhibition() {
if (!monitor.available) {
return;
}
switch (inhibitor.state) {
case Inhibitor.Inhibiting:
case Inhibitor.Inhibited:
inhibitor.uninhibit();
break;
case Inhibitor.Uninhibiting:
case Inhibitor.Uninhibited:
inhibitor.inhibit();
break;
}
}
function action_KCMNightColor() {
KCMShell.open("kcm_nightcolor");
}
Inhibitor {
id: inhibitor
}
Monitor {
id: monitor
}
Component.onCompleted: {
if (KCMShell.authorize("kcm_nightcolor.desktop").length > 0) {
plasmoid.setAction("KCMNightColor", i18n("Configure Night Color..."), "configure");
}
plasmoid.removeAction("configure");
}
}
[Desktop Entry]
Name=Night Color Control
Comment=Plasmoid for controlling Night Color
Icon=preferences-desktop-display-nightcolor
Type=Service
X-KDE-ServiceTypes=Plasma/Applet
X-KDE-PluginInfo-Author=Vlad Zahorodnii
X-KDE-PluginInfo-Category=Graphics
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-Email=vladzzag@gmail.com
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-PluginInfo-License=GPL-2.0+
X-KDE-PluginInfo-Name=org.kde.plasma.nightcolorcontrol
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=plasma.kde.org
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/main.qml
X-Plasma-NotificationArea=true
X-Plasma-NotificationAreaCategory=SystemServices
set(nightcolorcontrolplugin_SOURCES
inhibitor.cpp
monitor.cpp
plugin.cpp
)
add_library(nightcolorcontrolplugin SHARED ${nightcolorcontrolplugin_SOURCES})
target_link_libraries(nightcolorcontrolplugin
Qt5::Core
Qt5::DBus
Qt5::Gui
Qt5::Qml
)
install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/nightcolorcontrol)
install(TARGETS nightcolorcontrolplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/nightcolorcontrol)
/*
* Copyright 2019 Vlad Zahorodnii <vladzzag@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "inhibitor.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QLoggingCategory>
Q_LOGGING_CATEGORY(NIGHTCOLOR_CONTROL, "org.kde.plasma.nightcolorcontrol")
static const QString s_serviceName = QStringLiteral("org.kde.KWin");
static const QString s_path = QStringLiteral("/ColorCorrect");
static const QString s_interface = QStringLiteral("org.kde.kwin.ColorCorrect");
class Inhibitor::Private
{
public:
uint cookie = 0;
State state = Uninhibited;
bool pendingUninhibit = false;
};
Inhibitor::Inhibitor(QObject *parent)
: QObject(parent)
, d(new Private)
{
}
Inhibitor::~Inhibitor()
{
uninhibit();
}
Inhibitor::State Inhibitor::state() const
{
return d->state;
}
void Inhibitor::inhibit()
{
if (d->state == Inhibited) {
return;
}
d->pendingUninhibit = false;
if (d->state == Inhibiting) {
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName,
s_path,
s_interface,
QStringLiteral("inhibit"));
QDBusPendingReply<uint> cookie = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(cookie, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) {
const bool wasPendingUninhibit = d->pendingUninhibit;
d->pendingUninhibit = false;
const QDBusPendingReply<uint> reply = *self;
self->deleteLater();
if (reply.isError()) {
qCWarning(NIGHTCOLOR_CONTROL()) << "Could not inhibit Night Color:" << reply.error().message();
d->state = Uninhibited;
emit stateChanged();
return;
}
d->cookie = reply.value();
d->state = Inhibited;
emit stateChanged();
if (wasPendingUninhibit) {
uninhibit();
}
});
d->state = Inhibiting;
emit stateChanged();
}
void Inhibitor::uninhibit()
{
if (d->state == Uninhibiting || d->state == Uninhibited) {
return;
}
if (d->state == Inhibiting) {
d->pendingUninhibit = true;
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName,
s_path,
s_interface,
QStringLiteral("uninhibit"));
message.setArguments({ d->cookie });
QDBusPendingReply<void> reply = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) {
self->deleteLater();
if (d->state != Uninhibiting) {
return;
}
const QDBusPendingReply<void> reply = *self;
if (reply.isError()) {
qCWarning(NIGHTCOLOR_CONTROL) << "Could not uninhibit Night Color:" << reply.error().message();
}
d->state = Uninhibited;
emit stateChanged();
});
d->state = Uninhibiting;
emit stateChanged();
}
/*
* Copyright 2019 Vlad Zahorodnii <vladzzag@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
/**
* The Inhibitor class provides a convenient way to temporarily disable Night Color.
*/
class Inhibitor : public QObject
{
Q_OBJECT
/**
* This property holds a value to indicate the current state of the inhibitor.
*/
Q_PROPERTY(State state READ state NOTIFY stateChanged)
public:
explicit Inhibitor(QObject *parent = nullptr);
~Inhibitor() override;
/**
* This enum type is used to specify the state of the inhibitor.
*/
enum State {
Inhibiting, ///< Night Color is being inhibited.
Inhibited, ///< Night Color is inhibited.
Uninhibiting, ///< Night Color is being uninhibited.
Uninhibited, ///< Night Color is uninhibited.
};
Q_ENUM(State)
/**
* Returns the current state of the inhibitor.
*/
State state() const;
public Q_SLOTS:
/**
* Attempts to temporarily disable Night Color.
*
* After calling this method, the inhibitor will enter the Inhibiting state.
* Eventually, the inhibitor will enter the Inhibited state when the inhibition
* request has been processed successfully by the Night Color manager.
*
* This method does nothing if the inhibitor is in the Inhibited state.
*/
void inhibit();
/**
* Attempts to undo the previous call to inhibit() method.
*
* After calling this method, the inhibitor will enter the Uninhibiting state.
* Eventually, the inhibitor will enter the Uninhibited state when the uninhibition
* request has been processed by the Night Color manager.
*
* This method does nothing if the inhibitor is in the Uninhibited state.
*/
void uninhibit();
Q_SIGNALS:
/**
* Emitted whenever the state of the inhibitor has changed.
*/
void stateChanged();
private:
class Private;
QScopedPointer<Private> d;
};
/*
* Copyright 2019 Vlad Zahorodnii <vladzzag@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "monitor.h"
#include "monitor_p.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusServiceWatcher>
static const QString s_serviceName = QStringLiteral("org.kde.KWin");
static const QString s_nightColorPath = QStringLiteral("/ColorCorrect");
static const QString s_nightColorInterface = QStringLiteral("org.kde.kwin.ColorCorrect");
static const QString s_propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
MonitorPrivate::MonitorPrivate(QObject *parent)
: QObject(parent)
{
QDBusServiceWatcher *watcher = new QDBusServiceWatcher(s_serviceName,
QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForOwnerChange,
this);
connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &MonitorPrivate::handleServiceRegistered);
connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &MonitorPrivate::handleServiceUnregistered);
handleServiceRegistered();
}
MonitorPrivate::~MonitorPrivate()
{
}
void MonitorPrivate::handleServiceRegistered()
{
QDBusConnection bus = QDBusConnection::sessionBus();
const bool connected = bus.connect(s_serviceName, s_nightColorPath, s_propertiesInterface,
QStringLiteral("PropertiesChanged"),
this, SLOT(handlePropertiesChanged(QString,QVariantMap,QStringList)));
if (!connected) {
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(s_serviceName,
s_nightColorPath,
s_propertiesInterface,
QStringLiteral("GetAll"));
message.setArguments({ s_nightColorInterface });
QDBusPendingReply<QVariantMap> properties = bus.asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(properties, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) {
self->deleteLater();
const QDBusPendingReply<QVariantMap> properties = *self;
if (properties.isError()) {
return;
}
updateProperties(properties.value());
});
}
void MonitorPrivate::handleServiceUnregistered()
{
QDBusConnection bus = QDBusConnection::sessionBus();
bus.disconnect(s_serviceName, s_nightColorPath, s_propertiesInterface,
QStringLiteral("PropertiesChanged"),
this, SLOT(handlePropertiesChanged(QString,QVariantMap,QStringList)));
setAvailable(false);
}
void MonitorPrivate::handlePropertiesChanged(const QString &interfaceName,
const QVariantMap &changedProperties,
const QStringList &invalidatedProperties)
{
Q_UNUSED(interfaceName)
Q_UNUSED(invalidatedProperties)
updateProperties(changedProperties);
}
int MonitorPrivate::currentTemperature() const
{
return m_currentTemperature;
}
int MonitorPrivate::targetTemperature() const
{
return m_targetTemperature;
}
void MonitorPrivate::updateProperties(const QVariantMap &properties)
{
const QVariant available = properties.value(QStringLiteral("available"));
if (available.isValid()) {
setAvailable(available.toBool());
}
const QVariant enabled = properties.value(QStringLiteral("enabled"));
if (enabled.isValid()) {
setEnabled(enabled.toBool());
}
const QVariant running = properties.value(QStringLiteral("running"));
if (running.isValid()) {
setRunning(running.toBool());
}
const QVariant currentTemperature = properties.value(QStringLiteral("currentTemperature"));
if (currentTemperature.isValid()) {
setCurrentTemperature(currentTemperature.toInt());
}
const QVariant targetTemperature = properties.value(QStringLiteral("targetTemperature"));
if (targetTemperature.isValid()) {
setTargetTemperature(targetTemperature.toInt());
}
}
void MonitorPrivate::setCurrentTemperature(int temperature)
{
if (m_currentTemperature == temperature) {
return;
}
m_currentTemperature = temperature;
emit currentTemperatureChanged();
}
void MonitorPrivate::setTargetTemperature(int temperature)
{
if (m_targetTemperature == temperature) {
return;
}
m_targetTemperature = temperature;
emit targetTemperatureChanged();
}
bool MonitorPrivate::isAvailable() const
{
return m_isAvailable;
}
void MonitorPrivate::setAvailable(bool available)
{
if (m_isAvailable == available) {
return;
}
m_isAvailable = available;
emit availableChanged();
}
bool MonitorPrivate::isEnabled() const
{
return m_isEnabled;
}
void MonitorPrivate::setEnabled(bool enabled)
{
if (m_isEnabled == enabled) {
return;
}
m_isEnabled = enabled;
emit enabledChanged();
}
bool MonitorPrivate::isRunning() const
{
return m_isRunning;
}
void MonitorPrivate::setRunning(bool running)
{
if (m_isRunning == running) {
return;
}
m_isRunning = running;
emit runningChanged();
}
Monitor::Monitor(QObject *parent)
: QObject(parent)
, d(new MonitorPrivate(this))
{
connect(d, &MonitorPrivate::availableChanged, this, &Monitor::availableChanged);
connect(d, &MonitorPrivate::enabledChanged, this, &Monitor::enabledChanged);
connect(d, &MonitorPrivate::runningChanged, this, &Monitor::runningChanged);
connect(d, &MonitorPrivate::currentTemperatureChanged, this, &Monitor::currentTemperatureChanged);