diff --git a/containments/panel/CMakeLists.txt b/containments/panel/CMakeLists.txt index 49cb9c9..13e6417 100644 --- a/containments/panel/CMakeLists.txt +++ b/containments/panel/CMakeLists.txt @@ -1,25 +1,26 @@ -qt5_add_dbus_interfaces(DBUS_SRCS dbus/org.kde.KWin.Screenshot.xml) +qt5_add_dbus_interfaces(DBUS_SRCS dbus/org.kde.KWin.Screenshot.xml + dbus/org.kde.KScreen.xml) set(phonepanel_SRCS phonepanel.cpp ${DBUS_SRCS} ) add_library(plasma_applet_phonepanel MODULE ${phonepanel_SRCS}) kcoreaddons_desktop_to_json(plasma_applet_phonepanel package/metadata.desktop) target_link_libraries(plasma_applet_phonepanel Qt5::Gui Qt5::DBus KF5::Plasma KF5::I18n ${GSTREAMER_LIBRARIES} GLIB2::GLIB2 ${GOBJECT_LIBRARIES}) target_include_directories(plasma_applet_phonepanel PRIVATE "${GSTREAMER_INCLUDE_DIR}") install(TARGETS plasma_applet_phonepanel DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) #install(FILES plasma-phonepanel-default.desktop DESTINATION ${SERVICES_INSTALL_DIR}) plasma_install_package(package org.kde.phone.panel) diff --git a/containments/panel/dbus/org.kde.KScreen.xml b/containments/panel/dbus/org.kde.KScreen.xml new file mode 100644 index 0000000..5c6f3a8 --- /dev/null +++ b/containments/panel/dbus/org.kde.KScreen.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/containments/panel/package/contents/ui/quicksettings/QuickSettings.qml b/containments/panel/package/contents/ui/quicksettings/QuickSettings.qml index c0fdccf..1e0d79a 100644 --- a/containments/panel/package/contents/ui/quicksettings/QuickSettings.qml +++ b/containments/panel/package/contents/ui/quicksettings/QuickSettings.qml @@ -1,261 +1,275 @@ /* * Copyright 2015 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.networkmanagement 0.2 as PlasmaNM Item { id: root implicitWidth: flow.implicitWidth + units.smallSpacing * 6 implicitHeight: flow.implicitHeight + units.smallSpacing * 6 signal closeRequested signal closed property bool screenshotRequested: false PlasmaNM.Handler { id: nmHandler } PlasmaNM.EnabledConnections { id: enabledConnections } function toggleAirplane() { print("toggle airplane mode") } function toggleTorch() { plasmoid.nativeInterface.toggleTorch() } function toggleWifi() { nmHandler.enableWireless(!enabledConnections.wirelessEnabled) settingsModel.get(1).enabled = !enabledConnections.wirelessEnabled } function toggleWwan() { nmHandler.enableWwan(!enabledConnections.wwanEnabled) settingsModel.get(2).enabled = !enabledConnections.wwanEnabled } + function toggleRotation() { + const enable = !plasmoid.nativeInterface.autoRotateEnabled + plasmoid.nativeInterface.autoRotateEnabled = enable + settingsModel.get(8).enabled = enable + } + function requestShutdown() { print("Shutdown requested, depends on ksmserver running"); var service = pmSource.serviceForSource("PowerDevil"); //note the strange camelCasing is intentional var operation = service.operationDescription("requestShutDown"); return service.startOperationCall(operation); } function addPlasmoid(applet) { settingsModel.append({"icon": applet.icon, "text": applet.title, "enabled": false, "applet": applet, "settingsCommand": "", "toggleFunction": ""}); } signal plasmoidTriggered(var applet, var id) Layout.minimumHeight: flow.implicitHeight + units.largeSpacing*2 property int screenBrightness property bool disableBrightnessUpdate: true readonly property int maximumScreenBrightness: pmSource.data["PowerDevil"] ? pmSource.data["PowerDevil"]["Maximum Screen Brightness"] || 0 : 0 onScreenBrightnessChanged: { if(!disableBrightnessUpdate) { var service = pmSource.serviceForSource("PowerDevil"); var operation = service.operationDescription("setBrightness"); operation.brightness = screenBrightness; operation.silent = true service.startOperationCall(operation); } } function requestScreenshot() { screenshotRequested = true; root.closeRequested(); } onClosed: { if (screenshotRequested) { plasmoid.nativeInterface.takeScreenshot(); screenshotRequested = false; } } PlasmaCore.DataSource { id: pmSource engine: "powermanagement" connectedSources: ["PowerDevil"] onSourceAdded: { if (source === "PowerDevil") { disconnectSource(source); connectSource(source); } } onDataChanged: { disableBrightnessUpdate = true; root.screenBrightness = pmSource.data["PowerDevil"]["Screen Brightness"]; disableBrightnessUpdate = false; } } //HACK: make the list know about the applet delegate which is a qtobject QtObject { id: nullApplet } Component.onCompleted: { //NOTE: add all in javascript as the static decl of listelements can't have scripts settingsModel.append({ "text": i18n("Settings"), "icon": "configure", "enabled": false, "settingsCommand": "plasma-settings", "toggleFunction": "", "delegate": "", "enabled": false, "applet": null }); settingsModel.append({ "text": i18n("Wifi"), "icon": "network-wireless-signal", "settingsCommand": "", "toggleFunction": "toggleWifi", "delegate": "", "enabled": enabledConnections.wirelessEnabled, "applet": null }); settingsModel.append({ "text": i18n("Mobile Data"), "icon": "network-modem", "settingsCommand": "", "toggleFunction": "toggleWwan", "delegate": "", "enabled": enabledConnections.wwanEnabled, "applet": null }); settingsModel.append({ "text": i18n("Battery"), "icon": "battery-full", "enabled": false, "settingsCommand": "plasma-settings -m kcm_mobile_power", "toggleFunction": "", "delegate": "", "enabled": false, "applet": null }); settingsModel.append({ "text": i18n("Sound"), "icon": "audio-speakers-symbolic", "enabled": false, "settingsCommand": "plasma-settings -m kcm_pulseaudio", "toggleFunction": "", "delegate": "", "enabled": false, "applet": null }); settingsModel.append({ "text": i18n("Flashlight"), "icon": "flashlight-on", "enabled": false, "settingsCommand": "", "toggleFunction": "toggleTorch", "applet": null }); settingsModel.append({ "text": i18n("Location"), "icon": "find-location-symbolic", "enabled": false, "settingsCommand": "", "applet": null }); settingsModel.append({ "text": i18n("Screenshot"), "icon": "spectacle", "enabled": false, "settingsCommand": "", "toggleFunction": "requestScreenshot", "applet": null }); + settingsModel.append({ + "text": i18n("Auto-rotate"), + "icon": "rotation-allowed", + "enabled": plasmoid.nativeInterface.autoRotateEnabled, + "settingsCommand": "", + "toggleFunction": "toggleRotation", + "applet": null + }); brightnessSlider.moved.connect(function() { root.screenBrightness = brightnessSlider.value; }); disableBrightnessUpdate = false; } ListModel { id: settingsModel } Flow { id: flow anchors { fill: parent margins: units.smallSpacing } readonly property real cellSizeHint: units.iconSizes.large + units.smallSpacing * 6 readonly property real columnWidth: Math.floor(width / Math.floor(width / cellSizeHint)) spacing: 0 Repeater { model: settingsModel delegate: Loader { id: loader //FIXME: why this is needed? width: flow.columnWidth height: item ? item.implicitHeight : 0 source: Qt.resolvedUrl((model.delegate ? model.delegate : "Delegate") + ".qml") Connections { target: loader.item onCloseRequested: root.closeRequested(); } Connections { target: root onClosed: loader.item.panelClosed(); } } } move: Transition { NumberAnimation { duration: units.shortDuration easing.type: Easing.InOutQuad properties: "x,y" } } BrightnessItem { id: brightnessSlider width: flow.width icon: "video-display-brightness" label: i18n("Display Brightness") value: root.screenBrightness maximumValue: root.maximumScreenBrightness Connections { target: root onScreenBrightnessChanged: brightnessSlider.value = root.screenBrightness } } } } diff --git a/containments/panel/phonepanel.cpp b/containments/panel/phonepanel.cpp index f3cd43d..7c8843f 100644 --- a/containments/panel/phonepanel.cpp +++ b/containments/panel/phonepanel.cpp @@ -1,134 +1,158 @@ /*************************************************************************** * Copyright (C) 2015 Marco Martin * * Copyright (C) 2018 Bhushan Shah * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "phonepanel.h" #include #include #include #include #include #include #include #include constexpr int SCREENSHOT_DELAY = 200; PhonePanel::PhonePanel(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args) { //setHasConfigurationInterface(true); + m_kscreenInterface = new org::kde::KScreen(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/kscreen"), QDBusConnection::sessionBus(), this); m_screenshotInterface = new org::kde::kwin::Screenshot(QStringLiteral("org.kde.KWin"), QStringLiteral("/Screenshot"), QDBusConnection::sessionBus(), this); } PhonePanel::~PhonePanel() = default; void PhonePanel::executeCommand(const QString &command) { qWarning() << "Executing" << command; QProcess::startDetached(command); } void PhonePanel::toggleTorch() { if (!m_running) { gst_init(nullptr, nullptr); // create elements m_source = gst_element_factory_make("droidcamsrc", "source"); m_sink = gst_element_factory_make("fakesink", "sink"); m_pipeline = gst_pipeline_new("torch-pipeline"); if (!m_pipeline || !m_source || !m_sink) { qDebug() << "Failed to turn on torch: failed to create elements"; return; } gst_bin_add_many(GST_BIN(m_pipeline), m_source, m_sink, NULL); if (gst_element_link(m_source, m_sink) != TRUE) { qDebug() << "Failed to turn on torch: failed to link source and sink"; g_object_unref(m_pipeline); return; } g_object_set(m_source, "mode", 2, NULL); g_object_set(m_source, "video-torch", TRUE, NULL); if (gst_element_set_state(m_pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { qDebug() << "Failed to turn on torch: failed to start pipeline"; g_object_unref(m_pipeline); return; } m_running = true; } else { gst_element_set_state(m_pipeline, GST_STATE_NULL); gst_object_unref(m_pipeline); m_running = false; } } +bool PhonePanel::autoRotate() +{ + QDBusPendingReply reply = m_kscreenInterface->getAutoRotate(); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "Getting auto rotate failed:" << reply.error().name() << reply.error().message(); + return false; + } else { + return reply.value(); + } +} + +void PhonePanel::setAutoRotate(bool value) +{ + QDBusPendingReply<> reply = m_kscreenInterface->setAutoRotate(value); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "Setting auto rotate failed:" << reply.error().name() << reply.error().message(); + } else { + emit autoRotateChanged(value); + } +} + void PhonePanel::takeScreenshot() { // wait ~200 ms to wait for rest of animations QTimer::singleShot(SCREENSHOT_DELAY, [=]() { // screenshot fullscreen currently doesn't work on all devices -> we need to use screenshot area // this won't work with multiple screens QSize screenSize = QGuiApplication::primaryScreen()->size(); QDBusPendingReply reply = m_screenshotInterface->screenshotArea(0, 0, screenSize.width(), screenSize.height()); auto *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [=](QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { qWarning() << "Creating the screenshot failed:" << reply.error().name() << reply.error().message(); } else { QString filePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); if (filePath.isEmpty()) { qWarning() << "Couldn't find a writable location for the screenshot! The screenshot is in /tmp."; return; } QDir picturesDir(filePath); if (!picturesDir.mkpath(QStringLiteral("Screenshots"))) { qWarning() << "Couldn't create folder at" << picturesDir.path() + QStringLiteral("/Screenshots") << "to take screenshot."; return; } filePath += QStringLiteral("/Screenshots/Screenshot_%1.png") .arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss"))); const QString currentPath = reply.argumentAt<0>(); QtConcurrent::run(QThreadPool::globalInstance(), [=]() { QFile screenshotFile(currentPath); if (!screenshotFile.rename(filePath)) { qWarning() << "Couldn't move screenshot into Pictures folder:" << screenshotFile.errorString(); } qDebug() << "Successfully saved screenshot at" << filePath; }); } watcher->deleteLater(); }); }); } K_EXPORT_PLASMA_APPLET_WITH_JSON(quicksettings, PhonePanel, "metadata.json") #include "phonepanel.moc" diff --git a/containments/panel/phonepanel.h b/containments/panel/phonepanel.h index 4e0bc67..4403fff 100644 --- a/containments/panel/phonepanel.h +++ b/containments/panel/phonepanel.h @@ -1,53 +1,62 @@ /*************************************************************************** * Copyright (C) 2015 Marco Martin * * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef PHONEPANEL_H #define PHONEPANEL_H #include #include +#include "kscreeninterface.h" #include "screenshotinterface.h" class PhonePanel : public Plasma::Containment { Q_OBJECT + Q_PROPERTY(bool autoRotateEnabled READ autoRotate WRITE setAutoRotate NOTIFY autoRotateChanged); public: PhonePanel( QObject *parent, const QVariantList &args ); ~PhonePanel() override; public Q_SLOTS: void executeCommand(const QString &command); void toggleTorch(); void takeScreenshot(); + bool autoRotate(); + void setAutoRotate(bool value); + +signals: + void autoRotateChanged(bool value); + private: GstElement* m_pipeline; GstElement* m_sink; GstElement* m_source; bool m_running = false; + org::kde::KScreen *m_kscreenInterface; org::kde::kwin::Screenshot *m_screenshotInterface; }; #endif