diff --git a/CMakeLists.txt b/CMakeLists.txt index a8fd43e3..7a9791fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,77 +1,85 @@ cmake_minimum_required(VERSION 3.0) project(kdeconnect) set(KDECONNECT_VERSION_MAJOR 1) -set(KDECONNECT_VERSION_MINOR 2) -set(KDECONNECT_VERSION_PATCH 1) +set(KDECONNECT_VERSION_MINOR 3) +set(KDECONNECT_VERSION_PATCH 0) set(KDECONNECT_VERSION "${KDECONNECT_VERSION_MAJOR}.${KDECONNECT_VERSION_MINOR}.${KDECONNECT_VERSION_PATCH}") set(QT_MIN_VERSION "5.7.0") set(KF5_MIN_VERSION "5.42.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_SOURCE_DIR}/cmake) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Quick) -find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils) -find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS DocTools) +find_package(KF5 ${KF5_MIN_VERSION} + REQUIRED COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils + OPTIONAL_COMPONENTS DocTools +) find_package(Qca-qt5 2.1.0 REQUIRED) +find_package(Phonon4Qt5 4.9.0 NO_MODULE) +set_package_properties(Phonon4Qt5 PROPERTIES + DESCRIPTION "Qt-based audio library" + TYPE OPTIONAL + PURPOSE "Required for Find My Device plugin" +) include_directories(${CMAKE_SOURCE_DIR}) configure_file(kdeconnect-version.h.in ${CMAKE_CURRENT_BINARY_DIR}/kdeconnect-version.h) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMAddTests) include(ECMSetupVersion) include(ECMInstallIcons) include(FeatureSummary) include(KDEConnectMacros.cmake) add_definitions(-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_KEYWORDS) include(GenerateExportHeader) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(core) add_subdirectory(kcm) add_subdirectory(kcmplugin) if(NOT WIN32) add_subdirectory(kio) add_subdirectory(plasmoid) endif() add_subdirectory(icon) add_subdirectory(interfaces) option(EXPERIMENTALAPP_ENABLED OFF) if(EXPERIMENTALAPP_ENABLED) add_subdirectory(app) endif() add_subdirectory(daemon) add_subdirectory(plugins) add_subdirectory(cli) add_subdirectory(indicator) add_subdirectory(fileitemactionplugin) add_subdirectory(urlhandler) add_subdirectory(nautilus-extension) option(SMSAPP_ENABLED OFF) if(SMSAPP_ENABLED) find_package(KF5People REQUIRED) add_subdirectory(smsapp) endif() if(KF5DocTools_FOUND) add_subdirectory(doc) endif() if(BUILD_TESTING) add_subdirectory(tests) endif() install(FILES org.kde.kdeconnect.kcm.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/app/qml/FindDevicesPage.qml b/app/qml/FindDevicesPage.qml index a2d8284b..8526eccd 100644 --- a/app/qml/FindDevicesPage.qml +++ b/app/qml/FindDevicesPage.qml @@ -1,68 +1,73 @@ /* * Copyright 2016 Aleix Pol Gonzalez * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import org.kde.kirigami 2.0 as Kirigami import org.kde.kdeconnect 1.0 Kirigami.Page { + Component { + id: deviceComp + DevicePage {} + } + objectName: "FindDevices" title: i18n("Pair") ScrollView { anchors.fill: parent ListView { section { property: "status" delegate: Label { text: switch (parseInt(section)) { case DevicesModel.Paired: return i18n("Paired") case DevicesModel.Reachable: return i18n("Reachable") case (DevicesModel.Reachable | DevicesModel.Paired): return i18n("Paired & Reachable") } } } model: DevicesSortProxyModel { sourceModel: DevicesModel { displayFilter: DevicesModel.Reachable } } delegate: Kirigami.BasicListItem { width: ListView.view.width icon: iconName label: display + "\n" + toolTip enabled: !(status & DevicesModel.Paired) onClicked: { pageStack.clear() pageStack.push( - "qrc:/qml/DevicePage.qml", + deviceComp, {currentDevice: device} ); } } } } } diff --git a/app/qml/PluginItem.qml b/app/qml/PluginItem.qml index b0d55ddd..ff5771da 100644 --- a/app/qml/PluginItem.qml +++ b/app/qml/PluginItem.qml @@ -1,49 +1,49 @@ /* * Copyright 2015 Aleix Pol Gonzalez * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import org.kde.kirigami 2.0 as Kirigami import org.kde.kdeconnect 1.0 Kirigami.BasicListItem { property alias pluginName: checker.pluginName property var interfaceFactory property string component readonly property var checker: PluginChecker { id: checker device: deviceView.currentDevice } visible: checker.available onClicked: { if (component === "") return; - var obj = interfaceFactory.create(checker.deviceId); + var obj = interfaceFactory.create(checker.device.id()); var page = pageStack.push( component, { pluginInterface: obj } ); obj.parent = page } } diff --git a/app/qml/mpris.qml b/app/qml/mpris.qml index fe2c2ed7..c3da7a32 100644 --- a/app/qml/mpris.qml +++ b/app/qml/mpris.qml @@ -1,79 +1,107 @@ /* * Copyright 2015 Aleix Pol Gonzalez * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ import QtQuick 2.2 import QtQuick.Controls 1.2 import QtQuick.Layouts 1.1 import org.kde.kirigami 2.0 as Kirigami Kirigami.Page { id: root property QtObject pluginInterface title: i18n("Multimedia Controls") ColumnLayout { anchors.fill: parent Component.onCompleted: { pluginInterface.requestPlayerList(); } Item { Layout.fillHeight: true } ComboBox { Layout.fillWidth: true model: root.pluginInterface.playerList onCurrentTextChanged: root.pluginInterface.player = currentText } Label { + id: nowPlaying Layout.fillWidth: true text: root.pluginInterface.nowPlaying + visible: root.pluginInterface.title.length == 0 + wrapMode: Text.Wrap + } + Label { + Layout.fillWidth: true + text: root.pluginInterface.title + visible: !nowPlaying.visible + wrapMode: Text.Wrap + } + Label { + Layout.fillWidth: true + text: root.pluginInterface.artist + visible: !nowPlaying.visible && !artistAlbum.visible && root.pluginInterface.artist.length > 0 + wrapMode: Text.Wrap + } + Label { + Layout.fillWidth: true + text: root.pluginInterface.album + visible: !nowPlaying.visible && !artistAlbum.visible && root.pluginInterface.album.length > 0 + wrapMode: Text.Wrap + } + Label { + id: artistAlbum + Layout.fillWidth: true + text: i18n("%1 - %2", root.pluginInterface.artist, root.pluginInterface.album) + visible: !nowPlaying.visible && root.pluginInterface.album.length > 0 && root.pluginInterface.artist.length > 0 + wrapMode: Text.Wrap } RowLayout { Layout.fillWidth: true Button { Layout.fillWidth: true iconName: "media-skip-backward" onClicked: root.pluginInterface.sendAction("Previous") } Button { Layout.fillWidth: true iconName: root.pluginInterface.isPlaying ? "media-playback-pause" : "media-playback-start" onClicked: root.pluginInterface.sendAction("PlayPause"); } Button { Layout.fillWidth: true iconName: "media-skip-forward" onClicked: root.pluginInterface.sendAction("Next") } } RowLayout { Layout.fillWidth: true Label { text: i18n("Volume:") } Slider { value: root.pluginInterface.volume maximumValue: 100 Layout.fillWidth: true } } Item { Layout.fillHeight: true } } } diff --git a/core/backends/bluetooth/bluetoothdevicelink.cpp b/core/backends/bluetooth/bluetoothdevicelink.cpp index 82f5df62..691066ef 100644 --- a/core/backends/bluetooth/bluetoothdevicelink.cpp +++ b/core/backends/bluetooth/bluetoothdevicelink.cpp @@ -1,106 +1,103 @@ /** * Copyright 2016 Saikrishna Arcot * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "bluetoothdevicelink.h" #include "../linkprovider.h" #include "bluetoothlinkprovider.h" #include "bluetoothuploadjob.h" #include "bluetoothdownloadjob.h" #include "core_debug.h" BluetoothDeviceLink::BluetoothDeviceLink(const QString& deviceId, LinkProvider* parent, QBluetoothSocket* socket) : DeviceLink(deviceId, parent) , mSocketReader(new DeviceLineReader(socket, this)) , mBluetoothSocket(socket) , mPairingHandler(new BluetoothPairingHandler(this)) { connect(mSocketReader, SIGNAL(readyRead()), this, SLOT(dataReceived())); //We take ownership of the socket. //When the link provider destroys us, //the socket (and the reader) will be //destroyed as well mBluetoothSocket->setParent(this); connect(mBluetoothSocket, SIGNAL(disconnected()), this, SLOT(deleteLater())); } QString BluetoothDeviceLink::name() { return "BluetoothLink"; // Should be same in both android and kde version } bool BluetoothDeviceLink::sendPacket(NetworkPacket& np) { if (np.hasPayload()) { - qCWarning(KDECONNECT_CORE) << "Sending packets with payload over bluetooth not yet supported"; - /* BluetoothUploadJob* uploadJob = new BluetoothUploadJob(np.payload(), mBluetoothSocket->peerAddress(), this); np.setPayloadTransferInfo(uploadJob->transferInfo()); uploadJob->start(); - */ } int written = mSocketReader->write(np.serialize()); return (written != -1); } void BluetoothDeviceLink::userRequestsPair() { mPairingHandler->requestPairing(); } void BluetoothDeviceLink::userRequestsUnpair() { mPairingHandler->unpair(); } bool BluetoothDeviceLink::linkShouldBeKeptAlive() { return pairStatus() == Paired; } void BluetoothDeviceLink::dataReceived() { if (mSocketReader->bytesAvailable() == 0) return; const QByteArray serializedPacket = mSocketReader->readLine(); //qCDebug(KDECONNECT_CORE) << "BluetoothDeviceLink dataReceived" << packet; NetworkPacket packet(QString::null); NetworkPacket::unserialize(serializedPacket, &packet); if (packet.type() == PACKET_TYPE_PAIR) { //TODO: Handle pair/unpair requests and forward them (to the pairing handler?) mPairingHandler->packetReceived(packet); return; } if (packet.hasPayloadTransferInfo()) { BluetoothDownloadJob* downloadJob = new BluetoothDownloadJob(mBluetoothSocket->peerAddress(), packet.payloadTransferInfo(), this); downloadJob->start(); packet.setPayload(downloadJob->payload(), packet.payloadSize()); } Q_EMIT receivedPacket(packet); if (mSocketReader->bytesAvailable() > 0) { QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); } } diff --git a/core/backends/bluetooth/bluetoothuploadjob.cpp b/core/backends/bluetooth/bluetoothuploadjob.cpp index 1137b1a3..45d320d1 100644 --- a/core/backends/bluetooth/bluetoothuploadjob.cpp +++ b/core/backends/bluetooth/bluetoothuploadjob.cpp @@ -1,85 +1,113 @@ /* * Copyright 2016 Saikrishna Arcot + * Copyright 2018 Matthijs TIjink * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "bluetoothuploadjob.h" #include #include "core_debug.h" #include BluetoothUploadJob::BluetoothUploadJob(const QSharedPointer& data, const QBluetoothAddress& remoteAddress, QObject* parent) : QObject(parent) , mData(data) , mRemoteAddress(remoteAddress) , mTransferUuid(QBluetoothUuid::createUuid()) , mServer(new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this)) { mServer->setSecurityFlags(QBluetooth::Encryption | QBluetooth::Secure); } QVariantMap BluetoothUploadJob::transferInfo() const { QVariantMap ret; ret["uuid"] = mTransferUuid.toString().mid(1, 36); return ret; } void BluetoothUploadJob::start() { - connect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + connect(mServer, &QBluetoothServer::newConnection, this, &BluetoothUploadJob::newConnection); mServiceInfo = mServer->listen(mTransferUuid, "KDE Connect Transfer Job"); Q_ASSERT(mServiceInfo.isValid()); } void BluetoothUploadJob::newConnection() { - QBluetoothSocket* socket = mServer->nextPendingConnection(); - connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); + m_socket = mServer->nextPendingConnection(); + Q_ASSERT(m_socket); + m_socket->setParent(this); + connect(m_socket, &QBluetoothSocket::disconnected, this, &BluetoothUploadJob::deleteLater); - if (socket->peerAddress() != mRemoteAddress) { - socket->close(); + if (m_socket->peerAddress() != mRemoteAddress) { + m_socket->close(); } else { mServer->close(); - disconnect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + disconnect(mServer, &QBluetoothServer::newConnection, this, &BluetoothUploadJob::newConnection); mServiceInfo.unregisterService(); if (!mData->open(QIODevice::ReadOnly)) { qCWarning(KDECONNECT_CORE) << "error when opening the input to upload"; - socket->close(); + m_socket->close(); deleteLater(); return; } } - while (mData->bytesAvailable() > 0 && socket->isWritable()) { - qint64 bytes = qMin(mData->bytesAvailable(), (qint64)4096); - int w = socket->write(mData->read(bytes)); - if (w < 0) { - qCWarning(KDECONNECT_CORE()) << "error when writing data to upload" << bytes << mData->bytesAvailable(); + connect(m_socket, &QBluetoothSocket::bytesWritten, this, &BluetoothUploadJob::writeSome); + connect(m_socket, &QBluetoothSocket::disconnected, this, &BluetoothUploadJob::closeConnection); + writeSome(); +} + +void BluetoothUploadJob::writeSome() { + Q_ASSERT(m_socket); + + bool errorOccurred = false; + while (m_socket->bytesToWrite() == 0 && mData->bytesAvailable() && m_socket->isWritable()) { + qint64 bytes = qMin(mData->bytesAvailable(), 4096); + int bytesWritten = m_socket->write(mData->read(bytes)); + + if (bytesWritten < 0) { + qCWarning(KDECONNECT_CORE()) << "error when writing data to bluetooth upload" << bytes << mData->bytesAvailable(); + errorOccurred = true; break; - } else { - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 2000); } } + if (mData->atEnd() || errorOccurred) { + disconnect(m_socket, &QBluetoothSocket::bytesWritten, this, &BluetoothUploadJob::writeSome); + mData->close(); + + connect(m_socket, &QBluetoothSocket::bytesWritten, this, &BluetoothUploadJob::finishWrites); + finishWrites(); + } +} + +void BluetoothUploadJob::finishWrites() { + Q_ASSERT(m_socket); + if (m_socket->bytesToWrite() == 0) { + closeConnection(); + } +} + +void BluetoothUploadJob::closeConnection() { mData->close(); - socket->close(); deleteLater(); } diff --git a/core/backends/bluetooth/bluetoothuploadjob.h b/core/backends/bluetooth/bluetoothuploadjob.h index a2bd2280..040d89bd 100644 --- a/core/backends/bluetooth/bluetoothuploadjob.h +++ b/core/backends/bluetooth/bluetoothuploadjob.h @@ -1,53 +1,59 @@ /* * Copyright 2016 Saikrishna Arcot + * Copyright 2018 Matthijs TIjink * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #ifndef BLUETOOTHUPLOADJOB_H #define BLUETOOTHUPLOADJOB_H #include #include #include #include #include #include #include class BluetoothUploadJob : public QObject { Q_OBJECT public: explicit BluetoothUploadJob(const QSharedPointer& data, const QBluetoothAddress& remoteAddress, QObject* parent = 0); QVariantMap transferInfo() const; void start(); private: QSharedPointer mData; QBluetoothAddress mRemoteAddress; QBluetoothUuid mTransferUuid; QBluetoothServer* mServer; QBluetoothServiceInfo mServiceInfo; + QBluetoothSocket* m_socket; + + void closeConnection(); private Q_SLOTS: void newConnection(); + void writeSome(); + void finishWrites(); }; #endif // BLUETOOTHUPLOADJOB_H diff --git a/core/device.cpp b/core/device.cpp index fa600213..05754de7 100644 --- a/core/device.cpp +++ b/core/device.cpp @@ -1,485 +1,546 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "device.h" #include +#include +#include #include #include #include #include #include "core_debug.h" #include "kdeconnectplugin.h" #include "pluginloader.h" #include "backends/devicelink.h" #include "backends/lan/landevicelink.h" #include "backends/linkprovider.h" #include "networkpacket.h" #include "kdeconnectconfig.h" #include "daemon.h" +class Device::DevicePrivate +{ +public: + DevicePrivate(const QString &id) + : m_deviceId(id) + { + + } + + ~DevicePrivate() + { + qDeleteAll(m_deviceLinks); + m_deviceLinks.clear(); + } + + const QString m_deviceId; + QString m_deviceName; + DeviceType m_deviceType; + int m_protocolVersion; + + QVector m_deviceLinks; + QHash m_plugins; + + QMultiMap m_pluginsByIncomingCapability; + QSet m_supportedPlugins; + QSet m_pairRequests; +}; + static void warn(const QString& info) { qWarning() << "Device pairing error" << info; } Device::Device(QObject* parent, const QString& id) : QObject(parent) - , m_deviceId(id) - , m_protocolVersion(NetworkPacket::s_protocolVersion) //We don't know it yet + , d(new Device::DevicePrivate(id)) { - KdeConnectConfig::DeviceInfo info = KdeConnectConfig::instance()->getTrustedDevice(id); + d->m_protocolVersion = NetworkPacket::s_protocolVersion; + KdeConnectConfig::DeviceInfo info = KdeConnectConfig::instance()->getTrustedDevice(d->m_deviceId); - m_deviceName = info.deviceName; - m_deviceType = str2type(info.deviceType); + d->m_deviceName = info.deviceName; + d->m_deviceType = str2type(info.deviceType); //Register in bus QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); //Assume every plugin is supported until addLink is called and we can get the actual list - m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); + d->m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); connect(this, &Device::pairingError, this, &warn); } Device::Device(QObject* parent, const NetworkPacket& identityPacket, DeviceLink* dl) : QObject(parent) - , m_deviceId(identityPacket.get(QStringLiteral("deviceId"))) - , m_deviceName(identityPacket.get(QStringLiteral("deviceName"))) + , d(new Device::DevicePrivate(identityPacket.get(QStringLiteral("deviceId")))) { + d->m_deviceName = identityPacket.get(QStringLiteral("deviceName")); addLink(identityPacket, dl); //Register in bus QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); connect(this, &Device::pairingError, this, &warn); } Device::~Device() { - qDeleteAll(m_deviceLinks); - m_deviceLinks.clear(); + delete d; +} + +QString Device::id() const +{ + return d->m_deviceId; +} + +QString Device::name() const +{ + return d->m_deviceName; +} + +QString Device::type() const +{ + return type2str(d->m_deviceType); +} + +bool Device::isReachable() const +{ + return !d->m_deviceLinks.isEmpty(); +} + +int Device::protocolVersion() +{ + return d->m_protocolVersion; +} + +QStringList Device::supportedPlugins() const +{ + return d->m_supportedPlugins.toList(); } bool Device::hasPlugin(const QString& name) const { - return m_plugins.contains(name); + return d->m_plugins.contains(name); } QStringList Device::loadedPlugins() const { - return m_plugins.keys(); + return d->m_plugins.keys(); } void Device::reloadPlugins() { - QHash newPluginMap, oldPluginMap = m_plugins; + QHash newPluginMap, oldPluginMap = d->m_plugins; QMultiMap newPluginsByIncomingCapability; if (isTrusted() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices PluginLoader* loader = PluginLoader::instance(); - for (const QString& pluginName : qAsConst(m_supportedPlugins)) { + for (const QString& pluginName : qAsConst(d->m_supportedPlugins)) { const KPluginMetaData service = loader->getPluginInfo(pluginName); const bool pluginEnabled = isPluginEnabled(pluginName); const QSet incomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPacketType")).toSet(); if (pluginEnabled) { - KdeConnectPlugin* plugin = m_plugins.take(pluginName); + KdeConnectPlugin* plugin = d->m_plugins.take(pluginName); if (!plugin) { plugin = loader->instantiatePluginForDevice(pluginName, this); } Q_ASSERT(plugin); for (const QString& interface : incomingCapabilities) { newPluginsByIncomingCapability.insert(interface, plugin); } newPluginMap[pluginName] = plugin; } } } const bool differentPlugins = oldPluginMap != newPluginMap; //Erase all left plugins in the original map (meaning that we don't want //them anymore, otherwise they would have been moved to the newPluginMap) - qDeleteAll(m_plugins); - m_plugins = newPluginMap; - m_pluginsByIncomingCapability = newPluginsByIncomingCapability; + qDeleteAll(d->m_plugins); + d->m_plugins = newPluginMap; + d->m_pluginsByIncomingCapability = newPluginsByIncomingCapability; QDBusConnection bus = QDBusConnection::sessionBus(); - for (KdeConnectPlugin* plugin : qAsConst(m_plugins)) { + for (KdeConnectPlugin* plugin : qAsConst(d->m_plugins)) { //TODO: see how it works in Android (only done once, when created) plugin->connected(); const QString dbusPath = plugin->dbusPath(); if (!dbusPath.isEmpty()) { bus.registerObject(dbusPath, plugin, QDBusConnection::ExportAllProperties | QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportScriptableSlots); } } if (differentPlugins) { Q_EMIT pluginsChanged(); } } QString Device::pluginsConfigFile() const { return KdeConnectConfig::instance()->deviceConfigDir(id()).absoluteFilePath(QStringLiteral("config")); } void Device::requestPair() { if (isTrusted()) { Q_EMIT pairingError(i18n("Already paired")); return; } if (!isReachable()) { Q_EMIT pairingError(i18n("Device not reachable")); return; } - for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { dl->userRequestsPair(); } } void Device::unpair() { - for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { dl->userRequestsUnpair(); } KdeConnectConfig::instance()->removeTrustedDevice(id()); Q_EMIT trustedChanged(false); } void Device::pairStatusChanged(DeviceLink::PairStatus status) { if (status == DeviceLink::NotPaired) { KdeConnectConfig::instance()->removeTrustedDevice(id()); - for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { if (dl != sender()) { dl->setPairStatus(DeviceLink::NotPaired); } } } else { KdeConnectConfig::instance()->addTrustedDevice(id(), name(), type()); } reloadPlugins(); //Will load/unload plugins bool isTrusted = (status == DeviceLink::Paired); Q_EMIT trustedChanged(isTrusted); Q_ASSERT(isTrusted == this->isTrusted()); } static bool lessThan(DeviceLink* p1, DeviceLink* p2) { return p1->provider()->priority() > p2->provider()->priority(); } void Device::addLink(const NetworkPacket& identityPacket, DeviceLink* link) { //qCDebug(KDECONNECT_CORE) << "Adding link to" << id() << "via" << link->provider(); setName(identityPacket.get(QStringLiteral("deviceName"))); - m_deviceType = str2type(identityPacket.get(QStringLiteral("deviceType"))); + d->m_deviceType = str2type(identityPacket.get(QStringLiteral("deviceType"))); - if (m_deviceLinks.contains(link)) + if (d->m_deviceLinks.contains(link)) return; - m_protocolVersion = identityPacket.get(QStringLiteral("protocolVersion"), -1); - if (m_protocolVersion != NetworkPacket::s_protocolVersion) { - qCWarning(KDECONNECT_CORE) << m_deviceName << "- warning, device uses a different protocol version" << m_protocolVersion << "expected" << NetworkPacket::s_protocolVersion; + d->m_protocolVersion = identityPacket.get(QStringLiteral("protocolVersion"), -1); + if (d->m_protocolVersion != NetworkPacket::s_protocolVersion) { + qCWarning(KDECONNECT_CORE) << d->m_deviceName << "- warning, device uses a different protocol version" << d->m_protocolVersion << "expected" << NetworkPacket::s_protocolVersion; } connect(link, &QObject::destroyed, this, &Device::linkDestroyed); - m_deviceLinks.append(link); + d->m_deviceLinks.append(link); //Theoretically we will never add two links from the same provider (the provider should destroy //the old one before this is called), so we do not have to worry about destroying old links. //-- Actually, we should not destroy them or the provider will store an invalid ref! connect(link, &DeviceLink::receivedPacket, this, &Device::privateReceivedPacket); - std::sort(m_deviceLinks.begin(), m_deviceLinks.end(), lessThan); + std::sort(d->m_deviceLinks.begin(), d->m_deviceLinks.end(), lessThan); const bool capabilitiesSupported = identityPacket.has(QStringLiteral("incomingCapabilities")) || identityPacket.has(QStringLiteral("outgoingCapabilities")); if (capabilitiesSupported) { const QSet outgoingCapabilities = identityPacket.get(QStringLiteral("outgoingCapabilities")).toSet() , incomingCapabilities = identityPacket.get(QStringLiteral("incomingCapabilities")).toSet(); - m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities); + d->m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities); //qDebug() << "new plugins for" << m_deviceName << m_supportedPlugins << incomingCapabilities << outgoingCapabilities; } else { - m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); + d->m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); } reloadPlugins(); - if (m_deviceLinks.size() == 1) { + if (d->m_deviceLinks.size() == 1) { Q_EMIT reachableChanged(true); } connect(link, &DeviceLink::pairStatusChanged, this, &Device::pairStatusChanged); connect(link, &DeviceLink::pairingRequest, this, &Device::addPairingRequest); connect(link, &DeviceLink::pairingRequestExpired, this, &Device::removePairingRequest); connect(link, &DeviceLink::pairingError, this, &Device::pairingError); } void Device::addPairingRequest(PairingHandler* handler) { - const bool wasEmpty = m_pairRequests.isEmpty(); - m_pairRequests.insert(handler); + const bool wasEmpty = d->m_pairRequests.isEmpty(); + d->m_pairRequests.insert(handler); - if (wasEmpty != m_pairRequests.isEmpty()) - Q_EMIT hasPairingRequestsChanged(!m_pairRequests.isEmpty()); + if (wasEmpty != d->m_pairRequests.isEmpty()) + Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty()); } void Device::removePairingRequest(PairingHandler* handler) { - const bool wasEmpty = m_pairRequests.isEmpty(); - m_pairRequests.remove(handler); + const bool wasEmpty = d->m_pairRequests.isEmpty(); + d->m_pairRequests.remove(handler); - if (wasEmpty != m_pairRequests.isEmpty()) - Q_EMIT hasPairingRequestsChanged(!m_pairRequests.isEmpty()); + if (wasEmpty != d->m_pairRequests.isEmpty()) + Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty()); } bool Device::hasPairingRequests() const { - return !m_pairRequests.isEmpty(); + return !d->m_pairRequests.isEmpty(); } void Device::acceptPairing() { - if (m_pairRequests.isEmpty()) + if (d->m_pairRequests.isEmpty()) qWarning() << "no pair requests to accept!"; //copying because the pairing handler will be removed upon accept - const auto prCopy = m_pairRequests; + const auto prCopy = d->m_pairRequests; for (auto ph: prCopy) ph->acceptPairing(); } void Device::rejectPairing() { - if (m_pairRequests.isEmpty()) + if (d->m_pairRequests.isEmpty()) qWarning() << "no pair requests to reject!"; //copying because the pairing handler will be removed upon reject - const auto prCopy = m_pairRequests; + const auto prCopy = d->m_pairRequests; for (auto ph: prCopy) ph->rejectPairing(); } void Device::linkDestroyed(QObject* o) { removeLink(static_cast(o)); } void Device::removeLink(DeviceLink* link) { - m_deviceLinks.removeAll(link); + d->m_deviceLinks.removeAll(link); //qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining"; - if (m_deviceLinks.isEmpty()) { + if (d->m_deviceLinks.isEmpty()) { reloadPlugins(); Q_EMIT reachableChanged(false); } } bool Device::sendPacket(NetworkPacket& np) { Q_ASSERT(np.type() != PACKET_TYPE_PAIR); Q_ASSERT(isTrusted()); //Maybe we could block here any packet that is not an identity or a pairing packet to prevent sending non encrypted data - for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { if (dl->sendPacket(np)) return true; } return false; } void Device::privateReceivedPacket(const NetworkPacket& np) { Q_ASSERT(np.type() != PACKET_TYPE_PAIR); if (isTrusted()) { - const QList plugins = m_pluginsByIncomingCapability.values(np.type()); + const QList plugins = d->m_pluginsByIncomingCapability.values(np.type()); if (plugins.isEmpty()) { qWarning() << "discarding unsupported packet" << np.type() << "for" << name(); } for (KdeConnectPlugin* plugin : plugins) { plugin->receivePacket(np); } } else { qCDebug(KDECONNECT_CORE) << "device" << name() << "not paired, ignoring packet" << np.type(); unpair(); } } bool Device::isTrusted() const { return KdeConnectConfig::instance()->trustedDevices().contains(id()); } QStringList Device::availableLinks() const { QStringList sl; - sl.reserve(m_deviceLinks.size()); - for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + sl.reserve(d->m_deviceLinks.size()); + for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { sl.append(dl->provider()->name()); } return sl; } void Device::cleanUnneededLinks() { if (isTrusted()) { return; } - for(int i = 0; i < m_deviceLinks.size(); ) { - DeviceLink* dl = m_deviceLinks[i]; + for(int i = 0; i < d->m_deviceLinks.size(); ) { + DeviceLink* dl = d->m_deviceLinks[i]; if (!dl->linkShouldBeKeptAlive()) { dl->deleteLater(); - m_deviceLinks.remove(i); + d->m_deviceLinks.remove(i); } else { i++; } } } QHostAddress Device::getLocalIpAddress() const { - for (DeviceLink* dl : m_deviceLinks) { + for (DeviceLink* dl : d->m_deviceLinks) { LanDeviceLink* ldl = dynamic_cast(dl); if (ldl) { return ldl->hostAddress(); } } return QHostAddress::Null; } Device::DeviceType Device::str2type(const QString& deviceType) { if (deviceType == QLatin1String("desktop")) return Desktop; if (deviceType == QLatin1String("laptop")) return Laptop; if (deviceType == QLatin1String("smartphone") || deviceType == QLatin1String("phone")) return Phone; if (deviceType == QLatin1String("tablet")) return Tablet; + if (deviceType == QLatin1String("tv")) return Tv; return Unknown; } QString Device::type2str(Device::DeviceType deviceType) { if (deviceType == Desktop) return QStringLiteral("desktop"); if (deviceType == Laptop) return QStringLiteral("laptop"); if (deviceType == Phone) return QStringLiteral("smartphone"); if (deviceType == Tablet) return QStringLiteral("tablet"); + if (deviceType == Tv) return QStringLiteral("tv"); return QStringLiteral("unknown"); } QString Device::statusIconName() const { return iconForStatus(isReachable(), isTrusted()); } QString Device::iconName() const { return iconForStatus(true, false); } QString Device::iconForStatus(bool reachable, bool trusted) const { - Device::DeviceType deviceType = m_deviceType; + Device::DeviceType deviceType = d->m_deviceType; if (deviceType == Device::Unknown) { deviceType = Device::Phone; //Assume phone if we don't know the type } else if (deviceType == Device::Desktop) { deviceType = Device::Device::Laptop; // We don't have desktop icon yet } QString status = (reachable? (trusted? QStringLiteral("connected") : QStringLiteral("disconnected")) : QStringLiteral("trusted")); QString type = type2str(deviceType); return type+status; } void Device::setName(const QString& name) { - if (m_deviceName != name) { - m_deviceName = name; + if (d->m_deviceName != name) { + d->m_deviceName = name; Q_EMIT nameChanged(name); } } KdeConnectPlugin* Device::plugin(const QString& pluginName) const { - return m_plugins[pluginName]; + return d->m_plugins[pluginName]; } void Device::setPluginEnabled(const QString& pluginName, bool enabled) { KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); const QString enabledKey = pluginName + QStringLiteral("Enabled"); pluginStates.writeEntry(enabledKey, enabled); reloadPlugins(); } bool Device::isPluginEnabled(const QString& pluginName) const { const QString enabledKey = pluginName + QStringLiteral("Enabled"); KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); return (pluginStates.hasKey(enabledKey) ? pluginStates.readEntry(enabledKey, false) : PluginLoader::instance()->getPluginInfo(pluginName).isEnabledByDefault()); } QString Device::encryptionInfo() const { QString result; QCryptographicHash::Algorithm digestAlgorithm = QCryptographicHash::Algorithm::Sha1; QString localSha1 = QString::fromLatin1(KdeConnectConfig::instance()->certificate().digest(digestAlgorithm).toHex()); for (int i = 2; igetDeviceProperty(id(), QStringLiteral("certificate")).toStdString(); QSslCertificate remoteCertificate = QSslCertificate(QByteArray(remotePem.c_str(), (int)remotePem.size())); QString remoteSha1 = QString::fromLatin1(remoteCertificate.digest(digestAlgorithm).toHex()); for (int i = 2; i < remoteSha1.size(); i += 3) { remoteSha1.insert(i, ':'); // Improve readability } result += i18n("SHA1 fingerprint of remote device certificate is: %1\n", remoteSha1); return result; } diff --git a/core/device.h b/core/device.h index d8ab653b..0621e291 100644 --- a/core/device.h +++ b/core/device.h @@ -1,164 +1,153 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #ifndef DEVICE_H #define DEVICE_H #include #include -#include -#include #include #include "networkpacket.h" #include "backends/devicelink.h" class DeviceLink; class KdeConnectPlugin; class KDECONNECTCORE_EXPORT Device : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device") Q_PROPERTY(QString type READ type CONSTANT) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString iconName READ iconName CONSTANT) Q_PROPERTY(QString statusIconName READ statusIconName) Q_PROPERTY(bool isReachable READ isReachable NOTIFY reachableChanged) Q_PROPERTY(bool isTrusted READ isTrusted NOTIFY trustedChanged) Q_PROPERTY(QStringList supportedPlugins READ supportedPlugins NOTIFY pluginsChanged) Q_PROPERTY(bool hasPairingRequests READ hasPairingRequests NOTIFY hasPairingRequestsChanged) public: enum DeviceType { Unknown, Desktop, Laptop, Phone, Tablet, + Tv, }; /** * Restores the @p device from the saved configuration * * We already know it but we need to wait for an incoming DeviceLink to communicate */ Device(QObject* parent, const QString& id); /** * Device known via an incoming connection sent to us via a devicelink. * * We know everything but we don't trust it yet */ Device(QObject* parent, const NetworkPacket& np, DeviceLink* dl); ~Device() override; - QString id() const { return m_deviceId; } - QString name() const { return m_deviceName; } + QString id() const; + QString name() const; QString dbusPath() const { return "/modules/kdeconnect/devices/"+id(); } - QString type() const { return type2str(m_deviceType); } + QString type() const; QString iconName() const; QString statusIconName() const; Q_SCRIPTABLE QString encryptionInfo() const; //Add and remove links void addLink(const NetworkPacket& identityPacket, DeviceLink*); void removeLink(DeviceLink*); Q_SCRIPTABLE bool isTrusted() const; Q_SCRIPTABLE QStringList availableLinks() const; - bool isReachable() const { return !m_deviceLinks.isEmpty(); } + bool isReachable() const; Q_SCRIPTABLE QStringList loadedPlugins() const; Q_SCRIPTABLE bool hasPlugin(const QString& name) const; Q_SCRIPTABLE QString pluginsConfigFile() const; KdeConnectPlugin* plugin(const QString& pluginName) const; Q_SCRIPTABLE void setPluginEnabled(const QString& pluginName, bool enabled); Q_SCRIPTABLE bool isPluginEnabled(const QString& pluginName) const; void cleanUnneededLinks(); - int protocolVersion() { return m_protocolVersion; } - QStringList supportedPlugins() const { return m_supportedPlugins.toList(); } + int protocolVersion(); + QStringList supportedPlugins() const; QHostAddress getLocalIpAddress() const; public Q_SLOTS: ///sends a @p np packet to the device ///virtual for testing purposes. virtual bool sendPacket(NetworkPacket& np); //Dbus operations public Q_SLOTS: Q_SCRIPTABLE void requestPair(); //to all links Q_SCRIPTABLE void unpair(); //from all links Q_SCRIPTABLE void reloadPlugins(); //from kconf Q_SCRIPTABLE void acceptPairing(); Q_SCRIPTABLE void rejectPairing(); Q_SCRIPTABLE bool hasPairingRequests() const; private Q_SLOTS: void privateReceivedPacket(const NetworkPacket& np); void linkDestroyed(QObject* o); void pairStatusChanged(DeviceLink::PairStatus current); void addPairingRequest(PairingHandler* handler); void removePairingRequest(PairingHandler* handler); Q_SIGNALS: Q_SCRIPTABLE void pluginsChanged(); Q_SCRIPTABLE void reachableChanged(bool reachable); Q_SCRIPTABLE void trustedChanged(bool trusted); Q_SCRIPTABLE void pairingError(const QString& error); Q_SCRIPTABLE void nameChanged(const QString& name); Q_SCRIPTABLE void hasPairingRequestsChanged(bool hasPairingRequests); private: //Methods static DeviceType str2type(const QString& deviceType); static QString type2str(DeviceType deviceType); void setName(const QString& name); QString iconForStatus(bool reachable, bool paired) const; -private: //Fields (TODO: dPointer!) - const QString m_deviceId; - QString m_deviceName; - DeviceType m_deviceType; - int m_protocolVersion; - - QVector m_deviceLinks; - QHash m_plugins; - - //Capabilities stuff - QMultiMap m_pluginsByIncomingCapability; - QSet m_supportedPlugins; - QSet m_pairRequests; +private: + class DevicePrivate; + DevicePrivate *d; }; Q_DECLARE_METATYPE(Device*) #endif // DEVICE_H diff --git a/icon/32-status-tvconnected.png b/icon/32-status-tvconnected.png new file mode 100644 index 00000000..fa079bdd Binary files /dev/null and b/icon/32-status-tvconnected.png differ diff --git a/icon/32-status-tvdisconnected.png b/icon/32-status-tvdisconnected.png new file mode 100644 index 00000000..7330dbd5 Binary files /dev/null and b/icon/32-status-tvdisconnected.png differ diff --git a/icon/32-status-tvtrusted.png b/icon/32-status-tvtrusted.png new file mode 100644 index 00000000..12a5e659 Binary files /dev/null and b/icon/32-status-tvtrusted.png differ diff --git a/icon/CMakeLists.txt b/icon/CMakeLists.txt index cf05f32f..4becac5d 100644 --- a/icon/CMakeLists.txt +++ b/icon/CMakeLists.txt @@ -1,39 +1,45 @@ ecm_install_icons( ICONS 256-apps-kdeconnect.png 128-apps-kdeconnect.png 32-apps-kdeconnect.png 64-apps-kdeconnect.png 48-apps-kdeconnect.png 22-apps-kdeconnect.png 16-apps-kdeconnect.png sc-apps-kdeconnect.svgz DESTINATION ${ICON_INSTALL_DIR} ) ecm_install_icons( ICONS 32-status-laptopconnected.png 32-status-laptopdisconnected.png 32-status-laptoptrusted.png 32-status-smartphoneconnected.png 32-status-smartphonedisconnected.png 32-status-smartphonetrusted.png 32-status-tabletconnected.png 32-status-tabletdisconnected.png 32-status-tablettrusted.png + 32-status-tvconnected.png + 32-status-tvdisconnected.png + 32-status-tvtrusted.png sc-status-laptopconnected.svg sc-status-laptopdisconnected.svg sc-status-laptoptrusted.svg sc-status-smartphoneconnected.svg sc-status-smartphonedisconnected.svg sc-status-smartphonetrusted.svg sc-status-tabletconnected.svg sc-status-tabletdisconnected.svg sc-status-tablettrusted.svg + sc-status-tvconnected.svg + sc-status-tvdisconnected.svg + sc-status-tvtrusted.svg DESTINATION ${ICON_INSTALL_DIR} ) diff --git a/icon/sc-status-tvconnected.svg b/icon/sc-status-tvconnected.svg new file mode 100644 index 00000000..370f6d4d --- /dev/null +++ b/icon/sc-status-tvconnected.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + +   + + + + diff --git a/icon/sc-status-tvdisconnected.svg b/icon/sc-status-tvdisconnected.svg new file mode 100644 index 00000000..2829658e --- /dev/null +++ b/icon/sc-status-tvdisconnected.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + +   + + + diff --git a/icon/sc-status-tvtrusted.svg b/icon/sc-status-tvtrusted.svg new file mode 100644 index 00000000..9a8efa91 --- /dev/null +++ b/icon/sc-status-tvtrusted.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + +   + + + + diff --git a/indicator/org.kde.kdeconnect.nonplasma.desktop b/indicator/org.kde.kdeconnect.nonplasma.desktop index ad0fc29c..c104503b 100644 --- a/indicator/org.kde.kdeconnect.nonplasma.desktop +++ b/indicator/org.kde.kdeconnect.nonplasma.desktop @@ -1,72 +1,73 @@ [Desktop Entry] Name=KDE Connect Indicator Name[ca]=Indicador del KDE Connect Name[ca@valencia]=Indicador del KDE Connect Name[cs]=Indikátor KDE Connect Name[da]=KDE Connect-indikator Name[de]=KDE-Connect-Anzeige Name[el]=Εφαρμογή ένδειξης KDE Connect Name[en_GB]=KDE Connect Indicator Name[es]=Indicador de KDE Connect Name[eu]=KDE Connect adierazlea Name[fr]=Indicateur de KDE Connect Name[gl]=Indicador de KDE Connect Name[it]=Indicatore di KDE Connect Name[nl]=KDE-Connect indicator Name[nn]=KDE Connect-indikator Name[pl]=Wskaźnik KDE Connect Name[pt]=Indicador do KDE Connect Name[ru]=Индикатор KDE Connect Name[sr]=КДЕ‑конекцијин показатељ Name[sr@ijekavian]=КДЕ‑конекцијин показатељ Name[sr@ijekavianlatin]=KDE‑konekcijin pokazatelj Name[sr@latin]=KDE‑konekcijin pokazatelj Name[sv]=KDE-anslut indikator Name[tr]=KDE Connect Göstergesi Name[uk]=Індикатор KDE Connect Name[x-test]=xxKDE Connect Indicatorxx Name[zh_CN]=KDE Connect 指示器 +Name[zh_TW]=KDE 連線指示符號 Comment=Display information about your devices Comment[ar]=اعرض معلومات عن أجهزتك Comment[ast]=Amuesa información tocante a los tos preseos Comment[ca]=Mostra la informació dels vostres dispositius Comment[ca@valencia]=Mostra la informació dels vostres dispositius Comment[cs]=Zobrazit informace o vašich zařízeních Comment[da]=Vis information om dine enheder Comment[de]=Anzeige von Informationen über Ihre Geräte Comment[el]=Προβολή πληροφοριών σχετικά με τις συσκευές σας Comment[en_GB]=Display information about your devices Comment[es]=Mostrar información sobre sus dispositivos Comment[et]=Teabe kuvamine seadmete kohta Comment[eu]=Bistaratu zure gailuei buruzko informazioa Comment[fi]=Näyttää tietoa laitteistasi Comment[fr]=Afficher les informations de vos périphériques Comment[gl]=Mostrar información sobre os dispositivos Comment[he]=הצג מידע עך ההתקנים שלך Comment[hu]=Információk megjelenítése az eszközeiről Comment[it]=Visualizza informazioni sui tuoi dispositivi Comment[ko]=장치 정보 표시 Comment[nl]=Toon informatie over uw apparaten Comment[nn]=Vis informasjon om einingane dine Comment[pl]=Wyświetl informacje o twoich urządzeniach Comment[pt]=Mostrar informações sobre os seus dispositivos Comment[pt_BR]=Mostra informações sobre seus dispositivos Comment[ru]=Просмотр информации о мобильных устройствах Comment[sk]=Zobraziť informácie o vašich zariadeniach Comment[sr]=Приказује податке о вашем телефону Comment[sr@ijekavian]=Приказује податке о вашем телефону Comment[sr@ijekavianlatin]=Prikazuje podatke o vašem telefonu Comment[sr@latin]=Prikazuje podatke o vašem telefonu Comment[sv]=Visa information om enheter Comment[tr]=Aygıtlarınız hakkında bilgi görüntüleyin Comment[uk]=Показати дані щодо ваших пристроїв Comment[x-test]=xxDisplay information about your devicesxx Comment[zh_CN]=显示您设备的信息 Comment[zh_TW]=顯示關於您的裝置資訊 Exec=kdeconnect-indicator Icon=kdeconnect Type=Application Terminal=false Categories=Qt;KDE;Network; NotShowIn=KDE; diff --git a/interfaces/dbusinterfaces.h b/interfaces/dbusinterfaces.h index b3f4e95d..5e1b8c54 100644 --- a/interfaces/dbusinterfaces.h +++ b/interfaces/dbusinterfaces.h @@ -1,235 +1,239 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #ifndef DBUSINTERFACES_H #define DBUSINTERFACES_H #include "interfaces/kdeconnectinterfaces_export.h" #include "interfaces/daemoninterface.h" #include "interfaces/deviceinterface.h" #include "interfaces/devicebatteryinterface.h" #include "interfaces/devicesftpinterface.h" #include "interfaces/devicefindmyphoneinterface.h" #include "interfaces/devicenotificationsinterface.h" #include "interfaces/notificationinterface.h" #include "interfaces/mprisremoteinterface.h" #include "interfaces/remotecontrolinterface.h" #include "interfaces/lockdeviceinterface.h" #include "interfaces/remotecommandsinterface.h" #include "interfaces/remotekeyboardinterface.h" #include "interfaces/telephonyinterface.h" #include "interfaces/messageinterface.h" /** * Using these "proxy" classes just in case we need to rename the * interface, so we can change the class name in a single place. */ class KDECONNECTINTERFACES_EXPORT DaemonDbusInterface : public OrgKdeKdeconnectDaemonInterface { Q_OBJECT public: explicit DaemonDbusInterface(QObject* parent = nullptr); ~DaemonDbusInterface() override; static QString activatedService(); Q_SIGNALS: void deviceAdded(const QString& id); void pairingRequestsChangedProxy(); }; class KDECONNECTINTERFACES_EXPORT DeviceDbusInterface : public OrgKdeKdeconnectDeviceInterface { Q_OBJECT // TODO: Workaround because OrgKdeKdeconnectDeviceInterface is not generating // the signals for the properties Q_PROPERTY(bool isReachable READ isReachable NOTIFY reachableChangedProxy) Q_PROPERTY(bool isTrusted READ isTrusted NOTIFY trustedChangedProxy) Q_PROPERTY(QString name READ name NOTIFY nameChangedProxy) Q_PROPERTY(bool hasPairingRequests READ hasPairingRequests NOTIFY hasPairingRequestsChangedProxy) public: explicit DeviceDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~DeviceDbusInterface() override; Q_SCRIPTABLE QString id() const; Q_SCRIPTABLE void pluginCall(const QString& plugin, const QString& method); Q_SIGNALS: void nameChangedProxy(const QString& name); void trustedChangedProxy(bool paired); void reachableChangedProxy(bool reachable); void hasPairingRequestsChangedProxy(bool); private: const QString m_id; }; class KDECONNECTINTERFACES_EXPORT DeviceBatteryDbusInterface : public OrgKdeKdeconnectDeviceBatteryInterface { Q_OBJECT public: explicit DeviceBatteryDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~DeviceBatteryDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT DeviceNotificationsDbusInterface : public OrgKdeKdeconnectDeviceNotificationsInterface { Q_OBJECT public: explicit DeviceNotificationsDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~DeviceNotificationsDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT NotificationDbusInterface : public OrgKdeKdeconnectDeviceNotificationsNotificationInterface { Q_OBJECT public: NotificationDbusInterface(const QString& deviceId, const QString& notificationId, QObject* parent = nullptr); ~NotificationDbusInterface() override; QString notificationId() { return id; } private: const QString id; }; class KDECONNECTINTERFACES_EXPORT MessageDbusInterface : public OrgKdeKdeconnectDeviceTelephonyMessagesInterface { Q_OBJECT public: MessageDbusInterface(const QString& deviceId, const QString& messageId, QObject* parent = nullptr); ~MessageDbusInterface() override; QString messageId() { return id; } private: const QString id; }; class KDECONNECTINTERFACES_EXPORT SftpDbusInterface : public OrgKdeKdeconnectDeviceSftpInterface { Q_OBJECT public: explicit SftpDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~SftpDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT MprisDbusInterface : public OrgKdeKdeconnectDeviceMprisremoteInterface { Q_OBJECT // TODO: Workaround because qdbusxml2cpp is not generating // the signals for the properties Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY propertiesChangedProxy) Q_PROPERTY(int length READ length NOTIFY propertiesChangedProxy) Q_PROPERTY(QString nowPlaying READ nowPlaying NOTIFY propertiesChangedProxy) + Q_PROPERTY(QString title READ title NOTIFY propertiesChangedProxy) + Q_PROPERTY(QString artist READ artist NOTIFY propertiesChangedProxy) + Q_PROPERTY(QString album READ album NOTIFY propertiesChangedProxy) + Q_PROPERTY(QStringList playerList READ playerList NOTIFY propertiesChangedProxy) Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY propertiesChangedProxy) Q_PROPERTY(int position READ position WRITE setPosition NOTIFY propertiesChangedProxy) public: explicit MprisDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~MprisDbusInterface() override; Q_SIGNALS: void propertiesChangedProxy(); }; class KDECONNECTINTERFACES_EXPORT RemoteControlDbusInterface : public OrgKdeKdeconnectDeviceRemotecontrolInterface { Q_OBJECT public: explicit RemoteControlDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~RemoteControlDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT LockDeviceDbusInterface : public OrgKdeKdeconnectDeviceLockdeviceInterface { Q_OBJECT Q_PROPERTY(bool isLocked READ isLocked WRITE setIsLocked NOTIFY lockedChangedProxy) public: explicit LockDeviceDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~LockDeviceDbusInterface() override; Q_SIGNALS: void lockedChangedProxy(bool isLocked); }; class KDECONNECTINTERFACES_EXPORT FindMyPhoneDeviceDbusInterface : public OrgKdeKdeconnectDeviceFindmyphoneInterface { Q_OBJECT public: explicit FindMyPhoneDeviceDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~FindMyPhoneDeviceDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT RemoteCommandsDbusInterface : public OrgKdeKdeconnectDeviceRemotecommandsInterface { Q_OBJECT public: explicit RemoteCommandsDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~RemoteCommandsDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT RemoteKeyboardDbusInterface : public OrgKdeKdeconnectDeviceRemotekeyboardInterface { Q_OBJECT Q_PROPERTY(bool remoteState READ remoteState NOTIFY remoteStateChanged) public: explicit RemoteKeyboardDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~RemoteKeyboardDbusInterface() override; Q_SIGNALS: void remoteStateChanged(bool state); }; class KDECONNECTINTERFACES_EXPORT TelephonyDbusInterface : public OrgKdeKdeconnectDeviceTelephonyInterface { Q_OBJECT public: explicit TelephonyDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~TelephonyDbusInterface() override; }; template static void setWhenAvailable(const QDBusPendingReply& pending, W func, QObject* parent) { QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pending, parent); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, parent, [func](QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); QDBusPendingReply reply = *watcher; func(reply.value()); }); } #endif diff --git a/interfaces/devicesmodel.h b/interfaces/devicesmodel.h index f208136f..00b98c4b 100644 --- a/interfaces/devicesmodel.h +++ b/interfaces/devicesmodel.h @@ -1,99 +1,99 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #ifndef DEVICESMODEL_H #define DEVICESMODEL_H #include #include #include #include "interfaces/kdeconnectinterfaces_export.h" class QDBusPendingCallWatcher; class DaemonDbusInterface; class DeviceDbusInterface; class KDECONNECTINTERFACES_EXPORT DevicesModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int displayFilter READ displayFilter WRITE setDisplayFilter) Q_PROPERTY(int count READ rowCount NOTIFY rowsChanged) public: enum ModelRoles { NameModelRole = Qt::DisplayRole, IconModelRole = Qt::DecorationRole, StatusModelRole = Qt::InitialSortOrderRole, IdModelRole = Qt::UserRole, IconNameRole, DeviceRole }; Q_ENUM(ModelRoles); // A device is always paired or reachable or both // You can combine the Paired and Reachable flags enum StatusFilterFlag { NoFilter = 0x00, Paired = 0x01, // show device only if it's paired Reachable = 0x02 // show device only if it's reachable }; Q_DECLARE_FLAGS(StatusFilterFlags, StatusFilterFlag) Q_FLAGS(StatusFilterFlags) Q_ENUM(StatusFilterFlag) explicit DevicesModel(QObject* parent = nullptr); ~DevicesModel() override; void setDisplayFilter(int flags); int displayFilter() const; QVariant data(const QModelIndex& index, int role) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; Q_SCRIPTABLE DeviceDbusInterface* getDevice(int row) const; QHash roleNames() const override; + int rowForDevice(const QString& id) const; private Q_SLOTS: void deviceAdded(const QString& id); void deviceRemoved(const QString& id); void deviceUpdated(const QString& id, bool isVisible); void refreshDeviceList(); void receivedDeviceList(QDBusPendingCallWatcher* watcher); void nameChanged(const QString& newName); Q_SIGNALS: void rowsChanged(); private: - int rowForDevice(const QString& id) const; void clearDevices(); void appendDevice(DeviceDbusInterface* dev); bool passesFilter(DeviceDbusInterface* dev) const; DaemonDbusInterface* m_dbusInterface; QVector m_deviceList; StatusFilterFlag m_displayFilter; }; //Q_DECLARE_OPERATORS_FOR_FLAGS(DevicesModel::StatusFilterFlag) #endif // DEVICESMODEL_H diff --git a/kcm/kcm.cpp b/kcm/kcm.cpp index 421afacd..2d6411b4 100644 --- a/kcm/kcm.cpp +++ b/kcm/kcm.cpp @@ -1,373 +1,397 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "kcm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "ui_kcm.h" #include "interfaces/dbusinterfaces.h" #include "interfaces/devicesmodel.h" #include "devicessortproxymodel.h" #include "kdeconnect-version.h" K_PLUGIN_FACTORY(KdeConnectKcmFactory, registerPlugin();) static QString createId() { return QStringLiteral("kcm")+QString::number(QCoreApplication::applicationPid()); } -KdeConnectKcm::KdeConnectKcm(QWidget* parent, const QVariantList&) +KdeConnectKcm::KdeConnectKcm(QWidget* parent, const QVariantList& args) : KCModule(KAboutData::pluginData(QStringLiteral("kdeconnect-kcm")), parent) , kcmUi(new Ui::KdeConnectKcmUi()) , daemon(new DaemonDbusInterface(this)) , devicesModel(new DevicesModel(this)) , currentDevice(nullptr) { KAboutData* about = new KAboutData(QStringLiteral("kdeconnect-kcm"), i18n("KDE Connect Settings"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect Settings module"), KAboutLicense::KAboutLicense::GPL_V2, i18n("(C) 2015 Albert Vaca Cintora"), QString(), QStringLiteral("https://community.kde.org/KDEConnect") ); about->addAuthor(i18n("Albert Vaca Cintora")); setAboutData(about); kcmUi->setupUi(this); sortProxyModel = new DevicesSortProxyModel(devicesModel); kcmUi->deviceList->setModel(sortProxyModel); kcmUi->deviceInfo->setVisible(false); kcmUi->progressBar->setVisible(false); kcmUi->messages->setVisible(false); //Workaround: If we set this directly (or if we set it in the .ui file), the layout breaks kcmUi->noDeviceLinks->setWordWrap(false); QTimer::singleShot(0, [this] { kcmUi->noDeviceLinks->setWordWrap(true); }); setWhenAvailable(daemon->announcedName(), [this](const QString& announcedName) { kcmUi->rename_label->setText(announcedName); kcmUi->rename_edit->setText(announcedName); }, this); connect(daemon, SIGNAL(announcedNameChanged(QString)), kcmUi->rename_edit, SLOT(setText(QString))); connect(daemon, SIGNAL(announcedNameChanged(QString)), kcmUi->rename_label, SLOT(setText(QString))); setRenameMode(false); setButtons(KCModule::Help | KCModule::NoAdditionalButton); connect(devicesModel, &QAbstractItemModel::dataChanged, this, &KdeConnectKcm::resetSelection); connect(kcmUi->deviceList->selectionModel(), &QItemSelectionModel::currentChanged, this, &KdeConnectKcm::deviceSelected); connect(kcmUi->accept_button, &QAbstractButton::clicked, this, &KdeConnectKcm::acceptPairing); connect(kcmUi->reject_button, &QAbstractButton::clicked, this, &KdeConnectKcm::rejectPairing); connect(kcmUi->pair_button, &QAbstractButton::clicked, this, &KdeConnectKcm::requestPair); connect(kcmUi->unpair_button, &QAbstractButton::clicked, this, &KdeConnectKcm::unpair); connect(kcmUi->ping_button, &QAbstractButton::clicked, this, &KdeConnectKcm::sendPing); connect(kcmUi->refresh_button,&QAbstractButton::clicked, this, &KdeConnectKcm::refresh); connect(kcmUi->rename_edit,&QLineEdit::returnPressed, this, &KdeConnectKcm::renameDone); connect(kcmUi->renameDone_button,&QAbstractButton::clicked, this, &KdeConnectKcm::renameDone); connect(kcmUi->renameShow_button,&QAbstractButton::clicked, this, &KdeConnectKcm::renameShow); daemon->acquireDiscoveryMode(createId()); + + #if KCMUTILS_VERSION >= QT_VERSION_CHECK(5, 45, 0) + + if (!args.isEmpty() && args.first().type() == QVariant::String) { + const QString input = args.first().toString(); + const auto colonIdx = input.indexOf(QLatin1Char(':')); + const QString deviceId = input.left(colonIdx); + const QString pluginCM = colonIdx < 0 ? QString() : input.mid(colonIdx+1); + + connect(devicesModel, &DevicesModel::rowsInserted, this, [this, deviceId, pluginCM]() { + auto row = devicesModel->rowForDevice(deviceId); + if (row >= 0) { + const QModelIndex idx = sortProxyModel->mapFromSource(devicesModel->index(row)); + kcmUi->deviceList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); + } + if (!pluginCM.isEmpty()) { + kcmUi->pluginSelector->showConfiguration(pluginCM); + } + disconnect(devicesModel, &DevicesModel::rowsInserted, this, nullptr); + }); + } + + #endif } void KdeConnectKcm::renameShow() { setRenameMode(true); } void KdeConnectKcm::renameDone() { QString newName = kcmUi->rename_edit->text(); if (newName.isEmpty()) { //Rollback changes kcmUi->rename_edit->setText(kcmUi->rename_label->text()); } else { kcmUi->rename_label->setText(newName); daemon->setAnnouncedName(newName); } setRenameMode(false); } void KdeConnectKcm::setRenameMode(bool b) { kcmUi->renameDone_button->setVisible(b); kcmUi->rename_edit->setVisible(b); kcmUi->renameShow_button->setVisible(!b); kcmUi->rename_label->setVisible(!b); } KdeConnectKcm::~KdeConnectKcm() { daemon->releaseDiscoveryMode(createId()); delete kcmUi; } void KdeConnectKcm::refresh() { daemon->acquireDiscoveryMode(createId()); daemon->forceOnNetworkChange(); } void KdeConnectKcm::resetSelection() { if (!currentDevice) { return; } kcmUi->deviceList->selectionModel()->setCurrentIndex(sortProxyModel->mapFromSource(currentIndex), QItemSelectionModel::ClearAndSelect); } void KdeConnectKcm::deviceSelected(const QModelIndex& current) { if (currentDevice) { disconnect(currentDevice, 0, this, 0); } //Store previous device config pluginsConfigChanged(); if (!current.isValid()) { currentDevice = nullptr; kcmUi->deviceInfo->setVisible(false); return; } currentIndex = sortProxyModel->mapToSource(current); currentDevice = devicesModel->getDevice(currentIndex.row()); kcmUi->noDevicePlaceholder->setVisible(false); bool valid = (currentDevice != nullptr && currentDevice->isValid()); kcmUi->deviceInfo->setVisible(valid); if (!valid) { return; } kcmUi->messages->setVisible(false); resetDeviceView(); connect(currentDevice, SIGNAL(pluginsChanged()), this, SLOT(resetCurrentDevice())); connect(currentDevice, SIGNAL(trustedChanged(bool)), this, SLOT(trustedChanged(bool))); connect(currentDevice, SIGNAL(pairingError(QString)), this, SLOT(pairingFailed(QString))); connect(currentDevice, &DeviceDbusInterface::hasPairingRequestsChangedProxy, this, &KdeConnectKcm::currentDevicePairingChanged); } void KdeConnectKcm::currentDevicePairingChanged(bool pairing) { if (pairing) { setCurrentDeviceTrusted(RequestedByPeer); } else { setWhenAvailable(currentDevice->isTrusted(), [this](bool trusted) { setCurrentDeviceTrusted(trusted ? Trusted : NotTrusted); }, this); } } void KdeConnectKcm::resetCurrentDevice() { const QStringList supportedPluginNames = currentDevice->supportedPlugins(); if (m_oldSupportedPluginNames != supportedPluginNames) { resetDeviceView(); } } void KdeConnectKcm::resetDeviceView() { //KPluginSelector has no way to remove a list of plugins and load another, so we need to destroy and recreate it each time delete kcmUi->pluginSelector; kcmUi->pluginSelector = new KPluginSelector(this); kcmUi->deviceInfo_layout->addWidget(kcmUi->pluginSelector); kcmUi->pluginSelector->setConfigurationArguments(QStringList(currentDevice->id())); kcmUi->name_label->setText(currentDevice->name()); setWhenAvailable(currentDevice->isTrusted(), [this](bool trusted) { if (trusted) setCurrentDeviceTrusted(Trusted); else setWhenAvailable(currentDevice->hasPairingRequests(), [this](bool haspr) { setCurrentDeviceTrusted(haspr ? RequestedByPeer : NotTrusted); }, this); }, this); const QList pluginInfo = KPluginInfo::fromMetaData(KPluginLoader::findPlugins(QStringLiteral("kdeconnect/"))); QList availablePluginInfo; m_oldSupportedPluginNames = currentDevice->supportedPlugins(); for (auto it = pluginInfo.cbegin(), itEnd = pluginInfo.cend(); it!=itEnd; ++it) { if (m_oldSupportedPluginNames.contains(it->pluginName())) { availablePluginInfo.append(*it); } } KSharedConfigPtr deviceConfig = KSharedConfig::openConfig(currentDevice->pluginsConfigFile()); kcmUi->pluginSelector->addPlugins(availablePluginInfo, KPluginSelector::ReadConfigFile, i18n("Available plugins"), QString(), deviceConfig); connect(kcmUi->pluginSelector, &KPluginSelector::changed, this, &KdeConnectKcm::pluginsConfigChanged); } void KdeConnectKcm::requestPair() { if (!currentDevice) { return; } kcmUi->messages->hide(); setCurrentDeviceTrusted(Requested); currentDevice->requestPair(); } void KdeConnectKcm::unpair() { if (!currentDevice) { return; } setCurrentDeviceTrusted(NotTrusted); currentDevice->unpair(); } void KdeConnectKcm::acceptPairing() { if (!currentDevice) { return; } currentDevice->acceptPairing(); } void KdeConnectKcm::rejectPairing() { if (!currentDevice) { return; } currentDevice->rejectPairing(); } void KdeConnectKcm::pairingFailed(const QString& error) { if (sender() != currentDevice) return; setCurrentDeviceTrusted(NotTrusted); kcmUi->messages->setText(i18n("Error trying to pair: %1",error)); kcmUi->messages->animatedShow(); } void KdeConnectKcm::trustedChanged(bool trusted) { DeviceDbusInterface* senderDevice = (DeviceDbusInterface*) sender(); if (senderDevice == currentDevice) setCurrentDeviceTrusted(trusted ? Trusted : NotTrusted); } void KdeConnectKcm::setCurrentDeviceTrusted(KdeConnectKcm::TrustStatus trusted) { kcmUi->accept_button->setVisible(trusted == RequestedByPeer); kcmUi->reject_button->setVisible(trusted == RequestedByPeer); kcmUi->pair_button->setVisible(trusted == NotTrusted); kcmUi->unpair_button->setVisible(trusted == Trusted); kcmUi->progressBar->setVisible(trusted == Requested); kcmUi->ping_button->setVisible(trusted == Trusted); switch (trusted) { case Trusted: kcmUi->status_label->setText(i18n("(paired)")); break; case NotTrusted: kcmUi->status_label->setText(i18n("(not paired)")); break; case RequestedByPeer: kcmUi->status_label->setText(i18n("(incoming pair request)")); break; case Requested: kcmUi->status_label->setText(i18n("(pairing requested)")); break; } } void KdeConnectKcm::pluginsConfigChanged() { //Store previous selection if (!currentDevice) return; DeviceDbusInterface* auxCurrentDevice = currentDevice; currentDevice = nullptr; //HACK to avoid infinite recursion (for some reason calling save on pluginselector emits changed) kcmUi->pluginSelector->save(); currentDevice = auxCurrentDevice; currentDevice->reloadPlugins(); } void KdeConnectKcm::save() { pluginsConfigChanged(); KCModule::save(); } void KdeConnectKcm::sendPing() { if (!currentDevice) return; currentDevice->pluginCall(QStringLiteral("ping"), QStringLiteral("sendPing")); } QSize KdeConnectKcm::sizeHint() const { return QSize(890,550); //Golden ratio :D } QSize KdeConnectKcm::minimumSizeHint() const { return QSize(500,300); } #include "kcm.moc" #include "moc_kcm.cpp" diff --git a/org.kde.kdeconnect.kcm.appdata.xml b/org.kde.kdeconnect.kcm.appdata.xml index cda118cf..bcb9b8ca 100644 --- a/org.kde.kdeconnect.kcm.appdata.xml +++ b/org.kde.kdeconnect.kcm.appdata.xml @@ -1,127 +1,128 @@ org.kde.kdeconnect.kcm.desktop CC0-1.0 GPL-2.0+ KDE Connect كدي المتّصل KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE-Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect KDE Connect КДЕ‑конекција KDE‑konekcija КДЕ‑конекција KDE‑konekcija KDE-anslut KDE Connect KDE Connect xxKDE Connectxx KDE Connect KDE Connect Seamless connection of your devices اتّصال سلس بين أجهزتك Conexón ensin torgues de los tos preseos Connexió transparent amb els vostres dispositius Connexió transparent amb els vostres dispositius Snadné propojení vašich zařízení Umærkbar forbindelse mellem dine enheder Nahtlose Verbindung zu Ihren Geräten Απρόσκοπτη σύνδεση των συσκευών σας Seamless connection of your devices Conexión sin interrupciones de sus dispositivos Sujuv ühendus oma seadmetega Zure gailuen etengabeko konexioa Saumaton yhteys laitteisiisi Connecter vos périphériques avec facilité Conexión constante entre dispositivos חיבור מהיר בין ההתקנים שלך Connessione trasparente dei tuoi dispositivi 장치와 항상 연결하기 Naadloze verbinding met uw apparaten Saumlaus integrering med andre einingar Zintegrowane połączenia do twoich urządzeń Ligação transparente aos seus dispositivos Conexão transparente com seus dispositivos Интеграция с мобильными устройствами Jednoduché prepojenie vašich zariadení Једноставно повезивање ваших уређаја Jednostavno povezivanje vaših uređaja Једноставно повезивање ваших уређаја Jednostavno povezivanje vaših uređaja Sömlös anslutning av dina apparater Aygıtlarınızı sorunsuz bağlayın Безшовне з’єднання для усіх ваших пристроїв xxSeamless connection of your devicesxx 无缝连接您的设备 與您的裝置無縫連線

KDE Connect provides several features to integrate your phone and your computer: Please note you will need to install KDE Connect on your computer for this app to work, and keep the desktop version up-to-date with the Android version for the latest features to work.

El KDE Connect proporciona diverses característiques per integrar el telèfon amb l'ordinador: Recordeu que haureu d'instal·lar el KDE Connect a l'ordinador perquè aquesta aplicació funcioni, i mantenir actualitzada la versió de l'escriptori amb la versió d'Android perquè funcionin les últimes característiques.

El KDE Connect proporciona diverses característiques per integrar el telèfon amb l'ordinador: Recordeu que haureu d'instal·lar el KDE Connect a l'ordinador perquè aquesta aplicació funcione, i mantindre actualitzada la versió de l'escriptori amb la versió d'Android perquè funcionen les últimes característiques.

KDE Connect poskytuje několik vlastností pro vzájemnou integraci vašeho telefonu a počítače: Prosím pamatujte, že pro správnou funkci této aplikace potřebujete nainstalovat KDE Connect na váš počítač a pro správnou funkci nejnovějších vlastností udržovat aplikaci na telefonu i počítači aktuální.

KDE Connect tilbyder adskillige funktioner, der integrerer din telefon og din computer: Bemærk at du vil skulle installere KDE Connect på din computer for at denne app virker, samt holde desktop-versionen opdateret sammen med Android-versionen, for at de nyeste funktioner vil virke.

KDE-Connect bietet mehrere Funktionen zur Integration Ihres Telefons und Ihres Rechners: Bitte beachten Sie, dass Sie KDE-Connect auf Ihrem Rechner installieren müssen, damit diese App funktioniert. Halten Sie die Arbeitsflächen-Version und Android-Version auf dem neusten Stand, damit Sie die neuesten Funktionen nutzen können

Το KDE Connect παρέχει πολλές λειτουργίες ενσωμάτωσης μεταξύ τηλεφωνικής συσκευής και υπολογιστή: Σημειώστε ότι για την εφαρμογή αυτή θα χρειαστεί να εγκαταστήσετε το KDE Connect στον υπολογιστή σας και να διατηρείτε την επιφάνεια εργασίας ενημερωμένη με την έκδοση του Android για τις πιο πρόσφατες λειτουργίες.

KDE Connect provides several features to integrate your phone and your computer: Please note you will need to install KDE Connect on your computer for this app to work, and keep the desktop version up-to-date with the Android version for the latest features to work.

KDE Connect proporciona varias funcionalidades para integrar su teléfono y su equipo: Por favor, tenga en cuenta que deberá instalar KDE Connect en su equipo para que esta aplicación funcione. Además, deberá mantener la versión de escritorio actualizada con su versión de Android para beneficiarse de las últimas funcionalidades.

KDE Connect-ek eginbide ugari ditu zure telefonoa eta ordenagailua integratzeko: KDE Connect instalatu beharko duzu zure ordenagailuan aplikazioa hau ibili dadin, eta mahaigaineko bertsioa Androiden bertsioarekin eguneratuta izan beharko duzu eginbide berrienak ibili daitezen.

KDE Connect fournit un ensemble de fonctionnalités pour intégrer votre téléphone et votre ordinateur : veuillez noter qu'il est nécessaire d'installer KDE Connect sur votre ordinateur pour faire fonctionner cette application, ainsi que de maintenir le logiciel à jour pour profiter des dernières fonctionnalités.

KDE Connect fornece varias funcionalidades para integrar teléfonos con computadores. Teña en conta que terá que instalar KDE Connect no seu computador para que este aplicativo funcione, e manter a versión de escritorio sincronizada coa de Android para que as últimas funcionalidades funcionen.

KDE Connect fornisce diverse funzioni per integrare il tuo telefono e il tuo computer: affinché questa applicazione funzioni, dovrai installare KDE Connect sul tuo computer e mantenere la versione desktop aggiornata alla versione Android, in modo che le funzioni più recenti siano attive.

KDE Connect biedt verschillende functies om uw telefoon en uw computer: merk op dat het nodig is om KDE Connect op uw computer te installeren om deze app te laten werken en de bureabladversie up-to-date te houden met de Android-versie om de laatste functies te laten werken.

KDE Connect har fleire funksjonar for integrering av telefonen og datamaskina di. Merk at du må installera KDE Connect på datamaskina for at denne appen skal fungera. Viss du vil bruka dei nyaste funksjonane, må du òg hugsa på å halda datamaskinversjonen oppdatert når du oppdaterer appen.

KDE Connect zapewnia kilka możliwości integracji twojego telefonu z twoim komputerem: Musisz mieć wgrane KDE Connect na swoim komputerze, aby ta aplikacja zadziałała, a także aktualną wersję zgodną z wersją Androida, aby wszystkie najnowsze funkcje działały.

O KDE Connect oferece diversas funcionalidades para integrar o seu telefone com o seu computador: Lembre-se por favor que terá de instalar o KDE Connect com o seu computador para esta aplicação funcionar e deverá manter a versão no ambiente de trabalho actualizada com a versão para Android, para que as últimas funcionalidades funcionem.

KDE Connect предоставляет несколько функций для интеграции телефона и компьютера. Обратите внимание, что для работы этого приложения необходимо установить KDE Connect на свой компьютер и, для работы новейших функций, держать версию обновлённой в соответствии с версией для Android.

КДЕ‑конекција поседује вишеструке могућности интеграције вашег телефона и рачунара. Морате да инсталирате КДЕ‑конекцију на ваш рачунар да би ова апликација радила. Такође, нека вам обе апликације буду ажурне како бисте уживали у најновијим могућностима.

KDE‑konekcija poseduje višestruke mogućnosti integracije vašeg telefona i računara. Morate da instalirate KDE‑konekciju na vaš računar da bi ova aplikacija radila. Takođe, neka vam obe aplikacije budu ažurne kako biste uživali u najnovijim mogućnostima.

КДЕ‑конекција поседује вишеструке могућности интеграције вашег телефона и рачунара. Морате да инсталирате КДЕ‑конекцију на ваш рачунар да би ова апликација радила. Такође, нека вам обе апликације буду ажурне како бисте уживали у најновијим могућностима.

KDE‑konekcija poseduje višestruke mogućnosti integracije vašeg telefona i računara. Morate da instalirate KDE‑konekciju na vaš računar da bi ova aplikacija radila. Takođe, neka vam obe aplikacije budu ažurne kako biste uživali u najnovijim mogućnostima.

KDE-anslut tillhandahåller flera funktioner för att integrera telefon och dator. Observera att KDE-anslut måste installeras på datorn för att appen ska fungera, och att skrivbordsdatorns version måste hållas uppdaterad tillsammans med Androidversionen för att de senaste funktionerna ska fungera.

KDE Connect, telefonunuzu ve bilgisayarınızı bütünleştirmek için çeşitli özellikler sunar: Bu uygulamanın çalışabilmesi için bilgisayarınıza KDE Connect'i yüklemeniz ve en son özelliklerin çalışabilmesi için masaüstü sürümünün Android sürümü ile güncel tutulması gereklidir.

У KDE Connect передбачено декілька можливостей для інтеграції вашого телефону з вашим комп’ютером. Будь ласка, зауважте, що вам доведеться встановити KDE Connect на ваш комп’ютер, щоб скористатися цією програмою, а також підтримувати актуальність версії на комп’ютері та версії для Android, щоб мати змогу скористатися найсвіжішими можливостями.

xxKDE Connect provides several features to integrate your phone and your computer: Please note you will need to install KDE Connect on your computer for this app to work, and keep the desktop version up-to-date with the Android version for the latest features to work.xx

KDE Connect 提供了很多整合手机和电脑的功能特性:请注意您需要在电脑上安装 KDE Connect 应用才能使其工作,为了使最新特性能够生效,您必须更新桌面应用和 Android 应用到最新版本。

+

KDE 連線提供了一些功能以整合您的手機與電腦:請注意,你將需要在您的電腦上安裝 KDE 連線使這個應用程式能運作,並保持桌面版本與手機版本保持最新狀態以保證最新版本能運作。

https://community.kde.org/KDEConnect https://bugs.kde.org/enter_bug.cgi?format=guided&product=kdeconnect https://docs.kde.org/index.php?application=kdeconnect-kde&language=en https://cdn.kde.org/screenshots/kdeconnect/plasmoid.png https://cdn.kde.org/screenshots/kdeconnect/kcm.png https://cdn.kde.org/screenshots/kdeconnect/gnome3.png KDE kdeconnectd kdeconnect-cli
diff --git a/plasmoid/package/contents/ui/DeviceDelegate.qml b/plasmoid/package/contents/ui/DeviceDelegate.qml index d710892d..57fc1fce 100644 --- a/plasmoid/package/contents/ui/DeviceDelegate.qml +++ b/plasmoid/package/contents/ui/DeviceDelegate.qml @@ -1,263 +1,253 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ 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.kdeconnect 1.0 import QtQuick.Controls.Styles 1.4 PlasmaComponents.ListItem { id: root readonly property QtObject device: DeviceDbusInterfaceFactory.create(model.deviceId) RemoteKeyboard { id: remoteKeyboard device: root.device - onRemoteStateChanged: { - remoteKeyboardInput.available = remoteKeyboard.remoteState; - } - onKeyPressReceived: { // console.log("XXX received keypress key=" + key + " special=" + specialKey + " shift=" + shift + " ctrl=" + ctrl + " text=" + remoteKeyboardInput.text + " cursorPos=" + remoteKeyboardInput.cursorPosition); // interpret some special keys: if (specialKey == 12 || specialKey == 14) // Return/Esc -> clear remoteKeyboardInput.text = ""; else if (specialKey == 4 // Left && remoteKeyboardInput.cursorPosition > 0) --remoteKeyboardInput.cursorPosition; else if (specialKey == 6 // Right && remoteKeyboardInput.cursorPosition < remoteKeyboardInput.text.length) ++remoteKeyboardInput.cursorPosition; else if (specialKey == 1) { // Backspace -> delete left var pos = remoteKeyboardInput.cursorPosition; if (pos > 0) { remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos-1) + remoteKeyboardInput.text.substring(pos, remoteKeyboardInput.text.length); remoteKeyboardInput.cursorPosition = pos - 1; } } else if (specialKey == 13) { // Delete -> delete right var pos = remoteKeyboardInput.cursorPosition; if (pos < remoteKeyboardInput.text.length) { remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos) + remoteKeyboardInput.text.substring(pos+1, remoteKeyboardInput.text.length); remoteKeyboardInput.cursorPosition = pos; // seems to be set to text.length automatically! } } else if (specialKey == 10) // Home remoteKeyboardInput.cursorPosition = 0; else if (specialKey == 11) // End remoteKeyboardInput.cursorPosition = remoteKeyboardInput.text.length; else { // echo visible keys var sanitized = ""; for (var i = 0; i < key.length; i++) { if (key.charCodeAt(i) > 31) sanitized += key.charAt(i); } if (sanitized.length > 0 && !ctrl && !alt) { // insert sanitized at current pos: var pos = remoteKeyboardInput.cursorPosition; remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos) + sanitized + remoteKeyboardInput.text.substring(pos, remoteKeyboardInput.text.length); remoteKeyboardInput.cursorPosition = pos + 1; // seems to be set to text.length automatically! } } // console.log("XXX After received keypress key=" + key + " special=" + specialKey + " shift=" + shift + " ctrl=" + ctrl + " text=" + remoteKeyboardInput.text + " cursorPos=" + remoteKeyboardInput.cursorPosition); } } Column { width: parent.width - + RowLayout { Item { //spacer to make the label centre aligned in a row yet still elide and everything implicitWidth: (ring.visible? ring.width : 0) + (browse.visible? browse.width : 0) + parent.spacing } Battery { id: battery device: root.device } - + PlasmaComponents.Label { horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight text: (battery.available && battery.charge > -1) ? i18n("%1 (%2)", display, battery.displayString) : display Layout.fillWidth: true textFormat: Text.PlainText } //Find my phone PlasmaComponents.Button { FindMyPhone { id: findmyphone device: root.device } id: ring iconSource: "irc-voice" visible: findmyphone.available tooltip: i18n("Ring my phone") onClicked: { findmyphone.ring() } } //SFTP PlasmaComponents.Button { Sftp { id: sftp device: root.device } id: browse iconSource: "document-open-folder" visible: sftp.available tooltip: i18n("Browse this device") onClicked: { sftp.browse() } } height: browse.height width: parent.width } //RemoteKeyboard PlasmaComponents.ListItem { - visible: remoteKeyboardInput.available + visible: remoteKeyboard.remoteState width: parent.width Row { width: parent.width spacing: 5 PlasmaComponents.Label { id: remoteKeyboardLabel text: i18n("Remote Keyboard") } PlasmaComponents.TextField { id: remoteKeyboardInput - property bool available: remoteKeyboard.remoteState - - textColor: "black" height: parent.height width: parent.width - 5 - remoteKeyboardLabel.width verticalAlignment: TextInput.AlignVCenter - readOnly: !available - enabled: available style: TextFieldStyle { - textColor: "black" background: Rectangle { radius: 2 border.color: "gray" border.width: 1 - color: remoteKeyboardInput.available ? "white" : "lightgray" + color: "white" } } Keys.onPressed: { if (remoteKeyboard.available) remoteKeyboard.sendEvent(event); event.accepted = true; } } } } //Notifications PlasmaComponents.ListItem { visible: notificationsModel.count>0 enabled: true PlasmaComponents.Label { text: i18n("Notifications:") } PlasmaComponents.ToolButton { enabled: true visible: notificationsModel.isAnyDimissable; anchors.right: parent.right iconSource: "window-close" tooltip: i18n("Dismiss all notifications") onClicked: notificationsModel.dismissAll(); } } Repeater { id: notificationsView model: NotificationsModel { id: notificationsModel deviceId: root.device.id() } delegate: PlasmaComponents.ListItem { id: listitem enabled: true onClicked: checked = !checked PlasmaCore.IconItem { id: notificationIcon source: appIcon width: (valid && appIcon.length) ? dismissButton.width : 0 height: width anchors.left: parent.left } PlasmaComponents.Label { text: appName + ": " + (title.length>0 ? (appName==title?notitext:title+": "+notitext) : display) anchors.right: replyButton.left anchors.left: notificationIcon.right elide: listitem.checked ? Text.ElideNone : Text.ElideRight maximumLineCount: listitem.checked ? 0 : 1 wrapMode: Text.WordWrap } PlasmaComponents.ToolButton { id: replyButton visible: repliable enabled: repliable anchors.right: dismissButton.left iconSource: "mail-reply-sender" tooltip: i18n("Reply") onClicked: dbusInterface.reply(); } PlasmaComponents.ToolButton { id: dismissButton visible: notificationsModel.isAnyDimissable; enabled: dismissable anchors.right: parent.right iconSource: "window-close" tooltip: i18n("Dismiss") onClicked: dbusInterface.dismiss(); } } } //NOTE: More information could be displayed here } } diff --git a/plasmoid/package/metadata.desktop b/plasmoid/package/metadata.desktop index d79ca171..d8fe358d 100644 --- a/plasmoid/package/metadata.desktop +++ b/plasmoid/package/metadata.desktop @@ -1,99 +1,100 @@ [Desktop Entry] Name=KDE Connect Name[ar]=كدي المتّصل Name[ast]=KDE Connect Name[bg]=KDE Connect Name[bs]=Konekcija KDE Name[ca]=KDE Connect Name[ca@valencia]=KDE Connect Name[cs]=KDE Connect Name[da]=KDE Connect Name[de]=KDE-Connect Name[el]=KDE Connect Name[en_GB]=KDE Connect Name[es]=KDE Connect Name[et]=KDE Connect Name[eu]=KDE Connect Name[fi]=KDE Connect Name[fr]=KDE Connect Name[gl]=KDE Connect Name[he]=KDE Connect Name[hu]=KDE csatlakozás Name[it]=KDE Connect Name[ko]=KDE Connect Name[nl]=KDE Connect Name[nn]=KDE Connect Name[pl]=KDE Connect Name[pt]=KDE Connect Name[pt_BR]=KDE Connect Name[ro]=KDE Connect Name[ru]=KDE Connect Name[sk]=KDE Connect Name[sr]=КДЕ‑конекција Name[sr@ijekavian]=КДЕ‑конекција Name[sr@ijekavianlatin]=KDE‑konekcija Name[sr@latin]=KDE‑konekcija Name[sv]=KDE-anslut Name[tr]=KDE Connect Name[uk]=З’єднання KDE Name[x-test]=xxKDE Connectxx Name[zh_CN]=KDE Connect Name[zh_TW]=KDE 連線 Comment=Show notifications from your devices using KDE Connect Comment[ar]=أظهر الإخطارات من أجهزتك باستخدام «كدي المتّصل» Comment[ast]=Amuesa avisos de los tos preseos qu'usen KDE Connect Comment[bg]=Показване на уведомления от вашите устройства чрез KDE Connect Comment[bs]=Prikaži obavlještenja sa uređaja koji koriste KDE konekciju Comment[ca]=Mostra les notificacions dels vostres dispositius emprant el KDE Connect Comment[ca@valencia]=Mostra les notificacions dels vostres dispositius emprant el KDE Connect Comment[cs]=Zobrazit upozornění z vašich zařízení pomocí KDE Connect Comment[da]=Vis bekendtgørelser fra dine enheder med KDE Connect Comment[de]=Zeigt Benachrichtigungen von Ihren Geräten mit KDE-Connect Comment[el]=Προβολή ειδοποιήσεων από τις συσκευές σας με το KDE Connect Comment[en_GB]=Show notifications from your devices using KDE Connect Comment[es]=KDE Connect muestra las notificaciones de sus dispositivos Comment[et]=Seadmete märguannete näitamine KDE Connecti vahendusel Comment[eu]=Ikusi zure gailuetako jakinarazpenak KDE Connect erabiliz Comment[fi]=Näytä laitteidesi ilmoitukset KDE Connectilla Comment[fr]=Afficher les notifications provenant de vos périphériques à l'aide de KDE Connect Comment[gl]=Mostrar notificacións de dispositivos usando KDE Connect. Comment[he]=הראה התראות מההתקן שלך באמצעות KDE Connect Comment[hu]=Az eszközökről származó értesítések megjelenítése a KDE csatlakozás használatával Comment[it]=Mostra le notifiche dei tuoi dispositivi tramite KDE Connect Comment[ko]=KDE Connect로 장치에 표시된 알림 보기 Comment[nl]=Meldingen van uw apparaten met KDE Connect tonen Comment[nn]=Vis varslingar frå einingane dine med KDE Connect Comment[pl]=Pokazuje powiadomienia z urządzeń z KDE Connect Comment[pt]=Mostrar notificações dos seus dispositivos usando o KDE Connect Comment[pt_BR]=Mostrar notificações dos seus dispositivos usando o KDE Connect Comment[ru]=Просмотр уведомлений с мобильных устройств с помощью KDE Connect Comment[sk]=Zobraziť oznámenia z vašich zariadení pomocou KDE Connect Comment[sr]=Приказује обавештења са вашег уређаја помоћу КДЕ‑конекције Comment[sr@ijekavian]=Приказује обавештења са вашег уређаја помоћу КДЕ‑конекције Comment[sr@ijekavianlatin]=Prikazuje obaveštenja sa vašeg uređaja pomoću KDE‑konekcije Comment[sr@latin]=Prikazuje obaveštenja sa vašeg uređaja pomoću KDE‑konekcije Comment[sv]=Visa underrättelser från apparater med KDE anslut Comment[tr]=KDE Connect kullanılarak cihazınızdan bildirimleri görüntüleyin Comment[uk]=Показ сповіщень з ваших пристроїв за допомогою програми «З’єднання KDE» Comment[x-test]=xxShow notifications from your devices using KDE Connectxx Comment[zh_CN]=通过 KDE Connect 显示来自您的设备的通知 Comment[zh_TW]=使用KDE 連線來顯示您的裝置通知 Icon=kdeconnect Type=Service X-KDE-ServiceTypes=Plasma/Applet X-Plasma-API=declarativeappletscript X-Plasma-MainScript=ui/main.qml X-Plasma-NotificationArea=true +X-Plasma-NotificationAreaCategory=Hardware X-Plasma-ConfigPlugins=kcm_kdeconnect X-KDE-PluginInfo-Author=Albert Vaca Cintora X-KDE-PluginInfo-Email=albertvaka@gmail.com X-KDE-PluginInfo-Name=org.kde.kdeconnect X-KDE-PluginInfo-Version=0.1 X-KDE-PluginInfo-Website=http://albertvaka.wordpress.com X-KDE-PluginInfo-Category=System Information X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 785a71e0..e2f1d877 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,32 +1,34 @@ include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}/core) add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-plugins\") install(FILES kdeconnect_plugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) add_subdirectory(ping) add_subdirectory(clipboard) +add_subdirectory(contacts) add_subdirectory(telephony) add_subdirectory(share) add_subdirectory(notifications) add_subdirectory(battery) add_subdirectory(findmyphone) +if(Phonon4Qt5_FOUND) + add_subdirectory(findthisdevice) +endif() add_subdirectory(remotekeyboard) -if(WIN32) - add_subdirectory(mousepad_windows) -else() +add_subdirectory(mousepad) +if(NOT WIN32) add_subdirectory(runcommand) add_subdirectory(sendnotifications) add_subdirectory(pausemusic) add_subdirectory(mpriscontrol) - add_subdirectory(mousepad) add_subdirectory(screensaver-inhibit) add_subdirectory(sftp) endif() if(EXPERIMENTALAPP_ENABLED) add_subdirectory(remotecommands) add_subdirectory(mprisremote) add_subdirectory(remotecontrol) add_subdirectory(lockdevice) endif() #FIXME: If we split notifications in several files, they won't appear in the same group in the Notifications KCM install(FILES kdeconnect.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) diff --git a/plugins/contacts/CMakeLists.txt b/plugins/contacts/CMakeLists.txt new file mode 100644 index 00000000..9e3bbe47 --- /dev/null +++ b/plugins/contacts/CMakeLists.txt @@ -0,0 +1,11 @@ +set(kdeconnect_contacts_SRCS + contactsplugin.cpp +) + +kdeconnect_add_plugin(kdeconnect_contacts JSON kdeconnect_contacts.json SOURCES ${kdeconnect_contacts_SRCS}) + +target_link_libraries(kdeconnect_contacts + kdeconnectcore + Qt5::DBus + KF5::I18n +) \ No newline at end of file diff --git a/plugins/contacts/README b/plugins/contacts/README new file mode 100644 index 00000000..4ed41255 --- /dev/null +++ b/plugins/contacts/README @@ -0,0 +1,3 @@ +This plugin allows communicating with the paired device to access its contacts +book, either by downloading the entire list of contacts or by requesting a +specific contact \ No newline at end of file diff --git a/plugins/contacts/contactsplugin.cpp b/plugins/contacts/contactsplugin.cpp new file mode 100644 index 00000000..8306d724 --- /dev/null +++ b/plugins/contacts/contactsplugin.cpp @@ -0,0 +1,211 @@ +/** + * Copyright 2018 Simon Redman + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +K_PLUGIN_FACTORY_WITH_JSON(KdeConnectPluginFactory, "kdeconnect_contacts.json", + registerPlugin(); ) + +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_CONTACTS, "kdeconnect.plugin.contacts") + +ContactsPlugin::ContactsPlugin (QObject* parent, const QVariantList& args) : + KdeConnectPlugin(parent, args) { + vcardsPath = QString(*vcardsLocation).append("/kdeconnect-").append(device()->id()); + + // Register custom types with dbus + qRegisterMetaType("uID"); + qDBusRegisterMetaType(); + + qRegisterMetaType("uIDList_t"); + qDBusRegisterMetaType(); + + // Create the storage directory if it doesn't exist + if (!QDir().mkpath(vcardsPath)) { + qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:" << "Unable to create VCard directory"; + } + + this->synchronizeRemoteWithLocal(); + + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Contacts constructor for device " << device()->name(); +} + +ContactsPlugin::~ContactsPlugin () { + QDBusConnection::sessionBus().unregisterObject(dbusPath(), QDBusConnection::UnregisterTree); +// qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Contacts plugin destructor for device" << device()->name(); +} + +bool ContactsPlugin::receivePacket (const NetworkPacket& np) { + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Packet Received for device " << device()->name(); + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << np.body(); + + if (np.type() == PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS) { + return this->handleResponseUIDsTimestamps(np); + } else if (np.type() == PACKET_TYPE_CONTACTS_RESPONSE_VCARDS) { + return this->handleResponseVCards(np); + } else { + // Is this check necessary? + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Unknown package type received from device: " + << device()->name() << ". Maybe you need to upgrade KDE Connect?"; + return false; + } +} + +void ContactsPlugin::synchronizeRemoteWithLocal () { + this->sendRequest(PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMP); +} + +bool ContactsPlugin::handleResponseUIDsTimestamps (const NetworkPacket& np) { + if (!np.has("uids")) { + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:" + << "Malformed packet does not have uids key"; + return false; + } + uIDList_t uIDsToUpdate; + QDir vcardsDir(vcardsPath); + + // Get a list of all file info in this directory + // Clean out IDs returned from the remote. Anything leftover should be deleted + QFileInfoList localVCards = vcardsDir.entryInfoList( { "*.vcard", "*.vcf" }); + + const QStringList& uIDs = np.get("uids"); + + // Check local storage for the contacts: + // If the contact is not found in local storage, request its vcard be sent + // If the contact is in local storage but not reported, delete it + // If the contact is in local storage, compare its timestamp. If different, request the contact + for (const QString& ID : uIDs) { + QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION); + QFile vcardFile(filename); + + if (!QFile().exists(filename)) { + // We do not have a vcard for this contact. Request it. + uIDsToUpdate.push_back(ID); + continue; + } + + // Remove this file from the list of known files + QFileInfo fileInfo(vcardFile); + bool success = localVCards.removeOne(fileInfo); + Q_ASSERT(success); // We should have always been able to remove the existing file from our listing + + // Check if the vcard needs to be updated + if (!vcardFile.open(QIODevice::ReadOnly)) { + qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:" + << "Unable to open" << filename << "to read even though it was reported to exist"; + continue; + } + + QTextStream fileReadStream(&vcardFile); + QString line; + while (!fileReadStream.atEnd()) { + fileReadStream >> line; + // TODO: Check that the saved ID is the same as the one we were expecting. This requires parsing the VCard + if (!line.startsWith("X-KDECONNECT-TIMESTAMP:")) { + continue; + } + QStringList parts = line.split(":"); + QString timestamp = parts[1]; + + qint32 remoteTimestamp = np.get(ID); + qint32 localTimestamp = timestamp.toInt(); + + if (!(localTimestamp == remoteTimestamp)) { + uIDsToUpdate.push_back(ID); + } + } + } + + // Delete all locally-known files which were not reported by the remote device + for (const QFileInfo& unknownFile : localVCards) { + QFile toDelete(unknownFile.filePath()); + toDelete.remove(); + } + + this->sendRequestWithIDs(PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS, uIDsToUpdate); + + return true; +} + +bool ContactsPlugin::handleResponseVCards (const NetworkPacket& np) { + if (!np.has("uids")) { + qCDebug(KDECONNECT_PLUGIN_CONTACTS) + << "handleResponseVCards:" << "Malformed packet does not have uids key"; + return false; + } + + QDir vcardsDir(vcardsPath); + const QStringList& uIDs = np.get("uids"); + + // Loop over all IDs, extract the VCard from the packet and write the file + for (const auto& ID : uIDs) { + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Got VCard:" << np.get(ID); + QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION); + QFile vcardFile(filename); + bool vcardFileOpened = vcardFile.open(QIODevice::WriteOnly); // Want to smash anything that might have already been there + if (!vcardFileOpened) { + qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:" << "Unable to open" << filename; + continue; + } + + QTextStream fileWriteStream(&vcardFile); + const QString& vcard = np.get(ID); + fileWriteStream << vcard; + } + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:" << "Got" << uIDs.size() << "VCards"; + Q_EMIT localCacheSynchronized(uIDs); + return true; +} + +bool ContactsPlugin::sendRequest (const QString& packetType) { + NetworkPacket np(packetType); + bool success = sendPacket(np); + qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "sendRequest: Sending " << packetType << success; + + return success; +} + +bool ContactsPlugin::sendRequestWithIDs (const QString& packetType, const uIDList_t& uIDs) { + NetworkPacket np(packetType); + + np.set("uids", uIDs); + bool success = sendPacket(np); + return success; +} + +QString ContactsPlugin::dbusPath () const { + return "/modules/kdeconnect/devices/" + device()->id() + "/contacts"; +} + +#include "contactsplugin.moc" + diff --git a/plugins/contacts/contactsplugin.h b/plugins/contacts/contactsplugin.h new file mode 100644 index 00000000..9f4ee2f6 --- /dev/null +++ b/plugins/contacts/contactsplugin.h @@ -0,0 +1,164 @@ +/** + * Copyright 2018 Simon Redman + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + */ + +#ifndef CONTACTSPLUGIN_H +#define CONTACTSPLUGIN_H + +#include +#include + +#include + +/** + * Used to request the device send the unique ID and last-changed timestamp of every contact + */ +#define PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMP QStringLiteral("kdeconnect.contacts.request_all_uids_timestamps") + +/** + * Used to request the vcards for the contacts corresponding to a list of UIDs + * + * It shall contain the key "uids", which will have a list of uIDs (long int, as string) + */ +#define PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS QStringLiteral("kdeconnect.contacts.request_vcards_by_uid") + +/** + * Response indicating the package contains a list of all contact uIDs and last-changed timestamps + * + * It shall contain the key "uids", which will mark a list of uIDs (long int, as string) + * then, for each UID, there shall be a field with the key of that UID and the value of the timestamp (int, as string) + * + * For example: + * ( 'uids' : ['1', '3', '15'], + * '1' : '973486597', + * '3' : '973485443', + * '15' : '973492390' ) + * + * The returned IDs can be used in future requests for more information about the contact + */ +#define PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS QStringLiteral("kdeconnect.contacts.response_uids_timestamps") + +/** + * Response indicating the package contains a list of contact vcards + * + * It shall contain the key "uids", which will mark a list of uIDs (long int, as string) + * then, for each UID, there shall be a field with the key of that UID and the value of the remote's vcard for that contact + * + * For example: + * ( 'uids' : ['1', '3', '15'], + * '1' : 'BEGIN:VCARD\n....\nEND:VCARD', + * '3' : 'BEGIN:VCARD\n....\nEND:VCARD', + * '15' : 'BEGIN:VCARD\n....\nEND:VCARD' ) + */ +#define PACKET_TYPE_CONTACTS_RESPONSE_VCARDS QStringLiteral("kdeconnect.contacts.response_vcards") + +/** + * Where the synchronizer will write vcards and other metadata + * TODO: Per-device folders since each device *will* have different uIDs + */ +Q_GLOBAL_STATIC_WITH_ARGS( + QString, vcardsLocation, + (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + ("/kpeoplevcard"))) + +#define VCARD_EXTENSION QStringLiteral(".vcf") +#define METADATA_EXTENSION QStringLiteral(".meta") + +typedef QString uID; +Q_DECLARE_METATYPE(uID) + +typedef QStringList uIDList_t; +Q_DECLARE_METATYPE(uIDList_t) + +class Q_DECL_EXPORT ContactsPlugin : public KdeConnectPlugin { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.contacts") + +public: + explicit ContactsPlugin (QObject *parent, const QVariantList &args); + ~ContactsPlugin () override; + + bool receivePacket (const NetworkPacket& np) override; + void connected () override { + } + + QString dbusPath () const override; + +protected: + /** + * Path where this instance of the plugin stores its synchronized contacts + */ + QString vcardsPath; + +public Q_SLOTS: + + /** + * Query the remote device for all its uIDs and last-changed timestamps, then: + * Delete any contacts which are known locally but not reported by the remote + * Update any contacts which are known locally but have an older timestamp + * Add any contacts which are not known locally but are reported by the remote + */ + Q_SCRIPTABLE + void synchronizeRemoteWithLocal (); + +public: +Q_SIGNALS: + /** + * Emitted to indicate that we have locally cached all remote contacts + * + * @param newContacts The list of just-synchronized contacts + */ + Q_SCRIPTABLE + void localCacheSynchronized (const uIDList_t& newContacts); + +protected: + + /** + * Handle a packet of type PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS + * + * For every uID in the reply: + * Delete any from local storage if it does not appear in the reply + * Compare the modified timestamp for each in the reply and update any which should have changed + * Request the details any IDs which were not locally cached + */ + bool handleResponseUIDsTimestamps (const NetworkPacket&); + + /** + * Handle a packet of type PACKET_TYPE_CONTACTS_RESPONSE_VCARDS + */ + bool handleResponseVCards (const NetworkPacket&); + + /** + * Send a request-type packet which contains no body + * + * @return True if the send was successful, false otherwise + */ + bool sendRequest (const QString& packetType); + + /** + * Send a request-type packet which has a body with the key 'uids' and the value the list of + * specified uIDs + * + * @param packageType Type of package to send + * @param uIDs List of uIDs to request + * @return True if the send was successful, false otherwise + */ + bool sendRequestWithIDs (const QString& packetType, const uIDList_t& uIDs); +}; + +#endif // CONTACTSPLUGIN_H diff --git a/plugins/contacts/kdeconnect_contacts.json b/plugins/contacts/kdeconnect_contacts.json new file mode 100644 index 00000000..58ccbe5f --- /dev/null +++ b/plugins/contacts/kdeconnect_contacts.json @@ -0,0 +1,59 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Authors": [ + { + "Email": "simon@ergotech.com", + "Name": "Simon Redman", + "Name[x-test]": "xxSimon Redmanxx" + } + ], + "Description": "Synchronize Contacts Between the Desktop and the Connected Device", + "Description[ca@valencia]": "Sincronitza els contactes entre l'escriptori i el dispositiu connectat", + "Description[ca]": "Sincronitza els contactes entre l'escriptori i el dispositiu connectat", + "Description[cs]": "Synchronizovat kontakty mezi pracovní plochou a připojeným zařízením", + "Description[en_GB]": "Synchronise Contacts Between the Desktop and the Connected Device", + "Description[es]": "Sincronizar contactos entre el equipo de escritorio y el dispositivo conectado", + "Description[gl]": "Sincronizar contactos entre o escritorio e o dispositivo conectado", + "Description[it]": "Sincronizza contatti tra il desktop e il dispositivo connesso", + "Description[nl]": "Contactpersonen synchroniseren tussen het bureaublad en het verbonden apparaat", + "Description[nn]": "Synkroniser kontaktar mellom skrivebordet og tilkopla eining", + "Description[pt]": "Sincronizar os Contactos entre o Sistema e o Dispositivo Ligado", + "Description[sv]": "Synkronisera kontakter mellan skrivbordet och ansluten enhet", + "Description[uk]": "Синхронізація контактів між комп'ютером і з'єднаним пристроєм", + "Description[x-test]": "xxSynchronize Contacts Between the Desktop and the Connected Devicexx", + "Description[zh_TW]": "在電腦與連線裝置之間同步聯絡人", + "EnabledByDefault": true, + "Icon": "dialog-ok", + "Id": "kdeconnect_contacts", + "License": "GPL", + "Name": "Contacts", + "Name[ca@valencia]": "Contactes", + "Name[ca]": "Contactes", + "Name[cs]": "Kontakty", + "Name[es]": "Contactos", + "Name[gl]": "Contactos", + "Name[it]": "Contatti", + "Name[nl]": "Contactpersonen", + "Name[nn]": "Kontaktar", + "Name[pt]": "Contactos", + "Name[sv]": "Kontakter", + "Name[uk]": "Контакти", + "Name[x-test]": "xxContactsxx", + "Name[zh_CN]": "联系人", + "Name[zh_TW]": "聯絡人", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1", + "Website": "http://albertvaka.wordpress.com" + }, + "X-KdeConnect-OutgoingPacketType": [ + "kdeconnect.contacts.request_all_uids_timestamps", + "kdeconnect.contacts.request_vcards_by_uid" + ], + "X-KdeConnect-SupportedPacketType": [ + "kdeconnect.contacts.response_uids_timestamps", + "kdeconnect.contacts.response_vcards" + ] +} diff --git a/plugins/findthisdevice/CMakeLists.txt b/plugins/findthisdevice/CMakeLists.txt new file mode 100644 index 00000000..40cf3c65 --- /dev/null +++ b/plugins/findthisdevice/CMakeLists.txt @@ -0,0 +1,33 @@ +include_directories(${PHONON_INCLUDE_DIR}) + +set(kdeconnect_findthisdevice_SRCS + findthisdeviceplugin.cpp +) + +kdeconnect_add_plugin(kdeconnect_findthisdevice + JSON kdeconnect_findthisdevice.json + SOURCES ${kdeconnect_findthisdevice_SRCS}) + +target_link_libraries(kdeconnect_findthisdevice + kdeconnectcore + ${PHONON_LIBRARIES} + Qt5::Core + Qt5::DBus +) + + +set(kdeconnect_findthisdevice_config_SRCS findthisdevice_config.cpp) +ki18n_wrap_ui(kdeconnect_findthisdevice_config_SRCS findthisdevice_config.ui) + +add_library(kdeconnect_findthisdevice_config MODULE ${kdeconnect_findthisdevice_config_SRCS}) +target_link_libraries(kdeconnect_findthisdevice_config + kdeconnectpluginkcm + ${PHONON_LIBRARIES} + KF5::I18n + KF5::CoreAddons + KF5::ConfigWidgets + KF5::KIOWidgets # KUrlRequester +) + +install(TARGETS kdeconnect_findthisdevice_config DESTINATION ${KDE_INSTALL_PLUGINDIR}) +install(FILES kdeconnect_findthisdevice_config.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) diff --git a/plugins/findthisdevice/findthisdevice_config.cpp b/plugins/findthisdevice/findthisdevice_config.cpp new file mode 100644 index 00000000..b20237f0 --- /dev/null +++ b/plugins/findthisdevice/findthisdevice_config.cpp @@ -0,0 +1,123 @@ +/** + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + */ + +#include "findthisdevice_config.h" + +#include "ui_findthisdevice_config.h" +// Phonon +#include +// KF +#include +#include +// Qt +#include + + +K_PLUGIN_FACTORY(FindThisDeviceConfigFactory, registerPlugin();) + + +namespace { +namespace Strings { +inline QString defaultSound() { return QStringLiteral("Oxygen-Im-Phone-Ring.ogg"); } +} +} + +FindThisDeviceConfig::FindThisDeviceConfig(QWidget* parent, const QVariantList& args) + : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_findthisdevice_config")) + , m_ui(new Ui::FindThisDeviceConfigUi()) +{ + m_ui->setupUi(this); + + const QStringList soundDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + QStringLiteral("sounds"), + QStandardPaths::LocateDirectory); + if (!soundDirs.isEmpty()) { + m_ui->soundFileRequester->setStartDir(QUrl::fromLocalFile(soundDirs.last())); + } + + connect(m_ui->playSoundButton, &QToolButton::clicked, + this, &FindThisDeviceConfig::playSound); + connect(m_ui->soundFileRequester, &KUrlRequester::textChanged, + this, QOverload<>::of(&FindThisDeviceConfig::changed)); +} + +FindThisDeviceConfig::~FindThisDeviceConfig() +{ + delete m_ui; +} + + +void FindThisDeviceConfig::defaults() +{ + KCModule::defaults(); + + m_ui->soundFileRequester->setText(Strings::defaultSound()); + + Q_EMIT changed(true); +} + +void FindThisDeviceConfig::load() +{ + KCModule::load(); + + const QString ringTone = config()->get(QStringLiteral("ringtone"), Strings::defaultSound()); + m_ui->soundFileRequester->setText(ringTone); + + Q_EMIT changed(false); +} + +void FindThisDeviceConfig::save() +{ + config()->set(QStringLiteral("ringtone"), m_ui->soundFileRequester->text()); + + KCModule::save(); + + Q_EMIT changed(false); +} + +void FindThisDeviceConfig::playSound() +{ + const QString soundFilename = m_ui->soundFileRequester->text(); + + QUrl soundURL; + const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + for (const QString &dataLocation : dataLocations) { + soundURL = QUrl::fromUserInput(soundFilename, + dataLocation + QStringLiteral("/sounds"), + QUrl::AssumeLocalFile); + if (soundURL.isLocalFile()) { + if (QFile::exists(soundURL.toLocalFile())) { + break; + } + } else { + if (soundURL.isValid()) { + break; + } + } + soundURL.clear(); + } + + Phonon::MediaObject *media = Phonon::createPlayer(Phonon::NotificationCategory, soundURL); + media->play(); + connect(media, SIGNAL(finished()), media, SLOT(deleteLater())); +} + + +#include "findthisdevice_config.moc" diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/findthisdevice/findthisdevice_config.h similarity index 61% copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/findthisdevice/findthisdevice_config.h index 5950b804..6354ce4d 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/findthisdevice/findthisdevice_config.h @@ -1,42 +1,50 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Friedrich W. H. Kossebau * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H +#ifndef FINDTHISDEVICE_CONFIG_H +#define FINDTHISDEVICE_CONFIG_H -#include -#include +#include -#include +namespace Ui { +class FindThisDeviceConfigUi; +} -class MousepadPlugin - : public KdeConnectPlugin +class FindThisDeviceConfig + : public KdeConnectPluginKcm { Q_OBJECT - public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; + FindThisDeviceConfig(QWidget* parent, const QVariantList&); + ~FindThisDeviceConfig() override; + +public Q_SLOTS: + void save() override; + void load() override; + void defaults() override; + +private Q_SLOTS: + void playSound(); - bool receivePacket(const NetworkPacket& np) override; - void connected() override { } +private: + Ui::FindThisDeviceConfigUi* m_ui; }; #endif diff --git a/plugins/findthisdevice/findthisdevice_config.ui b/plugins/findthisdevice/findthisdevice_config.ui new file mode 100644 index 00000000..ef09e933 --- /dev/null +++ b/plugins/findthisdevice/findthisdevice_config.ui @@ -0,0 +1,72 @@ + + + FindThisDeviceConfigUi + + + + 0 + 0 + 569 + 140 + + + + + + + Discovery Utilities + + + + + + + + Sound to play: + + + + + + + + + + + + + + Select the sound to play + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + KUrlRequester + QWidget +
kurlrequester.h
+
+
+ + +
diff --git a/plugins/findthisdevice/findthisdeviceplugin.cpp b/plugins/findthisdevice/findthisdeviceplugin.cpp new file mode 100644 index 00000000..48bcf448 --- /dev/null +++ b/plugins/findthisdevice/findthisdeviceplugin.cpp @@ -0,0 +1,100 @@ +/** + * Copyright 2018 Friedrich W. H. Kossebau + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + */ + +#include "findthisdeviceplugin.h" + +// Phonon +#include +// KF +#include +// Qt +#include +#include +#include +#include + + +K_PLUGIN_FACTORY_WITH_JSON(KdeConnectPluginFactory, "kdeconnect_findthisdevice.json", + registerPlugin();) + +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_FINDTHISDEVICE, "kdeconnect.plugin.findthisdevice") + +namespace { +namespace Strings { +inline QString defaultSound() { return QStringLiteral("Oxygen-Im-Phone-Ring.ogg"); } +} +} + +FindThisDevicePlugin::FindThisDevicePlugin(QObject* parent, const QVariantList& args) + : KdeConnectPlugin(parent, args) +{ +} + +FindThisDevicePlugin::~FindThisDevicePlugin() = default; + +void FindThisDevicePlugin::connected() +{ +} + +bool FindThisDevicePlugin::receivePacket(const NetworkPacket& np) +{ + Q_UNUSED(np); + + const QString soundFilename = config()->get(QStringLiteral("ringtone"), Strings::defaultSound()); + + QUrl soundURL; + const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + for (const QString &dataLocation : dataLocations) { + soundURL = QUrl::fromUserInput(soundFilename, + dataLocation + QStringLiteral("/sounds"), + QUrl::AssumeLocalFile); + if (soundURL.isLocalFile()) { + if (QFile::exists(soundURL.toLocalFile())) { + break; + } + } else { + if (soundURL.isValid()) { + break; + } + } + soundURL.clear(); + } + if (soundURL.isEmpty()) { + qCWarning(KDECONNECT_PLUGIN_FINDTHISDEVICE) << "Not playing sounds, could not find ring tone" << soundFilename; + return true; + } + + Phonon::MediaObject *media = Phonon::createPlayer(Phonon::NotificationCategory, soundURL); // or CommunicationCategory? + media->play(); + connect(media, &Phonon::MediaObject::finished, media, &QObject::deleteLater); + + // TODO: by-pass volume settings in case it is muted + // TODO: ensure to use built-in loudspeakers + + return true; +} + +QString FindThisDevicePlugin::dbusPath() const +{ + return "/modules/kdeconnect/devices/" + device()->id() + "/findthisdevice"; +} + +#include "findthisdeviceplugin.moc" + diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/findthisdevice/findthisdeviceplugin.h similarity index 62% copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/findthisdevice/findthisdeviceplugin.h index 5950b804..3df26590 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/findthisdevice/findthisdeviceplugin.h @@ -1,42 +1,47 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Friedrich W. H. Kossebau * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H - -#include -#include +#ifndef FINDTHISDEVICEPLUGIN_H +#define FINDTHISDEVICEPLUGIN_H #include +// Qt +#include + +#define PACKET_TYPE_FINDMYPHONE_REQUEST QStringLiteral("kdeconnect.findmyphone.request") + +Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_FINDTHISDEVICE) -class MousepadPlugin +class FindThisDevicePlugin : public KdeConnectPlugin { Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.findthisdevice") public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; + explicit FindThisDevicePlugin(QObject* parent, const QVariantList& args); + ~FindThisDevicePlugin() override; + QString dbusPath() const override; + void connected() override; bool receivePacket(const NetworkPacket& np) override; - void connected() override { } }; #endif diff --git a/plugins/findthisdevice/kdeconnect_findthisdevice.json b/plugins/findthisdevice/kdeconnect_findthisdevice.json new file mode 100644 index 00000000..490c8734 --- /dev/null +++ b/plugins/findthisdevice/kdeconnect_findthisdevice.json @@ -0,0 +1,24 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "kossebau@kde.org", + "Name": "Friedrich W. H. Kossebau" + } + ], + "Description": "Find this device by making it play an alarm sound", + "EnabledByDefault": true, + "Icon": "edit-find", + "Id": "kdeconnect_findthisdevice", + "License": "GPL", + "Name": "Find this device", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1", + "Website": "https://kde.org" + }, + "X-KdeConnect-SupportedPacketType": [ + "kdeconnect.findmyphone.request" + ] +} diff --git a/plugins/findthisdevice/kdeconnect_findthisdevice_config.desktop b/plugins/findthisdevice/kdeconnect_findthisdevice_config.desktop new file mode 100644 index 00000000..92b0c133 --- /dev/null +++ b/plugins/findthisdevice/kdeconnect_findthisdevice_config.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kdeconnect_findthisdevice_config +X-KDE-ParentComponents=kdeconnect_findthisdevice + +Name=Find This Device plugin settings + +Categories=Qt;KDE;X-KDE-settings-kdeconnect; diff --git a/plugins/mousepad/CMakeLists.txt b/plugins/mousepad/CMakeLists.txt index ab9913ff..8ea00470 100644 --- a/plugins/mousepad/CMakeLists.txt +++ b/plugins/mousepad/CMakeLists.txt @@ -1,32 +1,39 @@ -find_package(LibFakeKey) -set_package_properties(LibFakeKey PROPERTIES DESCRIPTION "fake key events" - URL "https://www.yoctoproject.org/tools-resources/projects/matchbox" - TYPE OPTIONAL - PURPOSE "Needed for the remote mousepad plugin" - ) +if(UNIX) + find_package(KF5 ${KF5_MIN_VERSION} QUIET OPTIONAL_COMPONENTS Wayland) -if (LibFakeKey_FOUND) - find_package(XTest REQUIRED) - find_package(X11 REQUIRED) - find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS X11Extras) + find_package(LibFakeKey QUIET) + set_package_properties(LibFakeKey PROPERTIES DESCRIPTION "fake key events" + URL "https://www.yoctoproject.org/tools-resources/projects/matchbox" + TYPE OPTIONAL + PURPOSE "Needed for the remote mousepad plugin" + ) + if (LibFakeKey_FOUND) + find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS X11Extras) + find_package(XTest REQUIRED) + find_package(X11 REQUIRED) + include_directories(${XTEST_INCLUDE_DIRS} ${X11_INCLUDE_DIR} ${LibFakeKey_INCLUDE_DIRS}) + endif() endif() -find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Wayland) +set(HAVE_WINDOWS ${WIN32}) set(HAVE_X11 ${LibFakeKey_FOUND}) set(HAVE_WAYLAND ${KF5Wayland_FOUND}) configure_file(config-mousepad.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-mousepad.h ) -kdeconnect_add_plugin(kdeconnect_mousepad JSON kdeconnect_mousepad.json SOURCES mousepadplugin.cpp) +kdeconnect_add_plugin(kdeconnect_mousepad JSON kdeconnect_mousepad.json SOURCES mousepadplugin.cpp abstractremoteinput.cpp) +target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt5::Gui KF5::I18n) -if (HAVE_X11) - include_directories(${XTEST_INCLUDE_DIRS} ${X11_INCLUDE_DIR} ${LibFakeKey_INCLUDE_DIRS}) -endif() -target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt5::Gui KF5::I18n) +if (HAVE_WINDOWS) + target_sources(kdeconnect_mousepad PUBLIC windowsremoteinput.cpp) +endif() if(HAVE_WAYLAND) + target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp) target_link_libraries(kdeconnect_mousepad KF5::WaylandClient) endif() + if(HAVE_X11) + target_sources(kdeconnect_mousepad PUBLIC x11remoteinput.cpp) target_link_libraries(kdeconnect_mousepad Qt5::X11Extras ${X11_LIBRARIES} ${XTEST_LIBRARIES} ${LibFakeKey_LIBRARIES}) endif() diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/mousepad/abstractremoteinput.cpp similarity index 65% copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/abstractremoteinput.cpp index 5950b804..b22d9080 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/abstractremoteinput.cpp @@ -1,42 +1,28 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Albert Vaca Cintora * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H -#include -#include +#include "abstractremoteinput.h" -#include - -class MousepadPlugin - : public KdeConnectPlugin +AbstractRemoteInput::AbstractRemoteInput(QObject* parent) + : QObject(parent) { - Q_OBJECT - -public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; - - bool receivePacket(const NetworkPacket& np) override; - void connected() override { } -}; -#endif +} diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/mousepad/abstractremoteinput.h similarity index 68% copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/abstractremoteinput.h index 5950b804..f6360310 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/abstractremoteinput.h @@ -1,42 +1,39 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Albert Vaca Cintora * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H +#ifndef ABSTRACTREMOTEINPUT_H +#define ABSTRACTREMOTEINPUT_H #include -#include -#include +#include -class MousepadPlugin - : public KdeConnectPlugin +class AbstractRemoteInput + : public QObject { Q_OBJECT - public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; + explicit AbstractRemoteInput(QObject* parent = nullptr); - bool receivePacket(const NetworkPacket& np) override; - void connected() override { } + virtual bool handlePacket(const NetworkPacket& np) = 0; + virtual bool hasKeyboardSupport() { return false; }; }; #endif diff --git a/plugins/mousepad/config-mousepad.h.cmake b/plugins/mousepad/config-mousepad.h.cmake index e962b6ab..91dbd4d8 100644 --- a/plugins/mousepad/config-mousepad.h.cmake +++ b/plugins/mousepad/config-mousepad.h.cmake @@ -1,2 +1,3 @@ #cmakedefine01 HAVE_WAYLAND #cmakedefine01 HAVE_X11 +#cmakedefine01 HAVE_WINDOWS diff --git a/plugins/mousepad/kdeconnect_mousepad.json b/plugins/mousepad/kdeconnect_mousepad.json index dc7dba1c..7f781855 100644 --- a/plugins/mousepad/kdeconnect_mousepad.json +++ b/plugins/mousepad/kdeconnect_mousepad.json @@ -1,96 +1,98 @@ { "KPlugin": { "Authors": [ { "Email": "ahmedibrahimkhali@gmail.com", "Name": "Ahmed I. Khalil", "Name[ar]": "أحمد إبراهيم خليل", "Name[sr@ijekavian]": "Ахмед И. Калил", "Name[sr@ijekavianlatin]": "Ahmed I. Kalil", "Name[sr@latin]": "Ahmed I. Kalil", "Name[sr]": "Ахмед И. Калил", "Name[x-test]": "xxAhmed I. Khalilxx" } ], "Description": "Use your phone as a touchpad and keyboard", "Description[ar]": "استخدم الهاتف كفأرة ولوحة مفاتيح", "Description[ca@valencia]": "Empra el telèfon com un ratolí tàctil i teclat", "Description[ca]": "Empra el telèfon com un ratolí tàctil i teclat", "Description[cs]": "Používejte svůj telefon jako touchpad a klávesnici", "Description[da]": "Brug din telefon som touchpad og tastatur", "Description[de]": "Verwendet Ihr Handy als Touchpad und Tastatur", "Description[el]": "Χρήση του τηλεφώνου σας ως οθόνη αφής και πληκτρολογίου", "Description[es]": "Usar teléfono como panel táctil y teclado", "Description[et]": "Telefoni kasutamine puutepadja ja klaviatuurina", "Description[eu]": "Erabili zure telefonoa touchpad eta teklatu gisa", "Description[fi]": "Käytä puhelintasi kosketuslevynä ja näppäimistönä", "Description[fr]": "Utilisez votre téléphone comme un pavé tactile et un clavier", "Description[gl]": "Usar o teléfono móbil como área táctil e teclado.", "Description[hu]": "A telefon használata érintőtáblaként és billentyűzetként", "Description[it]": "Usa il telefono come touchpad e tastiera", "Description[ko]": "휴대폰을 터치패드와 키보드로 사용", "Description[nl]": "Uw telefoon gebruiken als een touchpad en toetsenbord", "Description[nn]": "Bruk telefonen som styreplate og tastatur", "Description[pl]": "Używaj swojego telefonu jako gładzika i klawiatury", "Description[pt]": "Use o seu telefone como um rato e teclado por toque", "Description[pt_BR]": "Use seu telefone como um touchpad e teclado", "Description[ru]": "Использование телефона в качестве сенсорной панели и клавиатуры", "Description[sk]": "Použite váš telefón ako touchpad a klávesnicu", "Description[sr@ijekavian]": "Користите телефон као додирник и тастатуру", "Description[sr@ijekavianlatin]": "Koristite telefon kao dodirnik i tastaturu", "Description[sr@latin]": "Koristite telefon kao dodirnik i tastaturu", "Description[sr]": "Користите телефон као додирник и тастатуру", "Description[sv]": "Använd telefonen som mus och tangentbord", "Description[tr]": "Telefonunuzu dokunmatik yüzey ve klayve olarak kullanın", "Description[uk]": "Скористайтеся телефоном як замінником сенсорної панелі і клавіатури", "Description[x-test]": "xxUse your phone as a touchpad and keyboardxx", "Description[zh_CN]": "用您的手机作为触摸板和键盘", "Description[zh_TW]": "讓您的智慧型手機當作觸碰板與鍵盤", "EnabledByDefault": true, "Icon": "edit-select", "Id": "kdeconnect_mousepad", "License": "GPL", "Name": "Virtual input", "Name[ar]": "دخل وهميّ", "Name[ca@valencia]": "Entrada virtual", "Name[ca]": "Entrada virtual", "Name[cs]": "Virtuální vstup", "Name[da]": "Virtuelt input", "Name[de]": "Virtuelle Eingabe", "Name[el]": "Εικονικά στοιχεία εισόδου", "Name[es]": "Entrada virtual", "Name[et]": "Virtuaalsisestus", "Name[eu]": "Sarrera birtuala", "Name[fi]": "Virtuaalinen syöttö", "Name[fr]": "Entrée virtuelle", "Name[gl]": "Entrada virtual", "Name[hu]": "Virtuális bevitel", "Name[it]": "Inserimento virtuale", "Name[ko]": "가상 입력", "Name[nl]": "Virtuele invoer", "Name[nn]": "Virtuelt tastatur", "Name[pl]": "Wirtualna obsługa", "Name[pt]": "Entrada virtual", "Name[pt_BR]": "Entrada virtual", "Name[ru]": "Виртуальный ввод", "Name[sk]": "Virtuálny vstup", "Name[sr@ijekavian]": "Виртуелни унос", "Name[sr@ijekavianlatin]": "Virtuelni unos", "Name[sr@latin]": "Virtuelni unos", "Name[sr]": "Виртуелни унос", "Name[sv]": "Virtuell inmatning", "Name[tr]": "Sanal içe aktarma", "Name[uk]": "Віртуальне введення даних", "Name[x-test]": "xxVirtual inputxx", "Name[zh_CN]": "虚拟输入", "Name[zh_TW]": "虛擬輸入", "ServiceTypes": [ "KdeConnect/Plugin" ], "Version": "0.1" }, - "X-KdeConnect-OutgoingPacketType": [], + "X-KdeConnect-OutgoingPacketType": [ + "kdeconnect.mousepad.keyboardstate" + ], "X-KdeConnect-SupportedPacketType": [ "kdeconnect.mousepad.request" ] } diff --git a/plugins/mousepad/mousepadplugin.cpp b/plugins/mousepad/mousepadplugin.cpp index 9c16498d..76eb01b8 100644 --- a/plugins/mousepad/mousepadplugin.cpp +++ b/plugins/mousepad/mousepadplugin.cpp @@ -1,337 +1,89 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Albert Vaca Cintora * Copyright 2015 Martin Gräßlin + * Copyright 2014 Ahmed I. Khalil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "mousepadplugin.h" #include #include -#include #include -#if HAVE_X11 -#include -#include -#include -#include +#if HAVE_WINDOWS + #include "windowsremoteinput.h" +#else + #if HAVE_X11 + #include "x11remoteinput.h" + #endif + #if HAVE_WAYLAND + #include "waylandremoteinput.h" + #endif #endif -#if HAVE_WAYLAND -#include -#include -#include -#endif K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mousepad.json", registerPlugin< MousepadPlugin >(); ) -enum MouseButtons { - LeftMouseButton = 1, - MiddleMouseButton = 2, - RightMouseButton = 3, - MouseWheelUp = 4, - MouseWheelDown = 5 -}; - -#if HAVE_X11 -//Translation table to keep in sync within all the implementations -int SpecialKeysMap[] = { - 0, // Invalid - XK_BackSpace, // 1 - XK_Tab, // 2 - XK_Linefeed, // 3 - XK_Left, // 4 - XK_Up, // 5 - XK_Right, // 6 - XK_Down, // 7 - XK_Page_Up, // 8 - XK_Page_Down, // 9 - XK_Home, // 10 - XK_End, // 11 - XK_Return, // 12 - XK_Delete, // 13 - XK_Escape, // 14 - XK_Sys_Req, // 15 - XK_Scroll_Lock, // 16 - 0, // 17 - 0, // 18 - 0, // 19 - 0, // 20 - XK_F1, // 21 - XK_F2, // 22 - XK_F3, // 23 - XK_F4, // 24 - XK_F5, // 25 - XK_F6, // 26 - XK_F7, // 27 - XK_F8, // 28 - XK_F9, // 29 - XK_F10, // 30 - XK_F11, // 31 - XK_F12, // 32 -}; -#endif - -template -size_t arraySize(T(&arr)[N]) { (void)arr; return N; } - MousepadPlugin::MousepadPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) -#if HAVE_X11 - , m_fakekey(nullptr) - , m_x11(QX11Info::isPlatformX11()) -#else - , m_x11(false) -#endif -#if HAVE_WAYLAND - , m_waylandInput(nullptr) - , m_waylandAuthenticationRequested(false) -#endif -{ -#if HAVE_WAYLAND - setupWaylandIntegration(); -#endif -} - -MousepadPlugin::~MousepadPlugin() + , m_impl(nullptr) { -#if HAVE_X11 - if (m_fakekey) { - free(m_fakekey); - m_fakekey = nullptr; +#if HAVE_WINDOWS + m_impl = new WindowsRemoteInput(this); +#else + #if HAVE_X11 + if (QGuiApplication::platformName() == QLatin1String("xcb")) { + m_impl = new X11RemoteInput(this); } -#endif -} + #endif -bool MousepadPlugin::receivePacket(const NetworkPacket& np) -{ -#if HAVE_X11 - if (m_x11) { - return handlePacketX11(np); - } -#endif -#if HAVE_WAYLAND - if (m_waylandInput) { - if (!m_waylandAuthenticationRequested) { - m_waylandInput->authenticate(i18n("KDE Connect"), i18n("Use your phone as a touchpad and keyboard")); - m_waylandAuthenticationRequested = true; - } - handPacketWayland(np); + #if HAVE_WAYLAND + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { + m_impl = new WaylandRemoteInput(this); } + #endif #endif - return false; -} -#if HAVE_X11 -bool isLeftHanded(Display * display) -{ - unsigned char map[20]; - int num_buttons = XGetPointerMapping(display, map, 20); - if( num_buttons == 1 ) { - return false; - } else if( num_buttons == 2 ) { - return ( (int)map[0] == 2 && (int)map[1] == 1 ); - } else { - return ( (int)map[0] == 3 && (int)map[2] == 1 ); + if (!m_impl) { + qDebug() << "KDE Connect was built without" << QGuiApplication::platformName() << "support"; } + } -#endif -#if HAVE_X11 -bool MousepadPlugin::handlePacketX11(const NetworkPacket& np) +MousepadPlugin::~MousepadPlugin() { - //qDebug() << np.serialize(); - - //TODO: Split mouse/keyboard in two different plugins to avoid this big if statement - - float dx = np.get(QStringLiteral("dx"), 0); - float dy = np.get(QStringLiteral("dy"), 0); - - bool isSingleClick = np.get(QStringLiteral("singleclick"), false); - bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); - bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); - bool isRightClick = np.get(QStringLiteral("rightclick"), false); - bool isSingleHold = np.get(QStringLiteral("singlehold"), false); - bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); - bool isScroll = np.get(QStringLiteral("scroll"), false); - QString key = np.get(QStringLiteral("key"), QLatin1String("")); - int specialKey = np.get(QStringLiteral("specialKey"), 0); - - if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { - Display* display = QX11Info::display(); - if(!display) { - return false; - } - - bool leftHanded = isLeftHanded(display); - int mainMouseButton = leftHanded? RightMouseButton : LeftMouseButton; - int secondaryMouseButton = leftHanded? LeftMouseButton : RightMouseButton; - - if (isSingleClick) { - XTestFakeButtonEvent(display, mainMouseButton, True, 0); - XTestFakeButtonEvent(display, mainMouseButton, False, 0); - } else if (isDoubleClick) { - XTestFakeButtonEvent(display, mainMouseButton, True, 0); - XTestFakeButtonEvent(display, mainMouseButton, False, 0); - XTestFakeButtonEvent(display, mainMouseButton, True, 0); - XTestFakeButtonEvent(display, mainMouseButton, False, 0); - } else if (isMiddleClick) { - XTestFakeButtonEvent(display, MiddleMouseButton, True, 0); - XTestFakeButtonEvent(display, MiddleMouseButton, False, 0); - } else if (isRightClick) { - XTestFakeButtonEvent(display, secondaryMouseButton, True, 0); - XTestFakeButtonEvent(display, secondaryMouseButton, False, 0); - } else if (isSingleHold){ - //For drag'n drop - XTestFakeButtonEvent(display, mainMouseButton, True, 0); - } else if (isSingleRelease){ - //For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofnes. - XTestFakeButtonEvent(display, mainMouseButton, False, 0); - } else if (isScroll) { - if (dy < 0) { - XTestFakeButtonEvent(display, MouseWheelDown, True, 0); - XTestFakeButtonEvent(display, MouseWheelDown, False, 0); - } else if (dy > 0) { - XTestFakeButtonEvent(display, MouseWheelUp, True, 0); - XTestFakeButtonEvent(display, MouseWheelUp, False, 0); - } - } else if (!key.isEmpty() || specialKey) { - - bool ctrl = np.get(QStringLiteral("ctrl"), false); - bool alt = np.get(QStringLiteral("alt"), false); - bool shift = np.get(QStringLiteral("shift"), false); - - if (ctrl) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Control_L), True, 0); - if (alt) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Alt_L), True, 0); - if (shift) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Shift_L), True, 0); - - if (specialKey) - { - if (specialKey >= (int)arraySize(SpecialKeysMap)) { - qWarning() << "Unsupported special key identifier"; - return false; - } - - int keycode = XKeysymToKeycode(display, SpecialKeysMap[specialKey]); - - XTestFakeKeyEvent (display, keycode, True, 0); - XTestFakeKeyEvent (display, keycode, False, 0); - - } else { - - if (!m_fakekey) { - m_fakekey = fakekey_init(display); - if (!m_fakekey) { - qWarning() << "Failed to initialize libfakekey"; - return false; - } - } - - //We use fakekey here instead of XTest (above) because it can handle utf characters instead of keycodes. - for (int i=0;ihandlePacket(np); + } else { + return false; } - Registry* registry = new Registry(this); - registry->create(connection); - connect(registry, &Registry::fakeInputAnnounced, this, - [this, registry] (quint32 name, quint32 version) { - m_waylandInput = registry->createFakeInput(name, version, this); - } - ); - registry->setup(); } -bool MousepadPlugin::handPacketWayland(const NetworkPacket& np) -{ - const float dx = np.get(QStringLiteral("dx"), 0); - const float dy = np.get(QStringLiteral("dy"), 0); - - const bool isSingleClick = np.get(QStringLiteral("singleclick"), false); - const bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); - const bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); - const bool isRightClick = np.get(QStringLiteral("rightclick"), false); - const bool isSingleHold = np.get(QStringLiteral("singlehold"), false); - const bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); - const bool isScroll = np.get(QStringLiteral("scroll"), false); - const QString key = np.get(QStringLiteral("key"), QLatin1String("")); - const int specialKey = np.get(QStringLiteral("specialKey"), 0); - - if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { - if (isSingleClick) { - m_waylandInput->requestPointerButtonClick(Qt::LeftButton); - } else if (isDoubleClick) { - m_waylandInput->requestPointerButtonClick(Qt::LeftButton); - m_waylandInput->requestPointerButtonClick(Qt::LeftButton); - } else if (isMiddleClick) { - m_waylandInput->requestPointerButtonClick(Qt::MiddleButton); - } else if (isRightClick) { - m_waylandInput->requestPointerButtonClick(Qt::RightButton); - } else if (isSingleHold){ - //For drag'n drop - m_waylandInput->requestPointerButtonPress(Qt::LeftButton); - } else if (isSingleRelease){ - //For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofnes. - m_waylandInput->requestPointerButtonRelease(Qt::LeftButton); - } else if (isScroll) { - m_waylandInput->requestPointerAxis(Qt::Vertical, dy); - } else if (!key.isEmpty() || specialKey) { - // TODO: implement key support - } - - } else { //Is a mouse move event - m_waylandInput->requestPointerMove(QSizeF(dx, dy)); - } - return true; +void MousepadPlugin::connected() { + NetworkPacket np(PACKET_TYPE_MOUSEPAD_KEYBOARDSTATE); + np.set(QStringLiteral("state"), m_impl->hasKeyboardSupport()); + sendPacket(np); } -#endif #include "mousepadplugin.moc" diff --git a/plugins/mousepad/mousepadplugin.h b/plugins/mousepad/mousepadplugin.h index ca0b23f8..f230cc57 100644 --- a/plugins/mousepad/mousepadplugin.h +++ b/plugins/mousepad/mousepadplugin.h @@ -1,71 +1,50 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Albert Vaca Cintora + * Copyright 2015 Martin Gräßlin + * Copyright 2014 Ahmed I. Khalil * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #ifndef MOUSEPADPLUGIN_H #define MOUSEPADPLUGIN_H -#include #include #include -struct FakeKey; +#include "abstractremoteinput.h" -#if HAVE_WAYLAND -namespace KWayland -{ -namespace Client -{ -class FakeInput; -} -} -#endif +#define PACKET_TYPE_MOUSEPAD_KEYBOARDSTATE QLatin1String("kdeconnect.mousepad.keyboardstate") class MousepadPlugin : public KdeConnectPlugin { Q_OBJECT public: explicit MousepadPlugin(QObject* parent, const QVariantList& args); ~MousepadPlugin() override; bool receivePacket(const NetworkPacket& np) override; - void connected() override { } + void connected() override; private: -#if HAVE_X11 - bool handlePacketX11(const NetworkPacket& np); -#endif -#if HAVE_WAYLAND - void setupWaylandIntegration(); - bool handPacketWayland(const NetworkPacket& np); -#endif + AbstractRemoteInput* m_impl; -#if HAVE_X11 - FakeKey* m_fakekey; -#endif - const bool m_x11; -#if HAVE_WAYLAND - KWayland::Client::FakeInput* m_waylandInput; - bool m_waylandAuthenticationRequested; -#endif }; #endif diff --git a/plugins/mousepad/waylandremoteinput.cpp b/plugins/mousepad/waylandremoteinput.cpp new file mode 100644 index 00000000..6b101deb --- /dev/null +++ b/plugins/mousepad/waylandremoteinput.cpp @@ -0,0 +1,103 @@ +/** + * Copyright 2015 Martin Gräßlin + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + */ + +#include "waylandremoteinput.h" + +#include +#include + +#include +#include +#include +#include + +WaylandRemoteInput::WaylandRemoteInput(QObject* parent) + : AbstractRemoteInput(parent) + , m_waylandInput(nullptr) + , m_waylandAuthenticationRequested(false) +{ + using namespace KWayland::Client; + ConnectionThread* connection = ConnectionThread::fromApplication(this); + if (!connection) { + qDebug() << "failed to get the Connection from Qt, Wayland remote input will not work"; + return; + } + Registry* registry = new Registry(this); + registry->create(connection); + connect(registry, &Registry::fakeInputAnnounced, this, + [this, registry] (quint32 name, quint32 version) { + m_waylandInput = registry->createFakeInput(name, version, this); + } + ); + registry->setup(); +} + +bool WaylandRemoteInput::handlePacket(const NetworkPacket& np) +{ + if (!m_waylandInput) { + return false; + } + + if (!m_waylandAuthenticationRequested) { + m_waylandInput->authenticate(i18n("KDE Connect"), i18n("Use your phone as a touchpad and keyboard")); + m_waylandAuthenticationRequested = true; + } + + const float dx = np.get(QStringLiteral("dx"), 0); + const float dy = np.get(QStringLiteral("dy"), 0); + + const bool isSingleClick = np.get(QStringLiteral("singleclick"), false); + const bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); + const bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); + const bool isRightClick = np.get(QStringLiteral("rightclick"), false); + const bool isSingleHold = np.get(QStringLiteral("singlehold"), false); + const bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); + const bool isScroll = np.get(QStringLiteral("scroll"), false); + const QString key = np.get(QStringLiteral("key"), QLatin1String("")); + const int specialKey = np.get(QStringLiteral("specialKey"), 0); + + if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { + + if (isSingleClick) { + m_waylandInput->requestPointerButtonClick(Qt::LeftButton); + } else if (isDoubleClick) { + m_waylandInput->requestPointerButtonClick(Qt::LeftButton); + m_waylandInput->requestPointerButtonClick(Qt::LeftButton); + } else if (isMiddleClick) { + m_waylandInput->requestPointerButtonClick(Qt::MiddleButton); + } else if (isRightClick) { + m_waylandInput->requestPointerButtonClick(Qt::RightButton); + } else if (isSingleHold){ + //For drag'n drop + m_waylandInput->requestPointerButtonPress(Qt::LeftButton); + } else if (isSingleRelease){ + //For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofnes. + m_waylandInput->requestPointerButtonRelease(Qt::LeftButton); + } else if (isScroll) { + m_waylandInput->requestPointerAxis(Qt::Vertical, dy); + } else if (!key.isEmpty() || specialKey) { + // TODO: implement key support + } + + } else { //Is a mouse move event + m_waylandInput->requestPointerMove(QSizeF(dx, dy)); + } + return true; +} diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/mousepad/waylandremoteinput.h similarity index 62% copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/waylandremoteinput.h index 5950b804..31b6b1f2 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/waylandremoteinput.h @@ -1,42 +1,51 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Albert Vaca Cintora * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H +#ifndef WAYLANDREMOTEINPUT_H +#define WAYLANDREMOTEINPUT_H -#include -#include +#include "abstractremoteinput.h" -#include +namespace KWayland +{ + namespace Client + { + class FakeInput; + } +} -class MousepadPlugin - : public KdeConnectPlugin +class WaylandRemoteInput + : public AbstractRemoteInput { Q_OBJECT public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; + explicit WaylandRemoteInput(QObject* parent); + + bool handlePacket(const NetworkPacket& np) override; + +private: + void setupWaylandIntegration(); - bool receivePacket(const NetworkPacket& np) override; - void connected() override { } + KWayland::Client::FakeInput* m_waylandInput; + bool m_waylandAuthenticationRequested; }; #endif diff --git a/plugins/mousepad_windows/mousepadplugin_windows.cpp b/plugins/mousepad/windowsremoteinput.cpp similarity index 89% rename from plugins/mousepad_windows/mousepadplugin_windows.cpp rename to plugins/mousepad/windowsremoteinput.cpp index 844eae70..ef945336 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.cpp +++ b/plugins/mousepad/windowsremoteinput.cpp @@ -1,150 +1,140 @@ /** - * Copyright 2014 Ahmed I. Khalil - * Copyright 2015 Martin Gräßlin + * Copyright 2018 Albert Vaca Cintora * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#include "mousepadplugin_windows.h" -#include -#include -#include -#include +#include "windowsremoteinput.h" + #include #include -K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mousepad.json", registerPlugin< MousepadPlugin >(); ) - -MousepadPlugin::MousepadPlugin(QObject* parent, const QVariantList& args) - : KdeConnectPlugin(parent, args) +WindowsRemoteInput::WindowsRemoteInput(QObject* parent) + : AbstractRemoteInput(parent) { -} -MousepadPlugin::~MousepadPlugin() -{ } -bool MousepadPlugin::receivePacket(const NetworkPacket& np) +bool WindowsRemoteInput::handlePacket(const NetworkPacket& np) { float dx = np.get(QStringLiteral("dx"), 0); float dy = np.get(QStringLiteral("dy"), 0); bool isSingleClick = np.get(QStringLiteral("singleclick"), false); bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); bool isRightClick = np.get(QStringLiteral("rightclick"), false); bool isSingleHold = np.get(QStringLiteral("singlehold"), false); bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); bool isScroll = np.get(QStringLiteral("scroll"), false); QString key = np.get(QStringLiteral("key"), QLatin1String("")); int specialKey = np.get(QStringLiteral("specialKey"), 0); if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { INPUT input={0}; input.type = INPUT_MOUSE; if (isSingleClick) { input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; ::SendInput(1,&input,sizeof(INPUT)); input.mi.dwFlags = MOUSEEVENTF_LEFTUP; ::SendInput(1,&input,sizeof(INPUT)); } else if (isDoubleClick) { input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; ::SendInput(1,&input,sizeof(INPUT)); input.mi.dwFlags = MOUSEEVENTF_LEFTUP; ::SendInput(1,&input,sizeof(INPUT)); input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; ::SendInput(1,&input,sizeof(INPUT)); input.mi.dwFlags = MOUSEEVENTF_LEFTUP; ::SendInput(1,&input,sizeof(INPUT)); } else if (isMiddleClick) { input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; ::SendInput(1,&input,sizeof(INPUT)); input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP; ::SendInput(1,&input,sizeof(INPUT)); } else if (isRightClick) { input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; ::SendInput(1,&input,sizeof(INPUT)); input.mi.dwFlags = MOUSEEVENTF_RIGHTUP; ::SendInput(1,&input,sizeof(INPUT)); } else if (isSingleHold){ input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; ::SendInput(1,&input,sizeof(INPUT)); } else if (isSingleRelease){ input.mi.dwFlags = MOUSEEVENTF_LEFTUP; ::SendInput(1,&input,sizeof(INPUT)); } else if (isScroll) { input.mi.dwFlags = MOUSEEVENTF_WHEEL; input.mi.mouseData = dy; ::SendInput(1,&input,sizeof(INPUT)); +//TODO: Keyboard input support /* } else if (!key.isEmpty() || specialKey) { bool ctrl = np.get(QStringLiteral("ctrl"), false); bool alt = np.get(QStringLiteral("alt"), false); bool shift = np.get(QStringLiteral("shift"), false); if (ctrl) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Control_L), True, 0); if (alt) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Alt_L), True, 0); if (shift) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Shift_L), True, 0); if (specialKey) { if (specialKey >= (int)arraySize(SpecialKeysMap)) { qWarning() << "Unsupported special key identifier"; return false; } int keycode = XKeysymToKeycode(display, SpecialKeysMap[specialKey]); XTestFakeKeyEvent (display, keycode, True, 0); XTestFakeKeyEvent (display, keycode, False, 0); } else { if (!m_fakekey) { m_fakekey = fakekey_init(display); if (!m_fakekey) { qWarning() << "Failed to initialize libfakekey"; return false; } } //We use fakekey here instead of XTest (above) because it can handle utf characters instead of keycodes. for (int i=0;i + * Copyright 2018 Albert Vaca Cintora * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H +#ifndef WINDOWSREMOTEINPUT_H +#define WINDOWSREMOTEINPUT_H -#include -#include +#include "abstractremoteinput.h" -#include - -class MousepadPlugin - : public KdeConnectPlugin +class WindowsRemoteInput + : public AbstractRemoteInput { Q_OBJECT public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; + explicit WindowsRemoteInput(QObject* parent); - bool receivePacket(const NetworkPacket& np) override; - void connected() override { } + bool handlePacket(const NetworkPacket& np) override; }; #endif diff --git a/plugins/mousepad/mousepadplugin.cpp b/plugins/mousepad/x11remoteinput.cpp similarity index 62% copy from plugins/mousepad/mousepadplugin.cpp copy to plugins/mousepad/x11remoteinput.cpp index 9c16498d..4f4bc971 100644 --- a/plugins/mousepad/mousepadplugin.cpp +++ b/plugins/mousepad/x11remoteinput.cpp @@ -1,337 +1,220 @@ /** + * Copyright 2018 Albert Vaca Cintora * Copyright 2014 Ahmed I. Khalil - * Copyright 2015 Martin Gräßlin * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#include "mousepadplugin.h" -#include -#include -#include -#include +#include "x11remoteinput.h" -#if HAVE_X11 #include +#include +#include + #include #include #include -#endif - -#if HAVE_WAYLAND -#include -#include -#include -#endif - -K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mousepad.json", registerPlugin< MousepadPlugin >(); ) enum MouseButtons { LeftMouseButton = 1, MiddleMouseButton = 2, RightMouseButton = 3, MouseWheelUp = 4, MouseWheelDown = 5 }; -#if HAVE_X11 //Translation table to keep in sync within all the implementations int SpecialKeysMap[] = { 0, // Invalid XK_BackSpace, // 1 XK_Tab, // 2 XK_Linefeed, // 3 XK_Left, // 4 XK_Up, // 5 XK_Right, // 6 XK_Down, // 7 XK_Page_Up, // 8 XK_Page_Down, // 9 XK_Home, // 10 XK_End, // 11 XK_Return, // 12 XK_Delete, // 13 XK_Escape, // 14 XK_Sys_Req, // 15 XK_Scroll_Lock, // 16 0, // 17 0, // 18 0, // 19 0, // 20 XK_F1, // 21 XK_F2, // 22 XK_F3, // 23 XK_F4, // 24 XK_F5, // 25 XK_F6, // 26 XK_F7, // 27 XK_F8, // 28 XK_F9, // 29 XK_F10, // 30 XK_F11, // 31 XK_F12, // 32 }; -#endif template size_t arraySize(T(&arr)[N]) { (void)arr; return N; } -MousepadPlugin::MousepadPlugin(QObject* parent, const QVariantList& args) - : KdeConnectPlugin(parent, args) -#if HAVE_X11 + +X11RemoteInput::X11RemoteInput(QObject* parent) + : AbstractRemoteInput(parent) , m_fakekey(nullptr) - , m_x11(QX11Info::isPlatformX11()) -#else - , m_x11(false) -#endif -#if HAVE_WAYLAND - , m_waylandInput(nullptr) - , m_waylandAuthenticationRequested(false) -#endif { -#if HAVE_WAYLAND - setupWaylandIntegration(); -#endif + } -MousepadPlugin::~MousepadPlugin() +X11RemoteInput::~X11RemoteInput() { -#if HAVE_X11 if (m_fakekey) { free(m_fakekey); m_fakekey = nullptr; } -#endif -} - -bool MousepadPlugin::receivePacket(const NetworkPacket& np) -{ -#if HAVE_X11 - if (m_x11) { - return handlePacketX11(np); - } -#endif -#if HAVE_WAYLAND - if (m_waylandInput) { - if (!m_waylandAuthenticationRequested) { - m_waylandInput->authenticate(i18n("KDE Connect"), i18n("Use your phone as a touchpad and keyboard")); - m_waylandAuthenticationRequested = true; - } - handPacketWayland(np); - } -#endif - return false; } -#if HAVE_X11 bool isLeftHanded(Display * display) { unsigned char map[20]; int num_buttons = XGetPointerMapping(display, map, 20); if( num_buttons == 1 ) { return false; } else if( num_buttons == 2 ) { return ( (int)map[0] == 2 && (int)map[1] == 1 ); } else { return ( (int)map[0] == 3 && (int)map[2] == 1 ); } } -#endif -#if HAVE_X11 -bool MousepadPlugin::handlePacketX11(const NetworkPacket& np) +bool X11RemoteInput::handlePacket(const NetworkPacket& np) { - //qDebug() << np.serialize(); - - //TODO: Split mouse/keyboard in two different plugins to avoid this big if statement - float dx = np.get(QStringLiteral("dx"), 0); float dy = np.get(QStringLiteral("dy"), 0); bool isSingleClick = np.get(QStringLiteral("singleclick"), false); bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); bool isRightClick = np.get(QStringLiteral("rightclick"), false); bool isSingleHold = np.get(QStringLiteral("singlehold"), false); bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); bool isScroll = np.get(QStringLiteral("scroll"), false); QString key = np.get(QStringLiteral("key"), QLatin1String("")); int specialKey = np.get(QStringLiteral("specialKey"), 0); if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { Display* display = QX11Info::display(); if(!display) { return false; } bool leftHanded = isLeftHanded(display); int mainMouseButton = leftHanded? RightMouseButton : LeftMouseButton; int secondaryMouseButton = leftHanded? LeftMouseButton : RightMouseButton; if (isSingleClick) { XTestFakeButtonEvent(display, mainMouseButton, True, 0); XTestFakeButtonEvent(display, mainMouseButton, False, 0); } else if (isDoubleClick) { XTestFakeButtonEvent(display, mainMouseButton, True, 0); XTestFakeButtonEvent(display, mainMouseButton, False, 0); XTestFakeButtonEvent(display, mainMouseButton, True, 0); XTestFakeButtonEvent(display, mainMouseButton, False, 0); } else if (isMiddleClick) { XTestFakeButtonEvent(display, MiddleMouseButton, True, 0); XTestFakeButtonEvent(display, MiddleMouseButton, False, 0); } else if (isRightClick) { XTestFakeButtonEvent(display, secondaryMouseButton, True, 0); XTestFakeButtonEvent(display, secondaryMouseButton, False, 0); } else if (isSingleHold){ //For drag'n drop XTestFakeButtonEvent(display, mainMouseButton, True, 0); } else if (isSingleRelease){ //For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofnes. XTestFakeButtonEvent(display, mainMouseButton, False, 0); } else if (isScroll) { if (dy < 0) { XTestFakeButtonEvent(display, MouseWheelDown, True, 0); XTestFakeButtonEvent(display, MouseWheelDown, False, 0); } else if (dy > 0) { XTestFakeButtonEvent(display, MouseWheelUp, True, 0); XTestFakeButtonEvent(display, MouseWheelUp, False, 0); } } else if (!key.isEmpty() || specialKey) { bool ctrl = np.get(QStringLiteral("ctrl"), false); bool alt = np.get(QStringLiteral("alt"), false); bool shift = np.get(QStringLiteral("shift"), false); if (ctrl) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Control_L), True, 0); if (alt) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Alt_L), True, 0); if (shift) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Shift_L), True, 0); if (specialKey) { if (specialKey >= (int)arraySize(SpecialKeysMap)) { qWarning() << "Unsupported special key identifier"; return false; } int keycode = XKeysymToKeycode(display, SpecialKeysMap[specialKey]); XTestFakeKeyEvent (display, keycode, True, 0); XTestFakeKeyEvent (display, keycode, False, 0); } else { if (!m_fakekey) { m_fakekey = fakekey_init(display); if (!m_fakekey) { qWarning() << "Failed to initialize libfakekey"; return false; } } //We use fakekey here instead of XTest (above) because it can handle utf characters instead of keycodes. for (int i=0;icreate(connection); - connect(registry, &Registry::fakeInputAnnounced, this, - [this, registry] (quint32 name, quint32 version) { - m_waylandInput = registry->createFakeInput(name, version, this); - } - ); - registry->setup(); -} -bool MousepadPlugin::handPacketWayland(const NetworkPacket& np) +bool X11RemoteInput::hasKeyboardSupport() { - const float dx = np.get(QStringLiteral("dx"), 0); - const float dy = np.get(QStringLiteral("dy"), 0); - - const bool isSingleClick = np.get(QStringLiteral("singleclick"), false); - const bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); - const bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); - const bool isRightClick = np.get(QStringLiteral("rightclick"), false); - const bool isSingleHold = np.get(QStringLiteral("singlehold"), false); - const bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); - const bool isScroll = np.get(QStringLiteral("scroll"), false); - const QString key = np.get(QStringLiteral("key"), QLatin1String("")); - const int specialKey = np.get(QStringLiteral("specialKey"), 0); - - if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { - - if (isSingleClick) { - m_waylandInput->requestPointerButtonClick(Qt::LeftButton); - } else if (isDoubleClick) { - m_waylandInput->requestPointerButtonClick(Qt::LeftButton); - m_waylandInput->requestPointerButtonClick(Qt::LeftButton); - } else if (isMiddleClick) { - m_waylandInput->requestPointerButtonClick(Qt::MiddleButton); - } else if (isRightClick) { - m_waylandInput->requestPointerButtonClick(Qt::RightButton); - } else if (isSingleHold){ - //For drag'n drop - m_waylandInput->requestPointerButtonPress(Qt::LeftButton); - } else if (isSingleRelease){ - //For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofnes. - m_waylandInput->requestPointerButtonRelease(Qt::LeftButton); - } else if (isScroll) { - m_waylandInput->requestPointerAxis(Qt::Vertical, dy); - } else if (!key.isEmpty() || specialKey) { - // TODO: implement key support - } - - } else { //Is a mouse move event - m_waylandInput->requestPointerMove(QSizeF(dx, dy)); - } return true; } -#endif - -#include "mousepadplugin.moc" diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/mousepad/x11remoteinput.h similarity index 67% copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/x11remoteinput.h index 5950b804..897271a2 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/x11remoteinput.h @@ -1,42 +1,44 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Albert Vaca Cintora * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H +#ifndef X11REMOTEINPUT_H +#define X11REMOTEINPUT_H -#include -#include +#include "abstractremoteinput.h" -#include +struct FakeKey; -class MousepadPlugin - : public KdeConnectPlugin +class X11RemoteInput + : public AbstractRemoteInput { Q_OBJECT public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; + explicit X11RemoteInput(QObject* parent); + ~X11RemoteInput() override; - bool receivePacket(const NetworkPacket& np) override; - void connected() override { } + bool handlePacket(const NetworkPacket& np) override; + bool hasKeyboardSupport() override; + +private: + FakeKey* m_fakekey; }; #endif diff --git a/plugins/mousepad_windows/CMakeLists.txt b/plugins/mousepad_windows/CMakeLists.txt deleted file mode 100644 index 2fbe4db2..00000000 --- a/plugins/mousepad_windows/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -kdeconnect_add_plugin(kdeconnect_mousepad JSON kdeconnect_mousepad.json SOURCES mousepadplugin_windows.cpp) -target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt5::Gui KF5::I18n) diff --git a/plugins/mousepad_windows/kdeconnect_mousepad.json b/plugins/mousepad_windows/kdeconnect_mousepad.json deleted file mode 100644 index dc7dba1c..00000000 --- a/plugins/mousepad_windows/kdeconnect_mousepad.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "KPlugin": { - "Authors": [ - { - "Email": "ahmedibrahimkhali@gmail.com", - "Name": "Ahmed I. Khalil", - "Name[ar]": "أحمد إبراهيم خليل", - "Name[sr@ijekavian]": "Ахмед И. Калил", - "Name[sr@ijekavianlatin]": "Ahmed I. Kalil", - "Name[sr@latin]": "Ahmed I. Kalil", - "Name[sr]": "Ахмед И. Калил", - "Name[x-test]": "xxAhmed I. Khalilxx" - } - ], - "Description": "Use your phone as a touchpad and keyboard", - "Description[ar]": "استخدم الهاتف كفأرة ولوحة مفاتيح", - "Description[ca@valencia]": "Empra el telèfon com un ratolí tàctil i teclat", - "Description[ca]": "Empra el telèfon com un ratolí tàctil i teclat", - "Description[cs]": "Používejte svůj telefon jako touchpad a klávesnici", - "Description[da]": "Brug din telefon som touchpad og tastatur", - "Description[de]": "Verwendet Ihr Handy als Touchpad und Tastatur", - "Description[el]": "Χρήση του τηλεφώνου σας ως οθόνη αφής και πληκτρολογίου", - "Description[es]": "Usar teléfono como panel táctil y teclado", - "Description[et]": "Telefoni kasutamine puutepadja ja klaviatuurina", - "Description[eu]": "Erabili zure telefonoa touchpad eta teklatu gisa", - "Description[fi]": "Käytä puhelintasi kosketuslevynä ja näppäimistönä", - "Description[fr]": "Utilisez votre téléphone comme un pavé tactile et un clavier", - "Description[gl]": "Usar o teléfono móbil como área táctil e teclado.", - "Description[hu]": "A telefon használata érintőtáblaként és billentyűzetként", - "Description[it]": "Usa il telefono come touchpad e tastiera", - "Description[ko]": "휴대폰을 터치패드와 키보드로 사용", - "Description[nl]": "Uw telefoon gebruiken als een touchpad en toetsenbord", - "Description[nn]": "Bruk telefonen som styreplate og tastatur", - "Description[pl]": "Używaj swojego telefonu jako gładzika i klawiatury", - "Description[pt]": "Use o seu telefone como um rato e teclado por toque", - "Description[pt_BR]": "Use seu telefone como um touchpad e teclado", - "Description[ru]": "Использование телефона в качестве сенсорной панели и клавиатуры", - "Description[sk]": "Použite váš telefón ako touchpad a klávesnicu", - "Description[sr@ijekavian]": "Користите телефон као додирник и тастатуру", - "Description[sr@ijekavianlatin]": "Koristite telefon kao dodirnik i tastaturu", - "Description[sr@latin]": "Koristite telefon kao dodirnik i tastaturu", - "Description[sr]": "Користите телефон као додирник и тастатуру", - "Description[sv]": "Använd telefonen som mus och tangentbord", - "Description[tr]": "Telefonunuzu dokunmatik yüzey ve klayve olarak kullanın", - "Description[uk]": "Скористайтеся телефоном як замінником сенсорної панелі і клавіатури", - "Description[x-test]": "xxUse your phone as a touchpad and keyboardxx", - "Description[zh_CN]": "用您的手机作为触摸板和键盘", - "Description[zh_TW]": "讓您的智慧型手機當作觸碰板與鍵盤", - "EnabledByDefault": true, - "Icon": "edit-select", - "Id": "kdeconnect_mousepad", - "License": "GPL", - "Name": "Virtual input", - "Name[ar]": "دخل وهميّ", - "Name[ca@valencia]": "Entrada virtual", - "Name[ca]": "Entrada virtual", - "Name[cs]": "Virtuální vstup", - "Name[da]": "Virtuelt input", - "Name[de]": "Virtuelle Eingabe", - "Name[el]": "Εικονικά στοιχεία εισόδου", - "Name[es]": "Entrada virtual", - "Name[et]": "Virtuaalsisestus", - "Name[eu]": "Sarrera birtuala", - "Name[fi]": "Virtuaalinen syöttö", - "Name[fr]": "Entrée virtuelle", - "Name[gl]": "Entrada virtual", - "Name[hu]": "Virtuális bevitel", - "Name[it]": "Inserimento virtuale", - "Name[ko]": "가상 입력", - "Name[nl]": "Virtuele invoer", - "Name[nn]": "Virtuelt tastatur", - "Name[pl]": "Wirtualna obsługa", - "Name[pt]": "Entrada virtual", - "Name[pt_BR]": "Entrada virtual", - "Name[ru]": "Виртуальный ввод", - "Name[sk]": "Virtuálny vstup", - "Name[sr@ijekavian]": "Виртуелни унос", - "Name[sr@ijekavianlatin]": "Virtuelni unos", - "Name[sr@latin]": "Virtuelni unos", - "Name[sr]": "Виртуелни унос", - "Name[sv]": "Virtuell inmatning", - "Name[tr]": "Sanal içe aktarma", - "Name[uk]": "Віртуальне введення даних", - "Name[x-test]": "xxVirtual inputxx", - "Name[zh_CN]": "虚拟输入", - "Name[zh_TW]": "虛擬輸入", - "ServiceTypes": [ - "KdeConnect/Plugin" - ], - "Version": "0.1" - }, - "X-KdeConnect-OutgoingPacketType": [], - "X-KdeConnect-SupportedPacketType": [ - "kdeconnect.mousepad.request" - ] -} diff --git a/plugins/mprisremote/CMakeLists.txt b/plugins/mprisremote/CMakeLists.txt index b4b7a2b1..5e2b1caa 100644 --- a/plugins/mprisremote/CMakeLists.txt +++ b/plugins/mprisremote/CMakeLists.txt @@ -1,7 +1,7 @@ -kdeconnect_add_plugin(kdeconnect_mprisremote JSON kdeconnect_mprisremote.json SOURCES mprisremoteplugin.cpp) +kdeconnect_add_plugin(kdeconnect_mprisremote JSON kdeconnect_mprisremote.json SOURCES mprisremoteplugin.cpp mprisremoteplayer.cpp) target_link_libraries(kdeconnect_mprisremote kdeconnectcore Qt5::DBus KF5::I18n ) diff --git a/plugins/mprisremote/mprisremoteplayer.cpp b/plugins/mprisremote/mprisremoteplayer.cpp new file mode 100644 index 00000000..cf520d7b --- /dev/null +++ b/plugins/mprisremote/mprisremoteplayer.cpp @@ -0,0 +1,106 @@ +/** + * Copyright 2018 Nicolas Fella + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + */ + +#include +#include + +#include "mprisremoteplayer.h" + +MprisRemotePlayer::MprisRemotePlayer() : + m_playing(false) + , m_nowPlaying() + , m_volume(50) + , m_length(0) + , m_lastPosition(0) + , m_lastPositionTime() + , m_title() + , m_artist() + , m_album() +{ +} + +MprisRemotePlayer::~MprisRemotePlayer() +{ +} + +void MprisRemotePlayer::parseNetworkPacket(const NetworkPacket& np) +{ + m_nowPlaying = np.get(QStringLiteral("nowPlaying"), m_nowPlaying); + m_title = np.get(QStringLiteral("title"), m_title); + m_artist = np.get(QStringLiteral("artist"), m_artist); + m_album = np.get(QStringLiteral("album"), m_album); + m_volume = np.get(QStringLiteral("volume"), m_volume); + m_length = np.get(QStringLiteral("length"), m_length); + if(np.has(QStringLiteral("pos"))){ + m_lastPosition = np.get(QStringLiteral("pos"), m_lastPosition); + m_lastPositionTime = QDateTime::currentMSecsSinceEpoch(); + } + m_playing = np.get(QStringLiteral("isPlaying"), m_playing); +} + +long MprisRemotePlayer::position() const +{ + if(m_playing) { + return m_lastPosition + (QDateTime::currentMSecsSinceEpoch() - m_lastPositionTime); + } else { + return m_lastPosition; + } +} + +void MprisRemotePlayer::setPosition(long position) +{ + m_lastPosition = position; + m_lastPositionTime = QDateTime::currentMSecsSinceEpoch(); +} + +int MprisRemotePlayer::volume() const +{ + return m_volume; +} + +long int MprisRemotePlayer::length() const +{ + return m_length; +} + +bool MprisRemotePlayer::playing() const +{ + return m_playing; +} + +QString MprisRemotePlayer::nowPlaying() const +{ + return m_nowPlaying; +} + +QString MprisRemotePlayer::title() const +{ + return m_title; +} + +QString MprisRemotePlayer::artist() const +{ + return m_artist; +} + +QString MprisRemotePlayer::album() const +{ + return m_album; +} diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/mprisremote/mprisremoteplayer.h similarity index 54% rename from plugins/mousepad_windows/mousepadplugin_windows.h rename to plugins/mprisremote/mprisremoteplayer.h index 5950b804..5cb4a6ae 100644 --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mprisremote/mprisremoteplayer.h @@ -1,42 +1,53 @@ /** - * Copyright 2014 Ahmed I. Khalil + * Copyright 2018 Nicolas Fella * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ +#pragma once -#ifndef MOUSEPADPLUGIN_H -#define MOUSEPADPLUGIN_H +#include -#include -#include +class MprisRemotePlayer { -#include +public: + explicit MprisRemotePlayer(); + virtual ~MprisRemotePlayer(); -class MousepadPlugin - : public KdeConnectPlugin -{ - Q_OBJECT + void parseNetworkPacket(const NetworkPacket& np); + long position() const; + void setPosition(long position); + int volume() const; + long length() const; + bool playing() const; + QString nowPlaying() const; + QString title() const; + QString artist() const; + QString album() const; -public: - explicit MousepadPlugin(QObject* parent, const QVariantList &args); - ~MousepadPlugin() override; +private: - bool receivePacket(const NetworkPacket& np) override; - void connected() override { } + QString id; + bool m_playing; + QString m_nowPlaying; + int m_volume; + long m_length; + long m_lastPosition; + qint64 m_lastPositionTime; + QString m_title; + QString m_artist; + QString m_album; }; - -#endif diff --git a/plugins/mprisremote/mprisremoteplugin.cpp b/plugins/mprisremote/mprisremoteplugin.cpp index 3f12029a..4abbc764 100644 --- a/plugins/mprisremote/mprisremoteplugin.cpp +++ b/plugins/mprisremote/mprisremoteplugin.cpp @@ -1,155 +1,204 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "mprisremoteplugin.h" #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mprisremote.json", registerPlugin< MprisRemotePlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRISREMOTE, "kdeconnect.plugin.mprisremote") MprisRemotePlugin::MprisRemotePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) - , m_player() - , m_playing(false) - , m_nowPlaying() - , m_volume(50) - , m_length(0) - , m_lastPosition(0) - , m_lastPositionTime() - , m_playerList() + , m_currentPlayer() + , m_players() { } MprisRemotePlugin::~MprisRemotePlugin() { } bool MprisRemotePlugin::receivePacket(const NetworkPacket& np) { if (np.type() != PACKET_TYPE_MPRIS) return false; - if (np.has(QStringLiteral("nowPlaying")) || np.has(QStringLiteral("volume")) || np.has(QStringLiteral("isPlaying")) || np.has(QStringLiteral("length")) || np.has(QStringLiteral("pos"))) { - if (np.get(QStringLiteral("player")) == m_player) { - m_nowPlaying = np.get(QStringLiteral("nowPlaying"), m_nowPlaying); - m_volume = np.get(QStringLiteral("volume"), m_volume); - m_length = np.get(QStringLiteral("length"), m_length); - if(np.has(QStringLiteral("pos"))){ - m_lastPosition = np.get(QStringLiteral("pos"), m_lastPosition); - m_lastPositionTime = QDateTime::currentMSecsSinceEpoch(); - } - m_playing = np.get(QStringLiteral("isPlaying"), m_playing); - } + if (np.has(QStringLiteral("player"))) { + m_players[np.get(QStringLiteral("player"))]->parseNetworkPacket(np); } if (np.has(QStringLiteral("playerList"))) { - m_playerList = np.get(QStringLiteral("playerList"), QStringList()); + QStringList players = np.get(QStringLiteral("playerList")); + qDeleteAll(m_players); + m_players.clear(); + for (const QString& player : players) { + m_players[player] = new MprisRemotePlayer(); + requestPlayerStatus(player); + } + + if (m_players.empty()) { + m_currentPlayer = QString(); + } else if (!m_players.contains(m_currentPlayer)) { + m_currentPlayer = m_players.keys().first(); + } + } Q_EMIT propertiesChanged(); return true; } long MprisRemotePlugin::position() const { - if(m_playing) { - return m_lastPosition + (QDateTime::currentMSecsSinceEpoch() - m_lastPositionTime); - } else { - return m_lastPosition; - } + auto player = m_players.value(m_currentPlayer); + return player ? player->position() : 0; } QString MprisRemotePlugin::dbusPath() const { return "/modules/kdeconnect/devices/" + device()->id() + "/mprisremote"; } -void MprisRemotePlugin::requestPlayerStatus() +void MprisRemotePlugin::requestPlayerStatus(const QString& player) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { - {"player", m_player}, + {"player", player}, {"requestNowPlaying", true}, {"requestVolume", true}} ); sendPacket(np); } void MprisRemotePlugin::requestPlayerList() { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, {{"requestPlayerList", true}}); sendPacket(np); } void MprisRemotePlugin::sendAction(const QString& action) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { - {"player", m_player}, + {"player", m_currentPlayer}, {"action", action} }); sendPacket(np); } void MprisRemotePlugin::seek(int offset) const { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { - {"player", m_player}, + {"player", m_currentPlayer}, {"Seek", offset}}); sendPacket(np); } void MprisRemotePlugin::setVolume(int volume) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { - {"player", m_player}, + {"player", m_currentPlayer}, {"setVolume",volume} }); sendPacket(np); } void MprisRemotePlugin::setPosition(int position) { NetworkPacket np(PACKET_TYPE_MPRIS_REQUEST, { - {"player", m_player}, + {"player", m_currentPlayer}, {"SetPosition", position} }); sendPacket(np); - m_lastPosition = position; - m_lastPositionTime = QDateTime::currentMSecsSinceEpoch(); + m_players[m_currentPlayer]->setPosition(position); } void MprisRemotePlugin::setPlayer(const QString& player) { - if (m_player != player) { - m_player = player; - requestPlayerStatus(); + if (m_currentPlayer != player) { + m_currentPlayer = player; + requestPlayerStatus(player); + Q_EMIT propertiesChanged(); } } +bool MprisRemotePlugin::isPlaying() const +{ + auto player = m_players.value(m_currentPlayer); + return player ? player->playing() : false; +} + +int MprisRemotePlugin::length() const +{ + auto player = m_players.value(m_currentPlayer); + return player ? player->length() : 0; +} + +int MprisRemotePlugin::volume() const +{ + auto player = m_players.value(m_currentPlayer); + return player ? player->volume() : 0; +} + +QString MprisRemotePlugin::player() const +{ + if (m_currentPlayer.isEmpty()) + return QString(); + return m_currentPlayer; +} + +QStringList MprisRemotePlugin::playerList() const +{ + return m_players.keys(); +} + +QString MprisRemotePlugin::nowPlaying() const +{ + auto player = m_players.value(m_currentPlayer); + return player ? player->nowPlaying() : QString(); +} + +QString MprisRemotePlugin::title() const +{ + auto player = m_players.value(m_currentPlayer); + return player ? player->title() : QString(); +} + +QString MprisRemotePlugin::album() const +{ + auto player = m_players.value(m_currentPlayer); + return player ? player->album() : QString(); +} + +QString MprisRemotePlugin::artist() const +{ + auto player = m_players.value(m_currentPlayer); + return player ? player->artist() : QString(); +} + #include "mprisremoteplugin.moc" diff --git a/plugins/mprisremote/mprisremoteplugin.h b/plugins/mprisremote/mprisremoteplugin.h index 00a16f21..175f5e8c 100644 --- a/plugins/mprisremote/mprisremoteplugin.h +++ b/plugins/mprisremote/mprisremoteplugin.h @@ -1,84 +1,86 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #ifndef MPRISREMOTEPLUGIN_H #define MPRISREMOTEPLUGIN_H #include #include +#include "mprisremoteplayer.h" + #define PACKET_TYPE_MPRIS_REQUEST QStringLiteral("kdeconnect.mpris.request") #define PACKET_TYPE_MPRIS QStringLiteral("kdeconnect.mpris") class Q_DECL_EXPORT MprisRemotePlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.mprisremote") Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY propertiesChanged) Q_PROPERTY(int length READ length NOTIFY propertiesChanged) Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY propertiesChanged) Q_PROPERTY(int position READ position WRITE setPosition NOTIFY propertiesChanged) Q_PROPERTY(QStringList playerList READ playerList NOTIFY propertiesChanged) Q_PROPERTY(QString player READ player WRITE setPlayer) Q_PROPERTY(QString nowPlaying READ nowPlaying NOTIFY propertiesChanged) + Q_PROPERTY(QString title READ title NOTIFY propertiesChanged) + Q_PROPERTY(QString artist READ artist NOTIFY propertiesChanged) + Q_PROPERTY(QString album READ album NOTIFY propertiesChanged) public: explicit MprisRemotePlugin(QObject* parent, const QVariantList &args); ~MprisRemotePlugin() override; long position() const; - int volume() const { return m_volume; } - int length() const { return m_length; } - bool isPlaying() const { return m_playing; } - QStringList playerList() const { return m_playerList; } - QString player() const { return m_player; } - QString nowPlaying() const { return m_nowPlaying; } + int volume() const; + int length() const; + bool isPlaying() const; + QStringList playerList() const; + QString player() const; + QString nowPlaying() const; + QString title() const; + QString artist() const; + QString album() const; void setVolume(int volume); void setPosition(int position); void setPlayer(const QString& player); bool receivePacket(const NetworkPacket& np) override; void connected() override {} QString dbusPath() const override; Q_SCRIPTABLE void seek(int offset) const; Q_SCRIPTABLE void requestPlayerList(); Q_SCRIPTABLE void sendAction(const QString& action); Q_SIGNALS: void propertiesChanged(); private: - void requestPlayerStatus(); + void requestPlayerStatus(const QString& player); - QString m_player; - bool m_playing; - QString m_nowPlaying; - int m_volume; - long m_length; - long m_lastPosition; - qint64 m_lastPositionTime; - QStringList m_playerList; + QString m_currentPlayer; + QMap m_players; }; #endif diff --git a/plugins/remotekeyboard/kdeconnect_remotekeyboard.json b/plugins/remotekeyboard/kdeconnect_remotekeyboard.json index d19b0dd8..4f59fa62 100644 --- a/plugins/remotekeyboard/kdeconnect_remotekeyboard.json +++ b/plugins/remotekeyboard/kdeconnect_remotekeyboard.json @@ -1,84 +1,86 @@ { "KPlugin": { "Authors": [ { "Email": "holger.k@elberer.de", "Name": "Holger Kaelberer", "Name[sr@ijekavian]": "Холгер Келберер", "Name[sr@ijekavianlatin]": "Holger Kelberer", "Name[sr@latin]": "Holger Kelberer", "Name[sr]": "Холгер Келберер", "Name[x-test]": "xxHolger Kaelbererxx" } ], "Description": "Use your keyboard to send key-events to your paired device", "Description[ca@valencia]": "Useu el vostre teclat per enviar esdeveniments de tecla al dispositiu enllaçat", "Description[ca]": "Useu el vostre teclat per enviar esdeveniments de tecla al dispositiu enllaçat", "Description[cs]": "Používejte svoji klávesnici pro odesílání událostí kláves na spárované zařízení", "Description[da]": "Brug dit tastatur til at sende tastehændelser til din parrede enhed", "Description[de]": "Benutzen Sie Ihre Tastatur, um Tastatureingaben an das angeschlossene Gerät zu senden", "Description[el]": "Στέλνεις ό,τι πληκτρολογείς στη συσκευή ζεύγους", "Description[es]": "Use su teclado para enviar eventos de teclado a su dispositivo vinculado", "Description[eu]": "Erabili zure teklatua tekla-gertaerak zure parekatutako gailura bidaltzeko", "Description[fr]": "Utiliser votre clavier pour envoyer des évènements de touche au périphérique associé", "Description[gl]": "Usar o teclado para enviar eventos de tecla ao dispositivo emparellado.", "Description[it]": "Utilizza la tua tastiera per inviare eventi di pressione dei tasti al dispositivo associato", "Description[nl]": "Uw toetsenbord gebruiken om toetsgebeurtenissen naar uw gepaarde apparaat te verzenden", "Description[nn]": "Bruk tastaturet til å senda tastetrykk til den para eininga", "Description[pl]": "Użyj swojej klawiatury do wysyłania naciśnięć klawiszy do swojego sparowanego urządzenia", "Description[pt]": "Usar o seu teclado para enviar eventos de teclas para o seu dispositivo emparelhado", "Description[pt_BR]": "Use seu teclado para enviar eventos chace ao seu dispositivo pareado", "Description[ru]": "Используйте клавиатуру для отправки нажатий клавиш на сопряжённое устройство", "Description[sr@ijekavian]": "Користите тастатуру за куцање на упареном уређају", "Description[sr@ijekavianlatin]": "Koristite tastaturu za kucanje na uparenom uređaju", "Description[sr@latin]": "Koristite tastaturu za kucanje na uparenom uređaju", "Description[sr]": "Користите тастатуру за куцање на упареном уређају", "Description[sv]": "Använd tangentbordet för att skicka tangenthändelser till den parade enheten", "Description[tr]": "Eşleşmiş cihazınıza önemli etkinlikler göndermek için klavyenizi kullanın", "Description[uk]": "Скористайтеся вашою клавіатурою для надсилання подій натискання клавіш на пов’язаний пристрій", "Description[x-test]": "xxUse your keyboard to send key-events to your paired devicexx", "Description[zh_CN]": "使用您的键盘发送按键事件给配对的设备", + "Description[zh_TW]": "利用您的鍵盤去傳送按鍵活動至您的配對裝置", "EnabledByDefault": true, "Icon": "edit-select", "Id": "kdeconnect_remotekeyboard", "License": "GPL", "Name": "Remote keyboard from the desktop", "Name[ca@valencia]": "Teclat remot des de l'escriptori", "Name[ca]": "Teclat remot des de l'escriptori", "Name[cs]": "Vzdálená klávesnice pro plochu", "Name[da]": "Eksternt tastatur fra desktoppen", "Name[de]": "Tastatureingaben von der Arbeitsfläche", "Name[el]": "Απομακρυσμένο πληκτρολόγιο της επιφάνειας εργασίας", "Name[es]": "Teclado remoto desde el equipo de escritorio", "Name[eu]": "Urruneko teklatua mahaigainetik", "Name[fr]": "Clavier distant depuis le bureau", "Name[gl]": "Teclado remoto do escritorio", "Name[it]": "Tastiera remota dal desktop", "Name[nl]": "Toetsenbord op afstand vanaf het bureaublad", "Name[nn]": "Fjerntastatur frå skrivebordet", "Name[pl]": "Zdalna klawiatura z pulpitu", "Name[pt]": "Teclado remoto do ambiente de trabalho", "Name[pt_BR]": "Teclado remoto a partir da área de trabalho", "Name[ru]": "Удалённая клавиатура с компьютера", "Name[sr@ijekavian]": "Даљинска тастатура са радне површи", "Name[sr@ijekavianlatin]": "Daljinska tastatura sa radne površi", "Name[sr@latin]": "Daljinska tastatura sa radne površi", "Name[sr]": "Даљинска тастатура са радне површи", "Name[sv]": "Externt tangentbord från skrivbordet", "Name[tr]": "Masaüstünden uzak klavye", "Name[uk]": "Віддалена клавіатура з комп’ютера", "Name[x-test]": "xxRemote keyboard from the desktopxx", "Name[zh_CN]": "来自桌面的远程键盘", + "Name[zh_TW]": "從桌面遠端控制鍵盤", "ServiceTypes": [ "KdeConnect/Plugin" ], "Version": "0.1" }, "X-KdeConnect-OutgoingPacketType": [ "kdeconnect.mousepad.request" ], "X-KdeConnect-SupportedPacketType": [ "kdeconnect.mousepad.echo", "kdeconnect.mousepad.keyboardstate" ] } diff --git a/plugins/runcommand/runcommandplugin.cpp b/plugins/runcommand/runcommandplugin.cpp index 3c19c0cf..c5f24e05 100644 --- a/plugins/runcommand/runcommandplugin.cpp +++ b/plugins/runcommand/runcommandplugin.cpp @@ -1,93 +1,101 @@ /** * Copyright 2013 Albert Vaca * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "runcommandplugin.h" #include #include #include #include #include #include #include #include +#include #include #include #define PACKET_TYPE_RUNCOMMAND QStringLiteral("kdeconnect.runcommand") K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_runcommand.json", registerPlugin< RunCommandPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_RUNCOMMAND, "kdeconnect.plugin.runcommand") RunCommandPlugin::RunCommandPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { connect(config(), &KdeConnectPluginConfig::configChanged, this, &RunCommandPlugin::configChanged); } RunCommandPlugin::~RunCommandPlugin() { } bool RunCommandPlugin::receivePacket(const NetworkPacket& np) { if (np.get(QStringLiteral("requestCommandList"), false)) { sendConfig(); return true; } if (np.has(QStringLiteral("key"))) { QJsonDocument commandsDocument = QJsonDocument::fromJson(config()->get(QStringLiteral("commands"), "{}")); QJsonObject commands = commandsDocument.object(); QString key = np.get(QStringLiteral("key")); QJsonValue value = commands[key]; if (value == QJsonValue::Undefined) { qCWarning(KDECONNECT_PLUGIN_RUNCOMMAND) << key << "is not a configured command"; } const QJsonObject commandJson = value.toObject(); qCInfo(KDECONNECT_PLUGIN_RUNCOMMAND) << "Running:" << "/bin/sh" << "-c" << commandJson[QStringLiteral("command")].toString(); QProcess::startDetached(QStringLiteral("/bin/sh"), QStringList()<< QStringLiteral("-c") << commandJson[QStringLiteral("command")].toString()); return true; + } else if (np.has("setup")) { + QProcess::startDetached(QStringLiteral("kcmshell5"), {QStringLiteral("kdeconnect"), QStringLiteral("--args"), QString(device()->id() + QStringLiteral(":kdeconnect_runcommand")) }); } return false; } void RunCommandPlugin::connected() { sendConfig(); } void RunCommandPlugin::sendConfig() { QString commands = config()->get(QStringLiteral("commands"),QStringLiteral("{}")); NetworkPacket np(PACKET_TYPE_RUNCOMMAND, {{"commandList", commands}}); + + #if KCMUTILS_VERSION >= QT_VERSION_CHECK(5, 45, 0) + np.set(QStringLiteral("canAddCommand"), true); + #endif + sendPacket(np); } void RunCommandPlugin::configChanged() { sendConfig(); } #include "runcommandplugin.moc" diff --git a/plugins/sftp/mounter.cpp b/plugins/sftp/mounter.cpp index 4352af3b..2832c3fe 100644 --- a/plugins/sftp/mounter.cpp +++ b/plugins/sftp/mounter.cpp @@ -1,247 +1,248 @@ /** * Copyright 2014 Samoilenko Yuri * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "mounter.h" #include #include #include #include #include "mountloop.h" #include "config-sftp.h" #include "sftp_debug.h" #include "kdeconnectconfig.h" Mounter::Mounter(SftpPlugin* sftp) : QObject(sftp) , m_sftp(sftp) , m_proc(nullptr) , m_mountPoint(sftp->mountPoint()) , m_started(false) { connect(m_sftp, &SftpPlugin::packetReceived, this, &Mounter::onPakcageReceived); connect(&m_connectTimer, &QTimer::timeout, this, &Mounter::onMountTimeout); connect(this, &Mounter::mounted, &m_connectTimer, &QTimer::stop); connect(this, &Mounter::failed, &m_connectTimer, &QTimer::stop); m_connectTimer.setInterval(10000); m_connectTimer.setSingleShot(true); QTimer::singleShot(0, this, &Mounter::start); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created mounter"; } Mounter::~Mounter() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Destroy mounter"; unmount(false); } bool Mounter::wait() { if (m_started) { return true; } qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting loop to wait for mount"; MountLoop loop; connect(this, &Mounter::mounted, &loop, &MountLoop::successed); connect(this, &Mounter::failed, &loop, &MountLoop::failed); return loop.exec(); } void Mounter::onPakcageReceived(const NetworkPacket& np) { if (np.get(QStringLiteral("stop"), false)) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "SFTP server stopped"; unmount(false); return; } //This is the previous code, to access sftp server using KIO. Now we are //using the external binary sshfs, and accessing it as a local filesystem. /* * QUrl url; * url.setScheme("sftp"); * url.setHost(np.get("ip")); * url.setPort(np.get("port").toInt()); * url.setUserName(np.get("user")); * url.setPassword(np.get("password")); * url.setPath(np.get("path")); * new KRun(url, 0); * Q_EMIT mounted(); */ unmount(false); m_proc = new KProcess(); m_proc->setOutputChannelMode(KProcess::MergedChannels); connect(m_proc, &QProcess::started, this, &Mounter::onStarted); connect(m_proc, SIGNAL(error(QProcess::ProcessError)), SLOT(onError(QProcess::ProcessError))); connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(onFinished(int,QProcess::ExitStatus))); QDir().mkpath(m_mountPoint); const QString program = QStringLiteral("sshfs"); QString path; if (np.has(QStringLiteral("multiPaths"))) path = '/'; else path = np.get(QStringLiteral("path")); QHostAddress addr = m_sftp->device()->getLocalIpAddress(); if (addr == QHostAddress::Null) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Device doesn't have a LanDeviceLink, unable to get IP address"; return; } QString ip = addr.toString(); const QStringList arguments = QStringList() << QStringLiteral("%1@%2:%3").arg( np.get(QStringLiteral("user")), ip, path) << m_mountPoint << QStringLiteral("-p") << np.get(QStringLiteral("port")) << QStringLiteral("-s") // This fixes a bug where file chunks are sent out of order and get corrupted on reception << QStringLiteral("-f") << QStringLiteral("-F") << QStringLiteral("/dev/null") //Do not use ~/.ssh/config << QStringLiteral("-o") << "IdentityFile=" + KdeConnectConfig::instance()->privateKeyPath() << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=no") //Do not ask for confirmation because it is not a known host << QStringLiteral("-o") << QStringLiteral("UserKnownHostsFile=/dev/null") //Prevent storing as a known host << QStringLiteral("-o") << QStringLiteral("HostKeyAlgorithms=ssh-dss") //https://bugs.kde.org/show_bug.cgi?id=351725 << QStringLiteral("-o") << QStringLiteral("uid=") + QString::number(getuid()) << QStringLiteral("-o") << QStringLiteral("gid=") + QString::number(getgid()) + << QStringLiteral("-o") << QStringLiteral("reconnect") << QStringLiteral("-o") << QStringLiteral("ServerAliveInterval=30") << QStringLiteral("-o") << QStringLiteral("password_stdin") ; m_proc->setProgram(program, arguments); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting process: " << m_proc->program().join(QStringLiteral(" ")); m_proc->start(); //qCDebug(KDECONNECT_PLUGIN_SFTP) << "Passing password: " << np.get("password").toLatin1(); m_proc->write(np.get(QStringLiteral("password")).toLatin1()); m_proc->write("\n"); } void Mounter::onStarted() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process started"; m_started = true; Q_EMIT mounted(); //m_proc->setStandardOutputFile("/tmp/kdeconnect-sftp.out"); //m_proc->setStandardErrorFile("/tmp/kdeconnect-sftp.err"); auto proc = m_proc; connect(m_proc, &KProcess::readyReadStandardError, [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "stderr: " << proc->readAll(); }); connect(m_proc, &KProcess::readyReadStandardOutput, [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "stdout:" << proc->readAll(); }); } void Mounter::onError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed to start"; m_started = false; Q_EMIT failed(i18n("Failed to start sshfs")); } } void Mounter::onFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process finished (exit code: " << exitCode << ")"; Q_EMIT unmounted(); } else { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed (exit code:" << exitCode << ")"; Q_EMIT failed(i18n("Error when accessing to filesystem")); } unmount(true); } void Mounter::onMountTimeout() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Timeout: device not responding"; Q_EMIT failed(i18n("Failed to mount filesystem: device not responding")); } void Mounter::start() { NetworkPacket np(PACKET_TYPE_SFTP_REQUEST, {{"startBrowsing", true}}); m_sftp->sendPacket(np); m_connectTimer.start(); } void Mounter::unmount(bool finished) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Unmount" << m_proc; if (m_proc) { if (!finished) { //Process is still running, we want to stop it //But when the finished signal come, we might have already gone. //Disconnect everything. m_proc->disconnect(); m_proc->kill(); auto proc = m_proc; m_proc = nullptr; connect(proc, static_cast(&QProcess::finished), [proc]() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Free" << proc; proc->deleteLater(); }); Q_EMIT unmounted(); } else m_proc->deleteLater(); //Free mount point (won't always succeed if the path is in use) #if defined(HAVE_FUSERMOUNT) KProcess::execute(QStringList() << QStringLiteral("fusermount") << QStringLiteral("-u") << m_mountPoint, 10000); #else KProcess::execute(QStringList() << QStringLiteral("umount") << m_mountPoint, 10000); #endif m_proc = nullptr; } m_started = false; } diff --git a/smsapp/CMakeLists.txt b/smsapp/CMakeLists.txt index 8aadde5d..a3d9e877 100644 --- a/smsapp/CMakeLists.txt +++ b/smsapp/CMakeLists.txt @@ -1,7 +1,7 @@ qt5_add_resources(KCSMS_SRCS resources.qrc) -add_executable(kdeconnect-sms main.cpp ${KCSMS_SRCS}) +add_executable(kdeconnect-sms main.cpp conversationmodel.cpp ${KCSMS_SRCS}) target_link_libraries(kdeconnect-sms Qt5::Quick Qt5::Widgets KF5::DBusAddons KF5::CoreAddons KF5::I18n) install(TARGETS kdeconnect-sms ${INSTALL_TARGETS_DEFAULT_ARGS}) install(PROGRAMS org.kde.kdeconnect.sms.desktop DESTINATION ${KDE_INSTALL_APPDIR}) diff --git a/smsapp/conversationmodel.cpp b/smsapp/conversationmodel.cpp new file mode 100644 index 00000000..0aac7251 --- /dev/null +++ b/smsapp/conversationmodel.cpp @@ -0,0 +1,43 @@ +/* + * This file is part of KDE Telepathy Chat + * + * Copyright (C) 2018 Aleix Pol Gonzalez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "conversationmodel.h" + +ConversationModel::ConversationModel(QObject* parent) + : QStandardItemModel(parent) +{ + auto roles = roleNames(); + roles.insert(FromMeRole, "fromMe"); + setItemRoleNames(roles); +} + +QString ConversationModel::threadId() const +{ + return {}; +} + +void ConversationModel::setThreadId(const QString &threadId) +{ + clear(); + appendRow(new QStandardItem(threadId + QStringLiteral(" - A"))); + appendRow(new QStandardItem(threadId + QStringLiteral(" - A1"))); + appendRow(new QStandardItem(threadId + QStringLiteral(" - A2"))); + appendRow(new QStandardItem(threadId + QStringLiteral(" - A3"))); +} diff --git a/smsapp/conversationmodel.h b/smsapp/conversationmodel.h new file mode 100644 index 00000000..d504b566 --- /dev/null +++ b/smsapp/conversationmodel.h @@ -0,0 +1,44 @@ +/* + * This file is part of KDE Telepathy Chat + * + * Copyright (C) 2018 Aleix Pol Gonzalez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CONVERSATIONMODEL_H +#define CONVERSATIONMODEL_H + +#include + +class ConversationModel : public QStandardItemModel +{ + Q_OBJECT + Q_PROPERTY(QString threadId READ threadId WRITE setThreadId) + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId) + +public: + ConversationModel(QObject* parent = nullptr); + + enum Roles { FromMeRole = Qt::UserRole }; + + QString threadId() const; + void setThreadId(const QString &threadId); + + QString deviceId() const { return {}; } + void setDeviceId(const QString &/*deviceId*/) {} +}; + +#endif // CONVERSATIONMODEL_H diff --git a/smsapp/main.cpp b/smsapp/main.cpp index 58c6dc64..77072586 100644 --- a/smsapp/main.cpp +++ b/smsapp/main.cpp @@ -1,54 +1,58 @@ /* * This file is part of KDE Telepathy Chat * * Copyright (C) 2015 Aleix Pol Gonzalez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include +#include "conversationmodel.h" #include "kdeconnect-version.h" +#include int main(int argc, char *argv[]) { QApplication app(argc, argv); KAboutData aboutData("org.kde.kdeconnect.sms", i18n("SMS Instant Messaging"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect SMS"), KAboutLicense::GPL, i18n("(c) 2018, Aleix Pol Gonzalez")); aboutData.addAuthor(i18n("Aleix Pol Gonzalez"), {}, "aleixpol@kde.org"); KAboutData::setApplicationData(aboutData); { QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addVersionOption(); parser.addHelpOption(); parser.process(app); aboutData.processCommandLine(&parser); } KDBusService service(KDBusService::Unique); + qmlRegisterType("org.kde.kdeconnect.sms", 1, 0, "ConversationModel"); + QQmlApplicationEngine engine; engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.load(QUrl("qrc:/qml/main.qml")); return app.exec(); } diff --git a/smsapp/org.kde.kdeconnect.sms.desktop b/smsapp/org.kde.kdeconnect.sms.desktop index 130c82a0..08d0fecb 100644 --- a/smsapp/org.kde.kdeconnect.sms.desktop +++ b/smsapp/org.kde.kdeconnect.sms.desktop @@ -1,40 +1,58 @@ [Desktop Entry] Name=KDE Connect SMS Name[ca]=SMS del KDE Connect Name[ca@valencia]=SMS del KDE Connect Name[cs]=KDE Connect SMS +Name[de]=KDE-Connect-SMS Name[en_GB]=KDE Connect SMS +Name[es]=SMS de KDE Connect +Name[gl]=SMS de KDE Connect +Name[it]=KDE Connect SMS Name[nl]=KDE Connect SMS +Name[nn]=KDE Connect-SMS Name[pt]=SMS do KDE Connect Name[sv]=KDE-anslut SMS Name[uk]=KDE Connect SMS Name[x-test]=xxKDE Connect SMSxx +Name[zh_TW]=KDE 連線簡訊 GenericName=SMS GenericName[ca]=SMS GenericName[ca@valencia]=SMS GenericName[cs]=SMS +GenericName[de]=SMS GenericName[en_GB]=SMS +GenericName[es]=SMS +GenericName[gl]=SMS +GenericName[it]=SMS GenericName[nl]=SMS +GenericName[nn]=SMS GenericName[pt]=SMS GenericName[sv]=SMS GenericName[uk]=SMS GenericName[x-test]=xxSMSxx GenericName[zh_CN]=SMS +GenericName[zh_TW]=簡訊 Comment=Text Messaging Comment[ca]=Missatgeria de text Comment[ca@valencia]=Missatgeria de text Comment[cs]=Zprávy +Comment[de]=Text-Nachrichten Comment[en_GB]=Text Messaging +Comment[es]=Envío de mensajes de texto +Comment[gl]=Mensaxaría de texto. +Comment[it]=Messaggi di testo Comment[nl]=Tekstberichten versturen +Comment[nn]=Tekstmeldingar Comment[pt]=Mensagem de Texto Comment[sv]=Textmeddelanden Comment[uk]=Обмін текстовими повідомленнями Comment[x-test]=xxText Messagingxx +Comment[zh_TW]=純文字簡訊 Exec=kdeconnect-sms Icon=kdeconnect Type=Application Terminal=false Categories=Qt;KDE;Network;InstantMessaging X-DBUS-StartupType=Unique X-DBUS-ServiceName=org.kde.kdeconnect.sms diff --git a/smsapp/qml/ContactList.qml b/smsapp/qml/ContactList.qml index c269ca3b..7d24c37d 100644 --- a/smsapp/qml/ContactList.qml +++ b/smsapp/qml/ContactList.qml @@ -1,96 +1,97 @@ /* * This file is part of KDE Telepathy Chat * * Copyright (C) 2015 Aleix Pol Gonzalez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ import QtQuick 2.5 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import org.kde.people 1.0 import org.kde.plasma.core 2.0 as Core import org.kde.kirigami 2.2 as Kirigami import org.kde.kdeconnect 1.0 Kirigami.ScrollablePage { Component { id: chatView ConversationDisplay {} } ListView { id: view - spacing: 3 currentIndex: 0 model: PersonsSortFilterProxyModel { requiredProperties: ["phoneNumber"] sortRole: Qt.DisplayRole sortCaseSensitivity: Qt.CaseInsensitive - sourceModel: PersonsModel { - id: people - } + sourceModel: PersonsModel {} } header: TextField { id: filter placeholderText: i18n("Filter...") - Layout.fillWidth: true + width: parent.width onTextChanged: { view.model.filterRegExp = new RegExp(filter.text) view.currentIndex = 0 } Keys.onUpPressed: view.currentIndex = Math.max(view.currentIndex-1, 0) Keys.onDownPressed: view.currentIndex = Math.min(view.currentIndex+1, view.count-1) onAccepted: { view.currentItem.startChat() } Shortcut { sequence: "Ctrl+F" onActivated: filter.forceActiveFocus() } } delegate: Kirigami.BasicListItem { - id: mouse hoverEnabled: true readonly property var person: PersonData { personUri: model.personUri } label: display icon: decoration function startChat() { applicationWindow().pageStack.push(chatView, { person: person.person, device: Qt.binding(function() {return devicesCombo.device })}) } onClicked: { startChat(); } } } footer: ComboBox { id: devicesCombo - readonly property QtObject device: model.data(model.index(currentIndex, 0), DevicesModel.DeviceRole) + readonly property QtObject device: currentIndex>0 ? model.data(model.index(currentIndex, 0), DevicesModel.DeviceRole) : null + enabled: count > 0 + displayText: enabled ? undefined : i18n("No devices available") model: DevicesSortProxyModel { //TODO: make it possible to sort only if they can do sms sourceModel: DevicesModel { displayFilter: DevicesModel.Paired | DevicesModel.Reachable } + onRowsInserted: if (devicesCombo.currentIndex < 0) { + devicesCombo.currentIndex = 0 + } } textRole: "display" } } diff --git a/smsapp/qml/ConversationDisplay.qml b/smsapp/qml/ConversationDisplay.qml index c8995967..00825fb0 100644 --- a/smsapp/qml/ConversationDisplay.qml +++ b/smsapp/qml/ConversationDisplay.qml @@ -1,66 +1,69 @@ /* * This file is part of KDE Telepathy Chat * * Copyright (C) 2015 Aleix Pol Gonzalez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ import QtQuick 2.1 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import org.kde.kirigami 2.2 as Kirigami +import org.kde.kdeconnect.sms 1.0 Kirigami.ScrollablePage { id: page - readonly property string phoneNumber: person.contactCustomProperty("phoneNumber") - title: i18n("%1: %2", person.name, phoneNumber) property QtObject person property QtObject device - readonly property QtObject telephony: TelephonyDbusInterfaceFactory.create(device.id()) + readonly property string phoneNumber: person.contactCustomProperty("phoneNumber") + readonly property QtObject telephony: device ? TelephonyDbusInterfaceFactory.create(device.id()) : null + title: i18n("%1: %2", person.name, phoneNumber) ListView { - model: ListModel { - ListElement { display: "aaa"; fromMe: true } - ListElement { display: "aaa" } - ListElement { display: "aaa"; fromMe: true } - ListElement { display: "aaa" } - ListElement { display: "aaa" } - ListElement { display: "aaa" } + model: ConversationModel { + id: model + deviceId: device.id() + threadId: "xxxx" } + delegate: Kirigami.BasicListItem { readonly property real margin: 100 x: fromMe ? Kirigami.Units.gridUnit : margin width: parent.width - margin - Kirigami.Units.gridUnit contentItem: Label { text: model.display } } } footer: RowLayout { + enabled: page.device TextField { id: message Layout.fillWidth: true placeholderText: i18n("Say hi...") + onAccepted: { + console.log("sending sms", page.phoneNumber) + page.telephony.sendSms(page.phoneNumber, message.text) + } } Button { text: "Send" onClicked: { - console.log("sending sms", page.phoneNumber) - page.telephony.sendSms(page.phoneNumber, message.text) + message.accepted() } } } } diff --git a/urlhandler/org.kde.kdeconnect.telhandler.desktop b/urlhandler/org.kde.kdeconnect.telhandler.desktop index 0ac9296c..dc1c35c5 100644 --- a/urlhandler/org.kde.kdeconnect.telhandler.desktop +++ b/urlhandler/org.kde.kdeconnect.telhandler.desktop @@ -1,34 +1,35 @@ [Desktop Entry] Name=KDE Connect Phone URL Handler Name[ca]=Gestor d'URL de telèfons del KDE Connect Name[ca@valencia]=Gestor d'URL de telèfons del KDE Connect Name[cs]=Nástroj pro práci s URL telefonu v KDE Connect Name[da]=Telefon-URL-håndtering til KDE Connect Name[de]=KDE-Connect-Telefon-Dienstprogramm für URLs (Adressen) Name[el]=KDE Connect χειριστής URL για σύνδεση τηλεφώνου Name[en_GB]=KDE Connect Phone URL Handler Name[es]=Controlador de URL de teléfonos de KDE Connect Name[eu]=KDE Connect-en URL-kudeatzailea Name[fr]=Gestionnaire d'URL téléphoniques de KDE Connect Name[gl]=Manexador de URL de teléfono de KDE Connect Name[it]=Gestore URL del telefono di KDE Connect Name[nl]=URL behandelaar van KDE-Connect-telefoon Name[nn]=Telefon-URL-handsamar for KDE Connect Name[pl]=KDE Connect - obsługa URL telefonu Name[pt]=Tratamento de URL's Telefónicos do KDE Connect Name[ru]=Обработчик телефонных ссылок KDE Connect Name[sr]=КДЕ‑конекцијин руковалац телефонским УРЛ‑овима Name[sr@ijekavian]=КДЕ‑конекцијин руковалац телефонским УРЛ‑овима Name[sr@ijekavianlatin]=KDE‑konekcijin rukovalac telefonskim URL‑ovima Name[sr@latin]=KDE‑konekcijin rukovalac telefonskim URL‑ovima Name[sv]=KDE-anslut hanterare av telefonwebbadress Name[tr]=KDE Connect Telefon URL İşleyici Name[uk]=Обробник телефонних адрес KDE Connect Name[x-test]=xxKDE Connect Phone URL Handlerxx Name[zh_CN]=KDE Connect 电话 URL 处理程序 +Name[zh_TW]=KDE 連線手機網址處理器 Exec=kdeconnect-handler %U Icon=kdeconnect Type=Application NoDisplay=true MimeType=x-scheme-handler/tel