[OpenDocString] kdeconnect-kde (cpp)
kdeconnect-cli.cpp
int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);
    KAboutData about(QStringLiteral("kdeconnect-cli"),
                     QStringLiteral("kdeconnect-cli"),
                     QStringLiteral(KDECONNECT_VERSION_STRING),
                     i18n("KDE Connect CLI tool"),
                     KAboutLicense::GPL,
                     i18n("(C) 2015 Aleix Pol Gonzalez"));
    KAboutData::setApplicationData(about);

    about.addAuthor(i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org"));
    about.addAuthor(i18n("Albert Vaca Cintora"), QString(), QStringLiteral("albertvaka@gmail.com"));
    QCommandLineParser parser;
    parser.addOption(QCommandLineOption(QStringList(QStringLiteral("l")) << QStringLiteral("list-devices"), i18n("List all devices")));
    parser.addOption(
        QCommandLineOption(QStringList(QStringLiteral("a")) << QStringLiteral("list-available"), i18n("List available (paired and reachable) devices")));
    parser.addOption(
        QCommandLineOption(QStringLiteral("id-only"), i18n("Make --list-devices or --list-available print only the devices id, to ease scripting")));
    parser.addOption(
        QCommandLineOption(QStringLiteral("name-only"), i18n("Make --list-devices or --list-available print only the devices name, to ease scripting")));
    parser.addOption(QCommandLineOption(QStringLiteral("id-name-only"),
                                        i18n("Make --list-devices or --list-available print only the devices id and name, to ease scripting")));
    parser.addOption(QCommandLineOption(QStringLiteral("refresh"), i18n("Search for devices in the network and re-establish connections")));
    parser.addOption(QCommandLineOption(QStringLiteral("pair"), i18n("Request pairing to a said device")));
    parser.addOption(QCommandLineOption(QStringLiteral("ring"), i18n("Find the said device by ringing it.")));
    parser.addOption(QCommandLineOption(QStringLiteral("unpair"), i18n("Stop pairing to a said device")));
    parser.addOption(QCommandLineOption(QStringLiteral("ping"), i18n("Sends a ping to said device")));
    parser.addOption(QCommandLineOption(QStringLiteral("ping-msg"), i18n("Same as ping but you can set the message to display"), i18n("message")));
    parser.addOption(QCommandLineOption(QStringLiteral("share"), i18n("Share a file/URL to a said device"), QStringLiteral("path or URL")));
    parser.addOption(QCommandLineOption(QStringLiteral("share-text"), i18n("Share text to a said device"), QStringLiteral("text")));
    parser.addOption(QCommandLineOption(QStringLiteral("list-notifications"), i18n("Display the notifications on a said device")));
    parser.addOption(QCommandLineOption(QStringLiteral("lock"), i18n("Lock the specified device")));
    parser.addOption(QCommandLineOption(QStringLiteral("unlock"), i18n("Unlock the specified device")));
    parser.addOption(QCommandLineOption(QStringLiteral("send-sms"), i18n("Sends an SMS. Requires destination"), i18n("message")));
    parser.addOption(QCommandLineOption(QStringLiteral("destination"), i18n("Phone number to send the message"), i18n("phone number")));
    parser.addOption(QCommandLineOption(QStringLiteral("attachment"),
                                        i18n("File urls to send attachments with the message (can be passed multiple times)"),
                                        i18n("file urls")));
    parser.addOption(QCommandLineOption(QStringList(QStringLiteral("device")) << QStringLiteral("d"), i18n("Device ID"), QStringLiteral("dev")));
    parser.addOption(QCommandLineOption(QStringList(QStringLiteral("name")) << QStringLiteral("n"), i18n("Device Name"), QStringLiteral("name")));
    parser.addOption(QCommandLineOption(QStringLiteral("encryption-info"), i18n("Get encryption info about said device")));
    parser.addOption(QCommandLineOption(QStringLiteral("list-commands"), i18n("Lists remote commands and their ids")));
    parser.addOption(QCommandLineOption(QStringLiteral("execute-command"), i18n("Executes a remote command by id"), QStringLiteral("id")));
    parser.addOption(
        QCommandLineOption(QStringList{QStringLiteral("k"), QStringLiteral("send-keys")}, i18n("Sends keys to a said device"), QStringLiteral("key")));
    parser.addOption(QCommandLineOption(QStringLiteral("my-id"), i18n("Display this device's id and exit")));
    parser.addOption(QCommandLineOption(QStringLiteral("photo"), i18n("Open the connected device's camera and transfer the photo"), QStringLiteral("path")));

    // Hidden because it's an implementation detail
    QCommandLineOption deviceAutocomplete(QStringLiteral("shell-device-autocompletion"));
    deviceAutocomplete.setFlags(QCommandLineOption::HiddenFromHelp);
    deviceAutocomplete.setDescription(
        QStringLiteral("Outputs all available devices id's with their name and paired status")); // Not visible, so no translation needed
    deviceAutocomplete.setValueName(QStringLiteral("shell"));
    parser.addOption(deviceAutocomplete);
    about.setupCommandLine(&parser);

    parser.process(app);
    about.processCommandLine(&parser);

    const QString id = QStringLiteral("kdeconnect-cli-") + QString::number(QCoreApplication::applicationPid());
    DaemonDbusInterface iface;

    if (parser.isSet(QStringLiteral("my-id"))) {
        QTextStream(stdout) << iface.selfId() << Qt::endl;
    } else if (parser.isSet(QStringLiteral("l")) || parser.isSet(QStringLiteral("a"))) {
        bool available = false;
        if (parser.isSet(QStringLiteral("a"))) {
            available = true;
        } else {
            blockOnReply(iface.acquireDiscoveryMode(id));
            QThread::sleep(2);
        }
        const QStringList devices = blockOnReply(iface.devices(available, available));

        bool displayCount = true;
        for (const QString &id : devices) {
            if (parser.isSet(QStringLiteral("id-only"))) {
                QTextStream(stdout) << id << Qt::endl;
                displayCount = false;
            } else if (parser.isSet(QStringLiteral("name-only"))) {
                DeviceDbusInterface deviceIface(id);
                QTextStream(stdout) << deviceIface.name() << Qt::endl;
                displayCount = false;
            } else if (parser.isSet(QStringLiteral("id-name-only"))) {
                DeviceDbusInterface deviceIface(id);
                QTextStream(stdout) << id << ' ' << deviceIface.name() << Qt::endl;
                displayCount = false;
            } else {
                DeviceDbusInterface deviceIface(id);
                QString statusInfo;
                const bool isReachable = deviceIface.isReachable();
                const bool isTrusted = deviceIface.isTrusted();
                if (isReachable && isTrusted) {
                    statusInfo = i18n("(paired and reachable)");
                } else if (isReachable) {
                    statusInfo = i18n("(reachable)");
                } else if (isTrusted) {
                    statusInfo = i18n("(paired)");
                }
                QTextStream(stdout) << "- " << deviceIface.name() << ": " << deviceIface.id() << ' ' << statusInfo << Qt::endl;
            }
        }
        if (displayCount) {
            QTextStream(stderr) << i18np("1 device found", "%1 devices found", devices.size()) << Qt::endl;
        } else if (devices.isEmpty()) {
            QTextStream(stderr) << i18n("No devices found") << Qt::endl;
        }

        blockOnReply(iface.releaseDiscoveryMode(id));
    } else if (parser.isSet(QStringLiteral("shell-device-autocompletion"))) {
        // Outputs a list of reachable devices in zsh autocomplete format, with the name as description
        const QStringList devices = blockOnReply(iface.devices(true, false));
        for (const QString &id : devices) {
            DeviceDbusInterface deviceIface(id);
            QString statusInfo;
            const bool isTrusted = deviceIface.isTrusted();
            if (isTrusted) {
                statusInfo = i18n("(paired)");
            } else {
                statusInfo = i18n("(unpaired)");
            }

            // Description: "device name (paired/unpaired)"
            QString description = deviceIface.name() + QLatin1Char(' ') + statusInfo;
            // Replace characters
            description.replace(QLatin1Char('\\'), QStringLiteral("\\\\"));
            description.replace(QLatin1Char('['), QStringLiteral("\\["));
            description.replace(QLatin1Char(']'), QStringLiteral("\\]"));
            description.replace(QLatin1Char('\''), QStringLiteral("\\'"));
            description.replace(QLatin1Char('\"'), QStringLiteral("\\\""));
            description.replace(QLatin1Char('\n'), QLatin1Char(' '));
            description.remove(QLatin1Char('\0'));

            // Output id and description
            QTextStream(stdout) << id << '[' << description << ']' << Qt::endl;
        }

        // Exit with 1 if we didn't find a device
        return int(devices.isEmpty());
    } else if (parser.isSet(QStringLiteral("refresh"))) {
        QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
                                                          QStringLiteral("/modules/kdeconnect"),
                                                          QStringLiteral("org.kde.kdeconnect.daemon"),
                                                          QStringLiteral("forceOnNetworkChange"));
        blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));
    } else {
        QString device = parser.value(QStringLiteral("device"));
        if (device.isEmpty() && parser.isSet(QStringLiteral("name"))) {
            device = blockOnReply(iface.deviceIdByName(parser.value(QStringLiteral("name"))));
            if (device.isEmpty()) {
                QTextStream(stderr) << "Couldn't find device: " << parser.value(QStringLiteral("name")) << Qt::endl;
                return 1;
            }
        }
        if (device.isEmpty()) {
            QTextStream(stderr) << i18n(
                "No device specified: Use -d  or -n  to specify a device. \nDevice ID's and names may be found using \"kdeconnect-cli "
                "-l\" \nView complete help with --help option")
                                << Qt::endl;
            return 1;
        }

        if (!blockOnReply(iface.devices(false, false)).contains(device)) {
            QTextStream(stderr) << "Couldn't find device with id \"" << device << "\". To specify a device by name use -n " << Qt::endl;
            return 1;
        }

        if (parser.isSet(QStringLiteral("share"))) {
            QStringList urls;

            QUrl url = QUrl::fromUserInput(parser.value(QStringLiteral("share")), QDir::currentPath());
            urls.append(url.toString());

            // Check for more arguments
            const auto args = parser.positionalArguments();
            for (const QString &input : args) {
                QUrl url = QUrl::fromUserInput(input, QDir::currentPath());
                urls.append(url.toString());
            }

            QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
                                                              QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/share"),
                                                              QStringLiteral("org.kde.kdeconnect.device.share"),
                                                              QStringLiteral("shareUrls"));

            msg.setArguments(QVariantList() << QVariant(urls));
            blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));

            for (const QString &url : qAsConst(urls)) {
                QTextStream(stdout) << i18n("Shared %1", url) << Qt::endl;
            }
        } else if (parser.isSet(QStringLiteral("share-text"))) {
            QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
                                                              QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/share"),
                                                              QStringLiteral("org.kde.kdeconnect.device.share"),
                                                              QStringLiteral("shareText"));
            msg.setArguments(QVariantList() << parser.value(QStringLiteral("share-text")));
            blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));
            QTextStream(stdout) << i18n("Shared text: %1", parser.value(QStringLiteral("share-text"))) << Qt::endl;
        } else if (parser.isSet(QStringLiteral("lock")) || parser.isSet(QStringLiteral("unlock"))) {
            LockDeviceDbusInterface iface(device);
            iface.setLocked(parser.isSet(QStringLiteral("lock")));

            DeviceDbusInterface deviceIface(device);
            if (parser.isSet(QStringLiteral("lock"))) {
                QTextStream(stdout) << i18nc("device has requested to lock peer device", "Requested to lock %1.", deviceIface.name()) << Qt::endl;
            } else {
                QTextStream(stdout) << i18nc("device has requested to unlock peer device", "Requested to unlock %1.", deviceIface.name()) << Qt::endl;
            }
        } else if (parser.isSet(QStringLiteral("pair"))) {
            DeviceDbusInterface dev(device);
            if (!dev.isReachable()) {
                // Device doesn't exist, go into discovery mode and wait up to 30 seconds for the device to appear
                QEventLoop wait;
                QTextStream(stderr) << i18n("waiting for device...") << Qt::endl;
                blockOnReply(iface.acquireDiscoveryMode(id));

                QObject::connect(&iface, &DaemonDbusInterface::deviceAdded, &iface, [&](const QString &deviceAddedId) {
                    if (device == deviceAddedId) {
                        wait.quit();
                    }
                });
                QTimer::singleShot(30 * 1000, &wait, &QEventLoop::quit);

                wait.exec();
            }

            if (!dev.isReachable()) {
                QTextStream(stderr) << i18n("Device not found") << Qt::endl;
            } else if (blockOnReply(dev.isTrusted())) {
                QTextStream(stderr) << i18n("Already paired") << Qt::endl;
            } else {
                QTextStream(stderr) << i18n("Pair requested") << Qt::endl;
                blockOnReply(dev.requestPair());
            }
            blockOnReply(iface.releaseDiscoveryMode(id));
        } else if (parser.isSet(QStringLiteral("unpair"))) {
            DeviceDbusInterface dev(device);
            if (!dev.isTrusted()) {
                QTextStream(stderr) << i18n("Already not paired") << Qt::endl;
            } else {
                QTextStream(stderr) << i18n("Unpaired") << Qt::endl;
                blockOnReply(dev.unpair());
            }
        } else if (parser.isSet(QStringLiteral("ping")) || parser.isSet(QStringLiteral("ping-msg"))) {
            QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
                                                              QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/ping"),
                                                              QStringLiteral("org.kde.kdeconnect.device.ping"),
                                                              QStringLiteral("sendPing"));
            if (parser.isSet(QStringLiteral("ping-msg"))) {
                QString message = parser.value(QStringLiteral("ping-msg"));
                msg.setArguments(QVariantList() << message);
            }
            blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));
        } else if (parser.isSet(QStringLiteral("send-sms"))) {
            if (parser.isSet(QStringLiteral("destination"))) {
                qDBusRegisterMetaType();
                QVariantList addresses;

                const QStringList addressList = parser.value(QStringLiteral("destination")).split(QRegularExpression(QStringLiteral("\\s+")));

                for (const QString &input : addressList) {
                    ConversationAddress address(input);
                    addresses << QVariant::fromValue(address);
                }

                const QString message = parser.value(QStringLiteral("send-sms"));

                const QStringList rawAttachmentUrlsList = parser.values(QStringLiteral("attachment"));

                QVariantList attachments;
                for (const QString &attachmentUrl : rawAttachmentUrlsList) {
                    // TODO: Construct attachment objects from the list of Urls
                    Q_UNUSED(attachmentUrl);
                }

                DeviceConversationsDbusInterface conversationDbusInterface(device);
                auto reply = conversationDbusInterface.sendWithoutConversation(addresses, message, attachments);

                reply.waitForFinished();
            } else {
                QTextStream(stderr) << i18n("error: should specify the SMS's recipient by passing --destination ") << Qt::endl;
                return 1;
            }
        } else if (parser.isSet(QStringLiteral("ring"))) {
            QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
                                                              QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/findmyphone"),
                                                              QStringLiteral("org.kde.kdeconnect.device.findmyphone"),
                                                              QStringLiteral("ring"));
            blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));
        } else if (parser.isSet(QStringLiteral("photo"))) {
            const QString fileName = parser.value(QStringLiteral("photo"));
            if (!fileName.isEmpty()) {
                QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
                                                                  QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/photo"),
                                                                  QStringLiteral("org.kde.kdeconnect.device.photo"),
                                                                  QStringLiteral("requestPhoto"));
                msg.setArguments({QUrl::fromLocalFile(fileName).toString()});
                blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));
            } else {
                QTextStream(stderr) << i18n("Please specify a filename for the photo") << Qt::endl;
            }
        } else if (parser.isSet(QStringLiteral("send-keys"))) {
            QString seq = parser.value(QStringLiteral("send-keys"));
            QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
                                                              QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/remotekeyboard"),
                                                              QStringLiteral("org.kde.kdeconnect.device.remotekeyboard"),
                                                              QStringLiteral("sendKeyPress"));
            if (seq.trimmed() == QLatin1String("-")) {
                // from stdin
                QFile in;
                if (in.open(stdin, QIODevice::ReadOnly | QIODevice::Unbuffered)) {
                    while (!in.atEnd()) {
                        QByteArray line = in.readLine(); // sanitize to ASCII-codes > 31?
                        msg.setArguments({QString::fromLatin1(line), -1, false, false, false});
                        blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));
                    }
                    in.close();
                }
            } else {
                msg.setArguments({seq, -1, false, false, false});
                blockOnReply(QDBusConnection::sessionBus().asyncCall(msg));
            }
        } else if (parser.isSet(QStringLiteral("list-notifications"))) {
            NotificationsModel notifications;
            notifications.setDeviceId(device);
            for (int i = 0, rows = notifications.rowCount(); i < rows; ++i) {
                QModelIndex idx = notifications.index(i);
                QTextStream(stdout) << "- " << idx.data(NotificationsModel::AppNameModelRole).toString() << ": "
                                    << idx.data(NotificationsModel::NameModelRole).toString() << Qt::endl;
            }
        } else if (parser.isSet(QStringLiteral("list-commands"))) {
            RemoteCommandsDbusInterface iface(device);
            const auto cmds = QJsonDocument::fromJson(iface.commands()).object();
            for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it != itEnd; ++it) {
                const QJsonObject cont = it->toObject();
                QTextStream(stdout) << it.key() << ": " << cont.value(QStringLiteral("name")).toString() << ": "
                                    << cont.value(QStringLiteral("command")).toString() << Qt::endl;
            }
        } else if (parser.isSet(QStringLiteral("execute-command"))) {
            RemoteCommandsDbusInterface iface(device);
            blockOnReply(iface.triggerCommand(parser.value(QStringLiteral("execute-command"))));
        } else if (parser.isSet(QStringLiteral("encryption-info"))) {
            DeviceDbusInterface dev(device);
            QString info = blockOnReply(dev.encryptionInfo()); // QSsl::Der = 1
            QTextStream(stdout) << info << Qt::endl;
        } else {
            QTextStream(stderr) << i18n("Nothing to be done") << Qt::endl;
        }
    }
    QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);

    return app.exec();
}
This constructor builds a QApplication object and its sub-commands. It takes all of the arguments from the command line, sets all of the options, sets all of the devices available, and available for communication, and responds to the request. If the user is following a request to a device and is reachable, it is allowed to send a message to the device. It returns 0 if the user is following a request and a response to a device. If the user is following a request and a response is given, it is paired with the device. It is blocked by the user, it is blocked.