diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,8 @@ 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") @@ -15,9 +15,17 @@ 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}) diff --git a/app/qml/FindDevicesPage.qml b/app/qml/FindDevicesPage.qml --- a/app/qml/FindDevicesPage.qml +++ b/app/qml/FindDevicesPage.qml @@ -26,6 +26,11 @@ Kirigami.Page { + Component { + id: deviceComp + DevicePage {} + } + objectName: "FindDevices" title: i18n("Pair") ScrollView { @@ -58,7 +63,7 @@ onClicked: { pageStack.clear() pageStack.push( - "qrc:/qml/DevicePage.qml", + deviceComp, {currentDevice: device} ); } diff --git a/app/qml/PluginItem.qml b/app/qml/PluginItem.qml --- a/app/qml/PluginItem.qml +++ b/app/qml/PluginItem.qml @@ -39,7 +39,7 @@ if (component === "") return; - var obj = interfaceFactory.create(checker.deviceId); + var obj = interfaceFactory.create(checker.device.id()); var page = pageStack.push( component, { pluginInterface: obj } diff --git a/app/qml/mpris.qml b/app/qml/mpris.qml --- a/app/qml/mpris.qml +++ b/app/qml/mpris.qml @@ -44,8 +44,36 @@ 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 diff --git a/core/backends/bluetooth/bluetoothdevicelink.cpp b/core/backends/bluetooth/bluetoothdevicelink.cpp --- a/core/backends/bluetooth/bluetoothdevicelink.cpp +++ b/core/backends/bluetooth/bluetoothdevicelink.cpp @@ -51,12 +51,9 @@ 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); diff --git a/core/backends/bluetooth/bluetoothuploadjob.h b/core/backends/bluetooth/bluetoothuploadjob.h --- a/core/backends/bluetooth/bluetoothuploadjob.h +++ b/core/backends/bluetooth/bluetoothuploadjob.h @@ -1,5 +1,6 @@ /* * 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 @@ -45,9 +46,14 @@ 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/backends/bluetooth/bluetoothuploadjob.cpp b/core/backends/bluetooth/bluetoothuploadjob.cpp --- a/core/backends/bluetooth/bluetoothuploadjob.cpp +++ b/core/backends/bluetooth/bluetoothuploadjob.cpp @@ -1,5 +1,6 @@ /* * 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 @@ -43,43 +44,70 @@ 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/device.h b/core/device.h --- a/core/device.h +++ b/core/device.h @@ -23,8 +23,6 @@ #include #include -#include -#include #include #include "networkpacket.h" @@ -55,6 +53,7 @@ Laptop, Phone, Tablet, + Tv, }; /** @@ -73,10 +72,10 @@ ~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; @@ -88,7 +87,7 @@ 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; @@ -101,8 +100,8 @@ void cleanUnneededLinks(); - int protocolVersion() { return m_protocolVersion; } - QStringList supportedPlugins() const { return m_supportedPlugins.toList(); } + int protocolVersion(); + QStringList supportedPlugins() const; QHostAddress getLocalIpAddress() const; @@ -144,19 +143,9 @@ 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*) diff --git a/core/device.cpp b/core/device.cpp --- a/core/device.cpp +++ b/core/device.cpp @@ -21,6 +21,8 @@ #include "device.h" #include +#include +#include #include #include @@ -37,35 +39,63 @@ #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 @@ -76,37 +106,66 @@ 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); @@ -126,12 +185,12 @@ //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(); @@ -162,14 +221,14 @@ 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()); @@ -181,7 +240,7 @@ 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); } @@ -207,44 +266,44 @@ //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); } @@ -256,45 +315,45 @@ 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(); } @@ -306,11 +365,11 @@ 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); } @@ -322,7 +381,7 @@ 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; } @@ -333,7 +392,7 @@ { 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(); } @@ -355,8 +414,8 @@ 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; @@ -366,20 +425,20 @@ 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(); @@ -393,14 +452,16 @@ 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"); } @@ -417,7 +478,7 @@ 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) { @@ -432,15 +493,15 @@ 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) diff --git a/icon/32-status-tvconnected.png b/icon/32-status-tvconnected.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + +   + + + + diff --git a/icon/sc-status-tvdisconnected.svg b/icon/sc-status-tvdisconnected.svg new file mode 100644 --- /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 --- /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 --- a/indicator/org.kde.kdeconnect.nonplasma.desktop +++ b/indicator/org.kde.kdeconnect.nonplasma.desktop @@ -26,6 +26,7 @@ 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 diff --git a/interfaces/dbusinterfaces.h b/interfaces/dbusinterfaces.h --- a/interfaces/dbusinterfaces.h +++ b/interfaces/dbusinterfaces.h @@ -148,6 +148,10 @@ 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) diff --git a/interfaces/devicesmodel.h b/interfaces/devicesmodel.h --- a/interfaces/devicesmodel.h +++ b/interfaces/devicesmodel.h @@ -71,6 +71,7 @@ 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); @@ -84,7 +85,6 @@ void rowsChanged(); private: - int rowForDevice(const QString& id) const; void clearDevices(); void appendDevice(DeviceDbusInterface* dev); bool passesFilter(DeviceDbusInterface* dev) const; diff --git a/kcm/kcm.cpp b/kcm/kcm.cpp --- a/kcm/kcm.cpp +++ b/kcm/kcm.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include "ui_kcm.h" #include "interfaces/dbusinterfaces.h" @@ -46,7 +47,7 @@ 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)) @@ -115,6 +116,29 @@ 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() diff --git a/org.kde.kdeconnect.kcm.appdata.xml b/org.kde.kdeconnect.kcm.appdata.xml --- a/org.kde.kdeconnect.kcm.appdata.xml +++ b/org.kde.kdeconnect.kcm.appdata.xml @@ -108,6 +108,7 @@

У 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 diff --git a/plasmoid/package/contents/ui/DeviceDelegate.qml b/plasmoid/package/contents/ui/DeviceDelegate.qml --- a/plasmoid/package/contents/ui/DeviceDelegate.qml +++ b/plasmoid/package/contents/ui/DeviceDelegate.qml @@ -34,10 +34,6 @@ 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: @@ -89,7 +85,7 @@ Column { width: parent.width - + RowLayout { Item { @@ -101,7 +97,7 @@ id: battery device: root.device } - + PlasmaComponents.Label { horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight @@ -152,7 +148,7 @@ //RemoteKeyboard PlasmaComponents.ListItem { - visible: remoteKeyboardInput.available + visible: remoteKeyboard.remoteState width: parent.width Row { @@ -167,21 +163,15 @@ 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" } } diff --git a/plasmoid/package/metadata.desktop b/plasmoid/package/metadata.desktop --- a/plasmoid/package/metadata.desktop +++ b/plasmoid/package/metadata.desktop @@ -86,6 +86,7 @@ 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 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -4,20 +4,22 @@ 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() diff --git a/plugins/contacts/CMakeLists.txt b/plugins/contacts/CMakeLists.txt new file mode 100644 --- /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 --- /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.h b/plugins/contacts/contactsplugin.h new file mode 100644 --- /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/contactsplugin.cpp b/plugins/contacts/contactsplugin.cpp new file mode 100644 --- /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/kdeconnect_contacts.json b/plugins/contacts/kdeconnect_contacts.json new file mode 100644 --- /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 --- /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/mousepad_windows/mousepadplugin_windows.h b/plugins/findthisdevice/findthisdevice_config.h rename from plugins/mousepad_windows/mousepadplugin_windows.h rename to plugins/findthisdevice/findthisdevice_config.h --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/findthisdevice/findthisdevice_config.h @@ -1,5 +1,5 @@ /** - * 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 @@ -18,25 +18,33 @@ * 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.cpp b/plugins/findthisdevice/findthisdevice_config.cpp new file mode 100644 --- /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/findthisdevice/findthisdevice_config.ui b/plugins/findthisdevice/findthisdevice_config.ui new file mode 100644 --- /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/mousepad_windows/mousepadplugin_windows.h b/plugins/findthisdevice/findthisdeviceplugin.h copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/findthisdevice/findthisdeviceplugin.h --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/findthisdevice/findthisdeviceplugin.h @@ -1,5 +1,5 @@ /** - * 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 @@ -18,25 +18,30 @@ * 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/findthisdeviceplugin.cpp b/plugins/findthisdevice/findthisdeviceplugin.cpp new file mode 100644 --- /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/findthisdevice/kdeconnect_findthisdevice.json b/plugins/findthisdevice/kdeconnect_findthisdevice.json new file mode 100644 --- /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 --- /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 --- 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.h copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/abstractremoteinput.h --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/abstractremoteinput.h @@ -1,5 +1,5 @@ /** - * 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 @@ -18,25 +18,22 @@ * 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_windows/mousepadplugin_windows.h b/plugins/mousepad/abstractremoteinput.cpp copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/abstractremoteinput.cpp --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/abstractremoteinput.cpp @@ -1,5 +1,5 @@ /** - * 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 @@ -18,25 +18,11 @@ * 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/config-mousepad.h.cmake b/plugins/mousepad/config-mousepad.h.cmake --- 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 --- a/plugins/mousepad/kdeconnect_mousepad.json +++ b/plugins/mousepad/kdeconnect_mousepad.json @@ -89,7 +89,9 @@ ], "Version": "0.1" }, - "X-KdeConnect-OutgoingPacketType": [], + "X-KdeConnect-OutgoingPacketType": [ + "kdeconnect.mousepad.keyboardstate" + ], "X-KdeConnect-SupportedPacketType": [ "kdeconnect.mousepad.request" ] diff --git a/plugins/mousepad/mousepadplugin.h b/plugins/mousepad/mousepadplugin.h --- a/plugins/mousepad/mousepadplugin.h +++ b/plugins/mousepad/mousepadplugin.h @@ -1,5 +1,7 @@ /** - * 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 @@ -21,21 +23,12 @@ #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 @@ -47,25 +40,11 @@ ~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/mousepadplugin.cpp b/plugins/mousepad/mousepadplugin.cpp --- a/plugins/mousepad/mousepadplugin.cpp +++ b/plugins/mousepad/mousepadplugin.cpp @@ -1,6 +1,7 @@ /** - * 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 @@ -22,316 +23,67 @@ #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_windows/mousepadplugin_windows.h b/plugins/mousepad/waylandremoteinput.h copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/waylandremoteinput.h --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/waylandremoteinput.h @@ -1,5 +1,5 @@ /** - * 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 @@ -18,25 +18,34 @@ * 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/waylandremoteinput.cpp b/plugins/mousepad/waylandremoteinput.cpp new file mode 100644 --- /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/windowsremoteinput.h copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/windowsremoteinput.h --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/windowsremoteinput.h @@ -1,5 +1,5 @@ /** - * 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 @@ -18,25 +18,20 @@ * 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_windows/mousepadplugin_windows.cpp b/plugins/mousepad/windowsremoteinput.cpp rename from plugins/mousepad_windows/mousepadplugin_windows.cpp rename to plugins/mousepad/windowsremoteinput.cpp --- a/plugins/mousepad_windows/mousepadplugin_windows.cpp +++ b/plugins/mousepad/windowsremoteinput.cpp @@ -1,6 +1,5 @@ /** - * 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 @@ -19,27 +18,19 @@ * 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); @@ -93,6 +84,7 @@ input.mi.dwFlags = MOUSEEVENTF_WHEEL; input.mi.mouseData = dy; ::SendInput(1,&input,sizeof(INPUT)); +//TODO: Keyboard input support /* } else if (!key.isEmpty() || specialKey) { @@ -146,5 +138,3 @@ } return true; } - -#include "mousepadplugin_windows.moc" diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/mousepad/x11remoteinput.h copy from plugins/mousepad_windows/mousepadplugin_windows.h copy to plugins/mousepad/x11remoteinput.h --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mousepad/x11remoteinput.h @@ -1,5 +1,5 @@ /** - * 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 @@ -18,25 +18,27 @@ * 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/mousepadplugin.cpp b/plugins/mousepad/x11remoteinput.cpp copy from plugins/mousepad/mousepadplugin.cpp copy to plugins/mousepad/x11remoteinput.cpp --- a/plugins/mousepad/mousepadplugin.cpp +++ b/plugins/mousepad/x11remoteinput.cpp @@ -1,6 +1,6 @@ /** + * 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 @@ -19,26 +19,15 @@ * 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, @@ -48,7 +37,6 @@ MouseWheelDown = 5 }; -#if HAVE_X11 //Translation table to keep in sync within all the implementations int SpecialKeysMap[] = { 0, // Invalid @@ -85,59 +73,26 @@ 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]; @@ -150,15 +105,9 @@ 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); @@ -264,74 +213,8 @@ } return true; } -#endif - -#if HAVE_WAYLAND -void MousepadPlugin::setupWaylandIntegration() -{ - if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { - // not wayland - return; - } - using namespace KWayland::Client; - ConnectionThread* connection = ConnectionThread::fromApplication(this); - if (!connection) { - // failed to get the Connection from Qt - 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 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/CMakeLists.txt b/plugins/mousepad_windows/CMakeLists.txt deleted file mode 100644 --- 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 --- 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 --- a/plugins/mprisremote/CMakeLists.txt +++ b/plugins/mprisremote/CMakeLists.txt @@ -1,4 +1,4 @@ -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 diff --git a/plugins/mousepad_windows/mousepadplugin_windows.h b/plugins/mprisremote/mprisremoteplayer.h rename from plugins/mousepad_windows/mousepadplugin_windows.h rename to plugins/mprisremote/mprisremoteplayer.h --- a/plugins/mousepad_windows/mousepadplugin_windows.h +++ b/plugins/mprisremote/mprisremoteplayer.h @@ -1,5 +1,5 @@ /** - * 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 @@ -17,26 +17,37 @@ * 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/mprisremoteplayer.cpp b/plugins/mprisremote/mprisremoteplayer.cpp new file mode 100644 --- /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/mprisremote/mprisremoteplugin.h b/plugins/mprisremote/mprisremoteplugin.h --- a/plugins/mprisremote/mprisremoteplugin.h +++ b/plugins/mprisremote/mprisremoteplugin.h @@ -25,6 +25,8 @@ #include +#include "mprisremoteplayer.h" + #define PACKET_TYPE_MPRIS_REQUEST QStringLiteral("kdeconnect.mpris.request") #define PACKET_TYPE_MPRIS QStringLiteral("kdeconnect.mpris") @@ -40,18 +42,24 @@ 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); @@ -69,16 +77,10 @@ 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/mprisremote/mprisremoteplugin.cpp b/plugins/mprisremote/mprisremoteplugin.cpp --- a/plugins/mprisremote/mprisremoteplugin.cpp +++ b/plugins/mprisremote/mprisremoteplugin.cpp @@ -35,14 +35,8 @@ 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() { } @@ -55,45 +49,46 @@ 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}} ); @@ -109,47 +104,101 @@ 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/remotekeyboard/kdeconnect_remotekeyboard.json b/plugins/remotekeyboard/kdeconnect_remotekeyboard.json --- a/plugins/remotekeyboard/kdeconnect_remotekeyboard.json +++ b/plugins/remotekeyboard/kdeconnect_remotekeyboard.json @@ -38,6 +38,7 @@ "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", @@ -69,6 +70,7 @@ "Name[uk]": "Віддалена клавіатура з комп’ютера", "Name[x-test]": "xxRemote keyboard from the desktopxx", "Name[zh_CN]": "来自桌面的远程键盘", + "Name[zh_TW]": "從桌面遠端控制鍵盤", "ServiceTypes": [ "KdeConnect/Plugin" ], diff --git a/plugins/runcommand/runcommandplugin.cpp b/plugins/runcommand/runcommandplugin.cpp --- a/plugins/runcommand/runcommandplugin.cpp +++ b/plugins/runcommand/runcommandplugin.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,8 @@ 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; @@ -83,6 +86,11 @@ { 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); } diff --git a/plugins/sftp/mounter.cpp b/plugins/sftp/mounter.cpp --- a/plugins/sftp/mounter.cpp +++ b/plugins/sftp/mounter.cpp @@ -137,6 +137,7 @@ << 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") ; diff --git a/smsapp/CMakeLists.txt b/smsapp/CMakeLists.txt --- a/smsapp/CMakeLists.txt +++ b/smsapp/CMakeLists.txt @@ -1,6 +1,6 @@ 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}) diff --git a/smsapp/conversationmodel.h b/smsapp/conversationmodel.h new file mode 100644 --- /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/conversationmodel.cpp b/smsapp/conversationmodel.cpp new file mode 100644 --- /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/main.cpp b/smsapp/main.cpp --- a/smsapp/main.cpp +++ b/smsapp/main.cpp @@ -26,7 +26,9 @@ #include #include #include +#include "conversationmodel.h" #include "kdeconnect-version.h" +#include int main(int argc, char *argv[]) { @@ -46,6 +48,8 @@ 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")); diff --git a/smsapp/org.kde.kdeconnect.sms.desktop b/smsapp/org.kde.kdeconnect.sms.desktop --- a/smsapp/org.kde.kdeconnect.sms.desktop +++ b/smsapp/org.kde.kdeconnect.sms.desktop @@ -3,33 +3,51 @@ 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 diff --git a/smsapp/qml/ContactList.qml b/smsapp/qml/ContactList.qml --- a/smsapp/qml/ContactList.qml +++ b/smsapp/qml/ContactList.qml @@ -35,22 +35,19 @@ 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 @@ -68,7 +65,6 @@ delegate: Kirigami.BasicListItem { - id: mouse hoverEnabled: true readonly property var person: PersonData { @@ -86,10 +82,15 @@ } 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 --- a/smsapp/qml/ConversationDisplay.qml +++ b/smsapp/qml/ConversationDisplay.qml @@ -22,44 +22,47 @@ 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 --- a/urlhandler/org.kde.kdeconnect.telhandler.desktop +++ b/urlhandler/org.kde.kdeconnect.telhandler.desktop @@ -26,6 +26,7 @@ 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