Skip to content

Explaining Qyoto - QtDBus, generic types, properties, cmake and Qt Designer support

Tuesday, 6 February 2007  |  richard dale

The Qyoto project has made some good progress over the past few weeks. We've now switched to the .NET 2.0 gmcs mono compiler, with support for generic types amongst other neat features. Q_PROPERTYs are mapped onto C# propertys, which makes the code look a lot nicer. Arno Rehn has implemented a C# version of the Qt Designer uic tool called 'uics', and the code it generates uses the new properties. And another important change has been switching to cmake, and so we have a nearly sane build system.

Arno Rehn and Paolo Capriotti changed all the calls in the Qyoto runtime to use generics, and Arno got all the marshalling stuff converted pretty quickly while I worked on the C# code generation for the Qt classes. The combination of generic types and properties, along with improvements in the conversion of operator methods are really starting to make the api look very nice and very complete. I've done all the cannon game tutorials t1 to t14, and all the qdbus examples, and looking at the code, it has its own distinctive personality.

Using QtDBus is very easy. As in C++ you define a normal Qt class with slots and signals, and just export it to use over the bus with some simple method calls. Here is the Qt pingpong example in C#:

using Qyoto;
using System;
using System.Collections.Generic;

class Pong : QObject { static private string SERVICE_NAME = "com.trolltech.QtDBus.PingExample";

[Q_SLOT]
public string ping(string arg)
{
    QMetaObject.InvokeMethod(QCoreApplication.Instance(), "quit");
    return "ping(\"" + arg + "\") got called";
}

public static int Main(string[] args) {
    new QCoreApplication(args);

    if (!QDBusConnection.SessionBus().IsConnected()) {
        Console.Write("Cannot connect to the D-BUS session bus.\n" +
            "To start it, run:\n" +
            "\teval `dbus-launch --auto-syntax`\n");
        return 1;
    }

    if (!QDBusConnection.SessionBus().RegisterService(SERVICE_NAME)) {
        Console.WriteLine(QDBusConnection.SessionBus().LastError().Message());        
        return 1;
    }

    Pong pong = new Pong();
    QDBusConnection.SessionBus().RegisterObject("/", pong, (int) QDBusConnection.RegisterOption.ExportAllSlots);

    return QCoreApplication.Exec();
}

}

There is a slot called 'ping()' which takes a string as an argument and returns a string. The slot is marked with a '[Q_SLOT]' Attribute, and just by calling QDBusConnection.RegisterObject() you make the slot visible to dbus.

The client code to talk to it looks like this:

using Qyoto;
using System;
using System.Collections.Generic;

class Ping { static private string SERVICE_NAME = "com.trolltech.QtDBus.PingExample";

public static int Main(string[] args) {
    new QCoreApplication(args);

    if (!QDBusConnection.SessionBus().IsConnected()) {
        Console.WriteLine("Cannot connect to the D-BUS session bus.\n" +
            "To start it, run:\n" +
            "\teval `dbus-launch --auto-syntax`\n");
        return 1;
    }

    QDBusInterface iface = new QDBusInterface(SERVICE_NAME, "/", "", QDBusConnection.SessionBus());
    if (iface.IsValid()) {
        QDBusMessage message = iface.Call("ping", new QVariant(args.Length > 0 ? args[0] : ""));
        QDBusReply<string> reply = new QDBusReply<string>(message);
        if (reply.IsValid()) {
            Console.WriteLine("Reply was: {0}", reply.Value());
            return 0;
        }

        Console.WriteLine("Call failed: {0}\n", reply.Error().Message());
        return 1;
    }

    Console.WriteLine(QDBusConnection.SessionBus().LastError().Message());
    return 1;
}

}

The call 'iface.Call("ping", new QVariant(args.Length > 0 ? args[0] : ""));' invokes the ping slot and sends it any argument you passed on the command line.

mardigras rdale 564% mono ping.exe foobar
Reply was: ping("foobar") got called

I've been playing with the Tracker search indexing tool, and here's an example of how you deal with complex return types be making calls on the QDBusArgument instance returned:

using Qyoto;
using System;
using System.Text;
using System.Collections.Generic;

public class TrackerTest1 { static private string SERVICE_NAME = "org.freedesktop.Tracker";

public static int Main(string[] args) {
    new QCoreApplication(args);

    if (!QDBusConnection.SessionBus().IsConnected()) {
        Console.Write("Cannot connect to the D-BUS session bus.\n" +
            "To start it, run:\n" +
            "\teval `dbus-launch --auto-syntax`\n");
        return 1;
    }

    QDBusInterface iface = new QDBusInterface(  SERVICE_NAME, 
                                                "/org/freedesktop/tracker", 
                                                "org.freedesktop.Tracker" );
    if (iface.IsValid()) {
        QDBusMessage message = iface.Call("GetServices", new QVariant(false));
        QDBusReply<QDBusArgument> reply = new QDBusReply<QDBusArgument>(message);
        if (reply.IsValid()) {
            Dictionary<string, List<string>> result = new Dictionary<string, List<string>>();
            QDBusArgument replyValue = reply.Value();
            replyValue.BeginMap();

            while (!replyValue.AtEnd()) {
                replyValue.BeginMapEntry();

                StringBuilder key = new StringBuilder();
                replyValue.Read(key);
                Console.WriteLine(key);

                QDBusVariant value = QDBusVariant.FromValue<List<string>>(new List<string>());
                replyValue.Read(value);
                List<string> list = value.Value<List<string>>();
                foreach (string element in list) {
                    Console.WriteLine("    {0}", element);
                }

                result[key.ToString()] = list;
                replyValue.EndMapEntry();
            }

            replyValue.EndMap();
            return 0;
        }

        Console.WriteLine("Call failed: {0}\n", reply.Error().Message());
        return 1;
    }

    Console.WriteLine(QDBusConnection.SessionBus().LastError().Message());
    return 1;
}

}

The above code calls the 'GetServices()' method over dbus with a single boolean argument of 'false'. The reply is in the form of a hash with the keys as strings, and a values are a list of strings - and in C# that is a 'Dictionary<<string>, List<string>>' type. To start getting the values of the Hash, you call QDBusArgument.BeginMap(), and you finish with a QDBusArgument.EndMap(). Individual values are retrieved with QDBusArgument.Read() calls. One small annoyance in C# is that you can use 'operator<<' and 'operator>>' methods with the restriction that the second argument must be an int. So someone somewhere obviously really doesn't like them meaning 'read' and 'write', and you can only use them for shift operations.

mardigras rdale 584% mono tracker_test1.exe
Folders
    File
    folders only
VFS Files
    File
    all VFS based files
VFS Folders
    File
    VFS based folders only
...

The combination of Qt Designer support via the uics tool, and using dbus to invoke backend services like Tracker or Strigi is very powerful. We can move towards a more service oriented, desktop agnostic approach that way.

I've recently update the Qyoto TODO list, and there isn't that much to go now. Any help is most welcome - especially translating the example programs and tutorials like the cannon game one. David Canar has done a good job getting some websites ready for Qyoto like this one. I hope we can finally do a first release to put up on it in a month or two.