Search code examples
c++qtdbusqtdbus

How to call a D-Bus interface from Qt with struct argument


I'm trying to ask polkit for authorization but I can't seem to find out how to provide a struct as an argument.

QDBusArgument subject;
subject.beginStructure();
subject << "unix-process";
subject << QMap<QString, QVariant>{
    {"pid", static_cast<uint32_t>(QCoreApplication::applicationPid())},
    {"start-time", static_cast<uint64_t>(0)},
};
subject.endStructure();

QDBusInterface polkit("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", "org.freedesktop.PolicyKit1.Authority");
auto result = polkit.callWithArgumentList(
    QDBus::CallMode::AutoDetect,
    "CheckAuthorization",
    {
      // how to provide the subject here?
    }
);

The QDBusArgument shows what the argument should look like and I somehow need to translate this to a format that can be used in callWithArgumentList.

FreeDesktop specifies the struct as such:

{
  String               subject_kind,
  Dict<String,Variant> subject_details
}

More specifically, I'm trying to replace this with a dbus call:

// checkProcess is *QProcess
checkProcess->start("pkcheck", {
    "--process",
    QString::number(QCoreApplication::applicationPid()),
    "--action-id",
    "my-custom-action-id",
    "--allow-user-interaction"
});

Solution

  • I'm not too familiar with D-Bus or its Qt interface, but I do happen to have a linux system I could test this on. It seems that you were already well on your way, I was able to get a response from the polkit service after some small adjustments:

    #include <QtDBus>
    
    int main() {
        // Not all Qt types are compatible with the D-Bus interface by default:
        // QMap<QString, QVariant> is, but QMap<QString, QString> is not.
        // If you need such a type, even if only to pass an empty
        // Dict<String, String>, it must first be registered like so:
        qDBusRegisterMetaType<QMap<QString, QString>>();
    
        // Subject kind should be provided as a QString, integers as Qt types
        QDBusArgument subject;
        subject.beginStructure();
        subject << QString("unix-process");
        subject << QMap<QString, QVariant>{
            {"pid", static_cast<quint32>(QCoreApplication::applicationPid())},
            {"start-time", static_cast<quint64>(0)},
        };
        subject.endStructure();
    
        // PolicyKit1 is on the system bus
        QDBusInterface polkit(
            "org.freedesktop.PolicyKit1",
            "/org/freedesktop/PolicyKit1/Authority",
            "org.freedesktop.PolicyKit1.Authority",
            QDBusConnection::systemBus()
        );
    
        // callWithArgumentList only takes actual QVariantLists as the arguments,
        // call is a more convenient variadic function template 
        auto result = polkit.call(
            QDBus::CallMode::AutoDetect,
            "CheckAuthorization",
            QVariant::fromValue(subject),
            "org.freedesktop.login1.set-user-linger",
            QVariant::fromValue(QMap<QString, QString>{}),
            0x1u, // AllowUserInteraction = 0x00000001
            ""
        );
        
        qInfo() << result;
    }
    

    This resulted in the following output:

    QDBusMessage(type=MethodReturn, service=":1.1", signature="(bba{ss})", contents=([Argument: (bba{ss}) false, true, [Argument: a{ss} {"polkit.retains_authorization_after_challenge" = "1"}]]) )

    Some further remarks:

    • Types registered with the D-Bus type system must be convertible to and from QDBusArgument by the << and >> operators, which is why you can run into issues when using those with non-Qt types. You can implement these operators for other types to allow registering them;
    • Types passed to a D-Bus call must be implicitly convertible to QVariant. If they are not, you can convert them explicitly using QVariant::fromValue().

    Once these requirements are met, you should be able to get meaningful output from D-Bus, even if you pass the wrong arguments. For example, leaving out the last string argument to CheckAuthorization leads to an InvalidArgs error response with the message

    Type of message, “((sa{sv})sa{ss}u)”, does not match expected type “((sa{sv})sa{ss}us)”

    The string encodes the expected types

    {
        { 
            String, 
            Dict<String, Variant>
        },
        String,
        Dict<String, String>,
        Uint32,
        String
    }