Jean Pierre Dudey

Jean Pierre Dudey

Systems Programmer

© 2021

Rust and Vala State of the Art

I have been using Elementary OS for a while, and - in my opinion - it provides a great user experience on Linux mainly using a combination of Vala and GTK+ for the user applications. And with some curiosity I checked if Vala and Rust could talk to each other.

Elementary OS Screenshot
Screenshot of my Elementary setup, default background

Vala

Vala is an OOP language that has been in use and made by GNOME Developers, it uses the GObject type system underneath to support almost all the language features, such as classes, interfaces, etc. It compiles down to C and provides a compatible C FFI interface for the generated code, so any library made in Vala can be used in other languages, like any other library that uses GObject (e.g. GTK+, Pango, etc).

Generating bindings to other languages should be pretty straightforward, you can use the automatically-generated GObject Introspection system, which is an XML file with all the information you will need to generate any FFI code for your language.

Gettings the hands dirty

I tried to use the GIR files to generate bindings for the Granite library made by the Elementary team which is written in Vala and provides nice widgets for GTK+ that look awesome on Elementary.

The Gtk-rs team made a tool to automate the process of generating Rust FFI code and wrappers for the GObject based libraries that already provide .gir files.

The gir tool came into my hands and some minutes later I got to test it with Granite, and the Gee library on which Granite depends.

The first thing you have to do is to have all your GIR files in a folder like the gtk-rs project gir-files repository, I forked it (here), added the bare GIR files for Granite and Gee.

The journey started with Gee which had all sorts of problems on the GIR file and the gir tool couldn’t process, such as the classes weren’t providing a C symbol prefix, the return value of void function wasn’t specified, so at this point I decided to get my hands dirty on the valac compiler and fix some of the GIR code generation routines.

I provided a little merge request which with some help of the maintainer some tests were added to confirm the behaviour, and made it to the work in progress tree of the next release.

As an alternative, while the valac packages on Ubuntu based distributions get updated with those changes, I add some code to the fix.sh script that is used by the Gtk-rs team, I added a single command of 160 lines full of not my not-so-proud-of XPaths to edit the Gee GIR file and fix the errors, the same was done with the Granite file, but this one was relatively small in comparision with only 74 lines. Most of these fixes will be removed when the new GIR files are released, there are still some things that needs to be fixed, such as the “generics” which the valac compiler generates in some way that the gtk-rs gir tool doesn’t like and doesn’t supports. But that is on my to-do list to investigate and solve one of these weekends.

At the end the files were working and gir started to generate Rust code that talked to vala, but still with some problems, such as the missing self parameter on the gee-sys and granite-sys crates that were generated, and subsequently on the wrapper crates gee and granite causing a lot of runtime errors. This is still unfixed and will have likely have to be fixed on the valac GIR code generation unit itself, since it’s a lot of work to write hundreds of lines more to fix the GIR files.

Fixing the errors on the valac compiler also helps to use other libraries that are made in Vala.

Putting it all together

With all these bugs I still managed to create a window and use the Granite.Widgets.Welcome widget on an application I’m working on to use SDR devices.

It was pretty easy to use on Rust after all that work:

use granite::prelude::*;
use granite::widgets::Welcome;

pub struct WelcomeView {
    welcome: Welcome,
}

impl WelcomeView {
    pub fn new() -> WelcomeView {
        let welcome = Welcome::new("No Devices Open", "Open a device to start.");
        // Can't be done as append doesn't have the self parameter :-(
        //welcome.append(None, "Open", "Open a connected device.");

        WelcomeView { welcome }
    }

    pub fn widget(&self) -> &Widget {
        self.welcome.as_ref()
    }
}

And adding it to a window:

use gio::prelude::*;
use gtk::prelude::*;

use gtk::{Application, ApplicationWindow};

mod views;
mod config;
mod widgets;

use views::WelcomeView;
use widgets::HeaderBar;

fn main() {
    let application = Application::new(Some(config::APP_ID), gio::ApplicationFlags::empty())
        .expect("failed to initialize radio-rs GUI");

    application.set_resource_base_path(Some("/tech/jeandudey/radio-rs"));

    let res = gio::Resource::load(config::PKGDATADIR.to_owned() + "/radio-rs-resources.gresource")
        .expect("couldn't load gresource file");
    gio::resources_register(&res);

    application.connect_activate(|app| {
        let header_bar = HeaderBar::new();
        header_bar.set_title("Radio-rs");

        let window = ApplicationWindow::new(app);
        window.set_titlebar(&header_bar);
        window.set_default_size(640, 400);

        // Show the welcome view
        let welcome_view = WelcomeView::new(&window);
        window.add(welcome_view.widget());
        window.show_all();
    });

    application.run(&[]);
}

With this beautiful result, that does nothing for now, but with some love aranite and other libraries could work with Rust in a future:

Elementary OS Screenshot

Where’s the code?

  • The fork of gir-files with the fixes.
  • Granite-rs repository with some code generated used for the application I’m doing.