Thursday, August 25, 2016

Introducing snapd-glib

World, meet snapd-glib. It's a new library that makes it easy for GLib based projects (e.g. software centres) to access the daemon that allows you to query, install and remove Snaps. If C is not for you, you can use all the functionality in Python (using GObject Introspection) and Vala. In the future it will support Qt/QML through a wrapper library.

snapd uses a REST API and snapd-glib very closely matches that. The behaviour is best understood by reading the documentation of that API. To give you a taste of how it works, here's an example that shows how to find and install the VLC snap.

Step 1: Connecting to snapd

The connection to snapd is controlled through the SnapdClient object. This object has all the methods required to communicate with snapd. Create and connect with:

    g_autoptr(SnapdClient) c = snapd_client_new ();
    if (!snapd_client_connect_sync (c, NULL, &error))
        // Something went wrong


Step 2: Find the VLC snap 

Asking snapd to perform a find causes it to contact the remote Snap store. This can take some time so consider using an asynchronous call for this. This is the synchronous version:

    g_autoptr(GPtrArray) snaps =
        snapd_client_find_sync (c,
                                SNAPD_FIND_FLAGS_NONE, "vlc",
                                NULL, NULL, &error);
    if (snaps == NULL)
        // Something went wrong
    for (int i = 0; i < snaps->len; i++) {
        SnapdSnap *snap = snaps->pdata[i];
        // Do something with this snap information
    }


Step 3: Authenticate 

Some methods require authorisation in the form of a Macaroon (the link is quite complex but in practise it's just a couple of strings). To get a Macaroon you need to provide credentials to snapd. In Ubuntu this is your Ubuntu account, but different snapd installations may use another authentication provider.

Convert credentials to authorization with:

    g_autoptr(SnapdAuthData) auth_data =
        snapd_login_sync (email, password, code,
                          NULL, &error);
    if (auth_data == NULL)
        return EXIT_FAILURE;

    snapd_client_set_auth_data (c, auth_data)

Once you have a Macaroon you can store it somewhere and re-use it next time you need it. Then the authorization can be created with:

    g_autoptr(SnapdAuthData) auth_data =
        snapd_auth_data_new (macaroon, discharges);
    snapd_client_set_auth_data (c, auth_data);

Step 4: Install VLC 

In step 2 we could determine the VLC snap has the name "vlc". Since this involves downloading ~100Mb and is going to take some time the asynchronous method is used. There is a callback that gives updates on the progress of the install and one that is called when the operation completes:
 
    snapd_client_install_async (c,
                                "vlc", NULL,
                                progress_cb, NULL,
                                NULL,
                                install_cb, NULL);


static void
progress_cb (SnapdClient *client,

             SnapdTask *main_task, GPtrArray *tasks,
             gpointer user_data)
{

    // Tell the user what's happening
}

static void
install_cb (GObject *object, GAsyncResult *result,

            gpointer user_data)
{
    g_autoptr(GError) error = NULL;

    if (snapd_client_install_finish (SNAPD_CLIENT (object),

                                     result, &error))
        // It installed!
    else
        // Something went wrong...
}


Conclusion 

With snapd-glib and the above code as a starting point you should be able to start integrating Snap support into your project. Have fun!

Wednesday, May 11, 2016

Developing for Ubuntu Phone without the SDK

I'm not a fan of IDEs. So when developing apps for Ubuntu Phone, I went looking for a way of doing everything from the command line. I'll describe the method I'm currently using to do this. It's pretty rough and there are probably better ways of doing this, but it does work!

For an example, I've make a small application that:
  • Has some C++ code
  • Has some QML code
  • Uses gettext for translations
I need to do the following things:
  • Extract translatable strings from source files and merge in translations
  • Cross-compile for the target device (ARM CPU) from my laptop (AMD64 CPU)
  • Create a click package
  • Deploy the click package to my phone for testing
I'm using make for the build system. It's pretty basic, but it's fairly easy to understand what it does.

The project

My example project has the following files:
 
Makefile
hello.apparmor
hello.cpp
hello.desktop.in
hello.h
hello.png
main.qml
manifest.json
po/fr.po
po/de.po
po/hello.robert-ancell.pot
share/locale/de/LC_MESSAGES/
share/locale/fr/LC_MESSAGES/

If you've done some Ubuntu phone apps hopefully these should be familiar.

Translations 

To make my app translatable to other languages (French and German in this example) I've used gettext:
  • The .desktop file needs translations added to it. This is done by using hello.dekstop.in and prefixing the fields that need translating with '_' (e.g. _Name). These are then combined with the translations to make hello.desktop.
  • In the .qml files translatable strings are marked with i18n.tr ("text to translate").
  • The compiled message catalogues (.mo files) need to be in the click package as share/locale/(language)/(appid).mo.
Gettext / intltool are somewhat scary to use, but here's the magic rules that work for me:

hello.desktop: hello.desktop.in po/*.po
    intltool-merge --desktop-style po $< $@

po/hello.robert-ancell.pot: main.qml hello.desktop.in
    xgettext --from-code=UTF-8 --language=JavaScript --keyword=tr --keyword=tr:1,2 --add-comments=TRANSLATORS main.qml -o po/hello.robert-ancell.pot
    intltool-extract --type=gettext/keys hello.desktop.in
    xgettext --keyword=N_ hello.desktop.in.h -j -o po/hello.robert-ancell.pot
    rm -f hello.desktop.in.h

share/locale/%/LC_MESSAGES/hello.robert-ancell.mo: po/%.po
    msgfmt -o $@ $<

Cross-compiling for the target device

To compile our package I need to make a chroot:

$ sudo click chroot -a armhf -f ubuntu-sdk-15.04 create

The following Makefile rule runs make inside this chroot, then packages the results into a click file:

click:
        click chroot -a armhf -f ubuntu-sdk-15.04 run ARCH_PREFIX=arm-linux-gnueabihf- make
        click build --ignore=Makefile --ignore=*.cpp --ignore=*.h --ignore=*.pot --ignore=*.po --ignore=*.in --ignore=po .

Note the ARCH_PREFIX variable in the above. I've used this to run the correct cross-compiler when inside the chroot. When compiling from my laptop this variable is not set so it uses the local compiler.

hello_moc.cpp: hello.h
    moc $< -o $@

hello: hello.cpp hello_moc.cpp
    $(ARCH_PREFIX)g++ -g -Wall -std=c++11 -fPIC $^ -o $@ `$(ARCH_PREFIX)pkg-config --cflags --libs Qt5Widgets Qt5Quick`

Running 'make click' in this project will spit out hello.robert-ancell_1_armhf.click.

Testing

I connect my phone with a USB cable and copy the click package over and install it locally:

$ adb push hello.robert-ancell_1_armhf.click /tmp/hello.robert-ancell_1_armhf.click
$ phablet-shell
$ pkcon install-local --allow-untrusted /tmp/hello.robert-ancell_1_armhf.click

Then on the phone I quit any instances of this app running, refresh the app scope (pull down at the top) and run my test version.

Summary

Just one more rule to add to the Makefile, then it all works:

all: hello \
     hello.desktop \
     po/hello.robert-ancell.pot \
     share/locale/de/LC_MESSAGES/hello.robert-ancell.mo \
     share/locale/fr/LC_MESSAGES/hello.robert-ancell.mo

The whole example is available in Launchpad:

$ bzr branch lp:~robert-ancell/+junk/hello-example

Happy hacking!

Tuesday, April 05, 2016

Five hundred days using Ubuntu Phone

Today is my five hundredth day of using the Meizu MX4 Ubuntu Edition exclusively as my mobile phone. This is a nice piece of hardware (good power, good camera and simple but elegant design).

Here's what I've learnt.

I have written a bunch of phone apps you can install and blogged it. Writing for the Ubuntu phone is by far the easiest platform I've developed for. Click packaging works really well and the speed at which you can release to the Ubuntu store and get the update on your phone is incredible. QML allows you to build beautiful apps quickly however can be a challenge when apps get more complicated. Qt / C++ is functional, but feels lacking compared to more modern languages. If I could get Swift and an improved QML working together I'd be very happy. I initially used the Ubuntu SDK for building and deplying the apps but have now switched to doing everything on the command line (I've never found an IDE that doesn't feel over-engineered).

There's more than enough apps in the store to keep me happy. In fact, I have installed far more apps than I ever did on my Android phone. I think that is because I really trust the Ubuntu store in a way I never did in Android (too much crap there).

I initially thought webapps wouldn't be useful but they're a good option when there's no native app. I use webapps for social networking and news sites and am pretty happy with that. They're definitely not as good as a native app but feel slightly more integrated than just visiting using your web browser.

Scopes. I can see there's something there in the concept but even though I've tried I've never found them useful. The only scope I have is the app scope (i.e. the traditional grid of applications). I'm hoping a few more iterations and they will find a place on my phone.

Love getting updates. Both system and app updates occur frequently and bring improvements. Unless you had a Nexus device you are more or less abandoned in the Android world - with Ubuntu the complete opposite.

The polish is not quite there compared to Android, but it's getting better quickly. There's little quirks / crashes that are annoying but nothing that stops me from using it all day. A couple more releases and the unforgiving mainstream will be able to thrash it too.

Here's to another five hundred days!