File indexing completed on 2024-04-14 04:51:37
0001 /* 0002 * SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include <QCoreApplication> 0008 #include <QCryptographicHash> 0009 #include <QDBusMessage> 0010 #include <QFile> 0011 #include <QIODevice> 0012 #include <QTextStream> 0013 0014 #include <KAboutData> 0015 0016 #include "interfaces/conversationmessage.h" 0017 #include "interfaces/dbushelpers.h" 0018 #include "interfaces/dbusinterfaces.h" 0019 #include "interfaces/devicesmodel.h" 0020 #include "interfaces/notificationsmodel.h" 0021 #include "kdeconnect-version.h" 0022 0023 #include <dbushelper.h> 0024 0025 int main(int argc, char **argv) 0026 { 0027 QCoreApplication app(argc, argv); 0028 KAboutData about(QStringLiteral("kdeconnect-cli"), 0029 QStringLiteral("kdeconnect-cli"), 0030 QStringLiteral(KDECONNECT_VERSION_STRING), 0031 i18n("KDE Connect CLI tool"), 0032 KAboutLicense::GPL, 0033 i18n("(C) 2015 Aleix Pol Gonzalez")); 0034 KAboutData::setApplicationData(about); 0035 0036 about.addAuthor(i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org")); 0037 about.addAuthor(i18n("Albert Vaca Cintora"), QString(), QStringLiteral("albertvaka@gmail.com")); 0038 QCommandLineParser parser; 0039 parser.addOption(QCommandLineOption(QStringList{QStringLiteral("l"), QStringLiteral("list-devices")}, i18n("List all devices"))); 0040 parser.addOption( 0041 QCommandLineOption(QStringList{QStringLiteral("a"), QStringLiteral("list-available")}, i18n("List available (paired and reachable) devices"))); 0042 parser.addOption( 0043 QCommandLineOption(QStringLiteral("id-only"), i18n("Make --list-devices or --list-available print only the devices id, to ease scripting"))); 0044 parser.addOption( 0045 QCommandLineOption(QStringLiteral("name-only"), i18n("Make --list-devices or --list-available print only the devices name, to ease scripting"))); 0046 parser.addOption(QCommandLineOption(QStringLiteral("id-name-only"), 0047 i18n("Make --list-devices or --list-available print only the devices id and name, to ease scripting"))); 0048 parser.addOption(QCommandLineOption(QStringLiteral("refresh"), i18n("Search for devices in the network and re-establish connections"))); 0049 parser.addOption(QCommandLineOption(QStringLiteral("pair"), i18n("Request pairing to a said device"))); 0050 parser.addOption(QCommandLineOption(QStringLiteral("ring"), i18n("Find the said device by ringing it."))); 0051 parser.addOption(QCommandLineOption(QStringLiteral("unpair"), i18n("Stop pairing to a said device"))); 0052 parser.addOption(QCommandLineOption(QStringLiteral("ping"), i18n("Sends a ping to said device"))); 0053 parser.addOption(QCommandLineOption(QStringLiteral("ping-msg"), i18n("Same as ping but you can set the message to display"), i18n("message"))); 0054 parser.addOption(QCommandLineOption(QStringLiteral("send-clipboard"), i18n("Sends the current clipboard to said device"))); 0055 parser.addOption(QCommandLineOption(QStringLiteral("share"), i18n("Share a file/URL to a said device"), QStringLiteral("path or URL"))); 0056 parser.addOption(QCommandLineOption(QStringLiteral("share-text"), i18n("Share text to a said device"), QStringLiteral("text"))); 0057 parser.addOption(QCommandLineOption(QStringLiteral("list-notifications"), i18n("Display the notifications on a said device"))); 0058 parser.addOption(QCommandLineOption(QStringLiteral("lock"), i18n("Lock the specified device"))); 0059 parser.addOption(QCommandLineOption(QStringLiteral("unlock"), i18n("Unlock the specified device"))); 0060 parser.addOption(QCommandLineOption(QStringLiteral("send-sms"), i18n("Sends an SMS. Requires destination"), i18n("message"))); 0061 parser.addOption(QCommandLineOption(QStringLiteral("destination"), i18n("Phone number to send the message"), i18n("phone number"))); 0062 parser.addOption(QCommandLineOption(QStringLiteral("attachment"), 0063 i18n("File urls to send attachments with the message (can be passed multiple times)"), 0064 i18n("file urls"))); 0065 parser.addOption(QCommandLineOption(QStringList{QStringLiteral("device"), QStringLiteral("d")}, i18n("Device ID"), QStringLiteral("dev"))); 0066 parser.addOption(QCommandLineOption(QStringList{QStringLiteral("name"), QStringLiteral("n")}, i18n("Device Name"), QStringLiteral("name"))); 0067 parser.addOption(QCommandLineOption(QStringLiteral("encryption-info"), i18n("Get encryption info about said device"))); 0068 parser.addOption(QCommandLineOption(QStringLiteral("list-commands"), i18n("Lists remote commands and their ids"))); 0069 parser.addOption(QCommandLineOption(QStringLiteral("execute-command"), i18n("Executes a remote command by id"), QStringLiteral("id"))); 0070 parser.addOption( 0071 QCommandLineOption(QStringList{QStringLiteral("k"), QStringLiteral("send-keys")}, i18n("Sends keys to a said device"), QStringLiteral("key"))); 0072 parser.addOption(QCommandLineOption(QStringLiteral("my-id"), i18n("Display this device's id and exit"))); 0073 0074 // Hidden because it's an implementation detail 0075 QCommandLineOption deviceAutocomplete(QStringLiteral("shell-device-autocompletion")); 0076 deviceAutocomplete.setFlags(QCommandLineOption::HiddenFromHelp); 0077 deviceAutocomplete.setDescription( 0078 QStringLiteral("Outputs all available devices id's with their name and paired status")); // Not visible, so no translation needed 0079 deviceAutocomplete.setValueName(QStringLiteral("shell")); 0080 parser.addOption(deviceAutocomplete); 0081 about.setupCommandLine(&parser); 0082 0083 parser.process(app); 0084 about.processCommandLine(&parser); 0085 0086 DaemonDbusInterface iface; 0087 0088 if (parser.isSet(QStringLiteral("my-id"))) { 0089 QTextStream(stdout) << iface.selfId() << Qt::endl; 0090 } else if (parser.isSet(QStringLiteral("l")) || parser.isSet(QStringLiteral("a"))) { 0091 bool available = false; 0092 if (parser.isSet(QStringLiteral("a"))) { 0093 available = true; 0094 } else { 0095 QThread::sleep(2); 0096 } 0097 const QStringList devices = blockOnReply<QStringList>(iface.devices(available, available)); 0098 0099 bool displayCount = true; 0100 for (const QString &id : devices) { 0101 if (parser.isSet(QStringLiteral("id-only"))) { 0102 QTextStream(stdout) << id << Qt::endl; 0103 displayCount = false; 0104 } else if (parser.isSet(QStringLiteral("name-only"))) { 0105 DeviceDbusInterface deviceIface(id); 0106 QTextStream(stdout) << deviceIface.name() << Qt::endl; 0107 displayCount = false; 0108 } else if (parser.isSet(QStringLiteral("id-name-only"))) { 0109 DeviceDbusInterface deviceIface(id); 0110 QTextStream(stdout) << id << ' ' << deviceIface.name() << Qt::endl; 0111 displayCount = false; 0112 } else { 0113 DeviceDbusInterface deviceIface(id); 0114 QString statusInfo; 0115 const bool isReachable = deviceIface.isReachable(); 0116 const bool isPaired = deviceIface.isPaired(); 0117 if (isReachable && isPaired) { 0118 statusInfo = i18n("(paired and reachable)"); 0119 } else if (isReachable) { 0120 statusInfo = i18n("(reachable)"); 0121 } else if (isPaired) { 0122 statusInfo = i18n("(paired)"); 0123 } 0124 QTextStream(stdout) << "- " << deviceIface.name() << ": " << deviceIface.id() << ' ' << statusInfo << Qt::endl; 0125 } 0126 } 0127 if (displayCount) { 0128 QTextStream(stderr) << i18np("1 device found", "%1 devices found", devices.size()) << Qt::endl; 0129 } else if (devices.isEmpty()) { 0130 QTextStream(stderr) << i18n("No devices found") << Qt::endl; 0131 } 0132 0133 } else if (parser.isSet(QStringLiteral("shell-device-autocompletion"))) { 0134 // Outputs a list of reachable devices in zsh autocomplete format, with the name as description 0135 const QStringList devices = blockOnReply<QStringList>(iface.devices(true, false)); 0136 for (const QString &id : devices) { 0137 DeviceDbusInterface deviceIface(id); 0138 QString statusInfo; 0139 const bool isPaired = deviceIface.isPaired(); 0140 if (isPaired) { 0141 statusInfo = i18n("(paired)"); 0142 } else { 0143 statusInfo = i18n("(unpaired)"); 0144 } 0145 0146 // Description: "device name (paired/unpaired)" 0147 QString description = deviceIface.name() + QLatin1Char(' ') + statusInfo; 0148 // Replace characters 0149 description.replace(QLatin1Char('\\'), QStringLiteral("\\\\")); 0150 description.replace(QLatin1Char('['), QStringLiteral("\\[")); 0151 description.replace(QLatin1Char(']'), QStringLiteral("\\]")); 0152 description.replace(QLatin1Char('\''), QStringLiteral("\\'")); 0153 description.replace(QLatin1Char('\"'), QStringLiteral("\\\"")); 0154 description.replace(QLatin1Char('\n'), QLatin1Char(' ')); 0155 description.remove(QLatin1Char('\0')); 0156 0157 // Output id and description 0158 QTextStream(stdout) << id << '[' << description << ']' << Qt::endl; 0159 } 0160 0161 // Exit with 1 if we didn't find a device 0162 return int(devices.isEmpty()); 0163 } else if (parser.isSet(QStringLiteral("refresh"))) { 0164 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), 0165 QStringLiteral("/modules/kdeconnect"), 0166 QStringLiteral("org.kde.kdeconnect.daemon"), 0167 QStringLiteral("forceOnNetworkChange")); 0168 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0169 } else { 0170 QString device = parser.value(QStringLiteral("device")); 0171 if (device.isEmpty() && parser.isSet(QStringLiteral("name"))) { 0172 device = blockOnReply(iface.deviceIdByName(parser.value(QStringLiteral("name")))); 0173 if (device.isEmpty()) { 0174 QTextStream(stderr) << "Couldn't find device: " << parser.value(QStringLiteral("name")) << Qt::endl; 0175 return 1; 0176 } 0177 } 0178 if (device.isEmpty()) { 0179 QTextStream(stderr) << i18n( 0180 "No device specified: Use -d <Device ID> or -n <Device Name> to specify a device. \nDevice ID's and names may be found using \"kdeconnect-cli " 0181 "-l\" \nView complete help with --help option") 0182 << Qt::endl; 0183 return 1; 0184 } 0185 0186 if (!blockOnReply<QStringList>(iface.devices(false, false)).contains(device)) { 0187 QTextStream(stderr) << "Couldn't find device with id \"" << device << "\". To specify a device by name use -n <devicename>" << Qt::endl; 0188 return 1; 0189 } 0190 0191 if (parser.isSet(QStringLiteral("share"))) { 0192 QStringList urls; 0193 0194 QString firstArg = parser.value(QStringLiteral("share")); 0195 const auto args = QStringList(firstArg) + parser.positionalArguments(); 0196 0197 for (const QString &input : args) { 0198 QUrl url = QUrl::fromUserInput(input, QDir::currentPath()); 0199 if (url.isEmpty()) { 0200 qWarning() << "URL not valid:" << input; 0201 continue; 0202 } 0203 urls.append(url.toString()); 0204 } 0205 0206 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), 0207 QLatin1String("/modules/kdeconnect/devices/%1/share").arg(device), 0208 QStringLiteral("org.kde.kdeconnect.device.share"), 0209 QStringLiteral("shareUrls")); 0210 0211 msg.setArguments(QVariantList{QVariant(urls)}); 0212 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0213 0214 for (const QString &url : qAsConst(urls)) { 0215 QTextStream(stdout) << i18n("Shared %1", url) << Qt::endl; 0216 } 0217 } else if (parser.isSet(QStringLiteral("share-text"))) { 0218 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), 0219 QLatin1String("/modules/kdeconnect/devices/%1/share").arg(device), 0220 QStringLiteral("org.kde.kdeconnect.device.share"), 0221 QStringLiteral("shareText")); 0222 msg.setArguments(QVariantList{parser.value(QStringLiteral("share-text"))}); 0223 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0224 QTextStream(stdout) << i18n("Shared text: %1", parser.value(QStringLiteral("share-text"))) << Qt::endl; 0225 } else if (parser.isSet(QStringLiteral("lock")) || parser.isSet(QStringLiteral("unlock"))) { 0226 LockDeviceDbusInterface iface(device); 0227 iface.setLocked(parser.isSet(QStringLiteral("lock"))); 0228 0229 DeviceDbusInterface deviceIface(device); 0230 if (parser.isSet(QStringLiteral("lock"))) { 0231 QTextStream(stdout) << i18nc("device has requested to lock peer device", "Requested to lock %1.", deviceIface.name()) << Qt::endl; 0232 } else { 0233 QTextStream(stdout) << i18nc("device has requested to unlock peer device", "Requested to unlock %1.", deviceIface.name()) << Qt::endl; 0234 } 0235 } else if (parser.isSet(QStringLiteral("pair"))) { 0236 DeviceDbusInterface dev(device); 0237 if (!dev.isReachable()) { 0238 // Device doesn't exist, go into discovery mode and wait up to 30 seconds for the device to appear 0239 QEventLoop wait; 0240 QTextStream(stderr) << i18n("waiting for device...") << Qt::endl; 0241 0242 QObject::connect(&iface, &DaemonDbusInterface::deviceAdded, &iface, [&](const QString &deviceAddedId) { 0243 if (device == deviceAddedId) { 0244 wait.quit(); 0245 } 0246 }); 0247 QTimer::singleShot(30 * 1000, &wait, &QEventLoop::quit); 0248 0249 wait.exec(); 0250 } 0251 0252 if (!dev.isReachable()) { 0253 QTextStream(stderr) << i18n("Device not found") << Qt::endl; 0254 } else if (blockOnReply<bool>(dev.isPaired())) { 0255 QTextStream(stderr) << i18n("Already paired") << Qt::endl; 0256 } else { 0257 QTextStream(stderr) << i18n("Pair requested") << Qt::endl; 0258 blockOnReply(dev.requestPairing()); 0259 } 0260 } else if (parser.isSet(QStringLiteral("unpair"))) { 0261 DeviceDbusInterface dev(device); 0262 if (!dev.isPaired()) { 0263 QTextStream(stderr) << i18n("Already not paired") << Qt::endl; 0264 } else { 0265 QTextStream(stderr) << i18n("Unpaired") << Qt::endl; 0266 blockOnReply(dev.unpair()); 0267 } 0268 } else if (parser.isSet(QStringLiteral("send-clipboard"))) { 0269 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), 0270 QLatin1String("/modules/kdeconnect/devices/%1/clipboard").arg(device), 0271 QStringLiteral("org.kde.kdeconnect.device.clipboard"), 0272 QStringLiteral("sendClipboard")); 0273 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0274 } else if (parser.isSet(QStringLiteral("ping")) || parser.isSet(QStringLiteral("ping-msg"))) { 0275 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), 0276 QLatin1String("/modules/kdeconnect/devices/%1/ping").arg(device), 0277 QStringLiteral("org.kde.kdeconnect.device.ping"), 0278 QStringLiteral("sendPing")); 0279 if (parser.isSet(QStringLiteral("ping-msg"))) { 0280 QString message = parser.value(QStringLiteral("ping-msg")); 0281 msg.setArguments(QVariantList{message}); 0282 } 0283 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0284 } else if (parser.isSet(QStringLiteral("send-sms"))) { 0285 if (parser.isSet(QStringLiteral("destination"))) { 0286 qDBusRegisterMetaType<ConversationAddress>(); 0287 QVariantList addresses; 0288 0289 const QStringList addressList = parser.value(QStringLiteral("destination")).split(QRegularExpression(QStringLiteral("\\s+"))); 0290 0291 for (const QString &input : addressList) { 0292 ConversationAddress address(input); 0293 addresses << QVariant::fromValue(address); 0294 } 0295 0296 const QString message = parser.value(QStringLiteral("send-sms")); 0297 0298 const QStringList rawAttachmentUrlsList = parser.values(QStringLiteral("attachment")); 0299 0300 QVariantList attachments; 0301 for (const QString &attachmentUrl : rawAttachmentUrlsList) { 0302 // TODO: Construct attachment objects from the list of Urls 0303 Q_UNUSED(attachmentUrl); 0304 } 0305 0306 DeviceConversationsDbusInterface conversationDbusInterface(device); 0307 auto reply = conversationDbusInterface.sendWithoutConversation(addresses, message, attachments); 0308 0309 reply.waitForFinished(); 0310 } else { 0311 QTextStream(stderr) << i18n("error: should specify the SMS's recipient by passing --destination <phone number>") << Qt::endl; 0312 return 1; 0313 } 0314 } else if (parser.isSet(QStringLiteral("ring"))) { 0315 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), 0316 QLatin1String("/modules/kdeconnect/devices/%1/findmyphone").arg(device), 0317 QStringLiteral("org.kde.kdeconnect.device.findmyphone"), 0318 QStringLiteral("ring")); 0319 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0320 } else if (parser.isSet(QStringLiteral("send-keys"))) { 0321 QString seq = parser.value(QStringLiteral("send-keys")); 0322 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), 0323 QLatin1String("/modules/kdeconnect/devices/%1/remotekeyboard").arg(device), 0324 QStringLiteral("org.kde.kdeconnect.device.remotekeyboard"), 0325 QStringLiteral("sendKeyPress")); 0326 if (seq.trimmed() == QLatin1String("-")) { 0327 // from stdin 0328 QFile in; 0329 if (in.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered)) { 0330 while (!in.atEnd()) { 0331 QByteArray line = in.readLine(); // sanitize to ASCII-codes > 31? 0332 msg.setArguments({QString::fromLatin1(line), -1, false, false, false}); 0333 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0334 } 0335 in.close(); 0336 } 0337 } else { 0338 msg.setArguments({seq, -1, false, false, false}); 0339 blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); 0340 } 0341 } else if (parser.isSet(QStringLiteral("list-notifications"))) { 0342 NotificationsModel notifications; 0343 notifications.setDeviceId(device); 0344 for (int i = 0, rows = notifications.rowCount(); i < rows; ++i) { 0345 QModelIndex idx = notifications.index(i); 0346 QTextStream(stdout) << "- " << idx.data(NotificationsModel::AppNameModelRole).toString() << ": " 0347 << idx.data(NotificationsModel::NameModelRole).toString() << Qt::endl; 0348 } 0349 } else if (parser.isSet(QStringLiteral("list-commands"))) { 0350 RemoteCommandsDbusInterface iface(device); 0351 const auto cmds = QJsonDocument::fromJson(iface.commands()).object(); 0352 for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it != itEnd; ++it) { 0353 const QJsonObject cont = it->toObject(); 0354 QTextStream(stdout) << it.key() << ": " << cont.value(QStringLiteral("name")).toString() << ": " 0355 << cont.value(QStringLiteral("command")).toString() << Qt::endl; 0356 } 0357 } else if (parser.isSet(QStringLiteral("execute-command"))) { 0358 RemoteCommandsDbusInterface iface(device); 0359 blockOnReply(iface.triggerCommand(parser.value(QStringLiteral("execute-command")))); 0360 } else if (parser.isSet(QStringLiteral("encryption-info"))) { 0361 DeviceDbusInterface dev(device); 0362 QString info = blockOnReply<QString>(dev.encryptionInfo()); // QSsl::Der = 1 0363 QTextStream(stdout) << info << Qt::endl; 0364 } else { 0365 QTextStream(stderr) << i18n("Nothing to be done") << Qt::endl; 0366 } 0367 } 0368 QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); 0369 0370 return app.exec(); 0371 }