adding wallpaper to egmde

Before we begin

My previous egmde post described how to build and install MirAL. If you’re using Ubuntu 16.10 (Yakkety) then that is no longer necessary as MirAL is in the archive. All you need is:

$ sudo apt install libmiral-dev

Otherwise, you can follow the instructions there.

The egmde source for this article is available at the same place:

$ git clone https://github.com/AlanGriffiths/egmde.git

The example code

In this article we add some simple, Ubuntu orange wallpaper. The previous egmde.cpp file is largely unchanged we just add a couple of headers and update the main program that looked like this:

int main(int argc, char const* argv[])
{
    miral::MirRunner runner{argc, argv};

    return runner.run_with(
        {
            set_window_managment_policy<ExampleWindowManagerPolicy>()
        });
}

Now it is:

int main(int argc, char const* argv[])
{
    miral::MirRunner runner{argc, argv};
    Wallpaper wallpaper;
    runner.add_stop_callback([&] { wallpaper.stop(); });
    return runner.run_with(
        {
            miral::StartupInternalClient{"wallpaper", std::ref(wallpaper)},
            set_window_managment_policy<ExampleWindowManagerPolicy>()
        });
}

The Wallpaper class is what we’ll be implementing here, StartupInternalClient starts it as an in-process Mir client and the verbose lambda incantations work-around the limitations of the current MirAL implementation.

The Wallpaper class uses a simple “Worker” class to pass work off to a separate thread. I’ll only show the header here as the methods as self-explanatory:

class Worker
{
public:
    ~Worker();
    void start_work();
    void enqueue_work(std::function<void()> const& functor);
    void stop_work();

};

The Wallpaper class

class Wallpaper : Worker
{
public:
    // These operators are the protocol for an "Internal Client"
    void operator()(miral::toolkit::Connection c) { start(c); }
    void operator()(std::weak_ptr<mir::scene::Session> const&){ }

    void start(miral::toolkit::Connection connection);
    void stop();
private:
    std::mutex mutable mutex;
    miral::toolkit::Connection connection;
    miral::toolkit::Surface surface;
    void create_surface();
};

The start and stop methods are fairly self-explanatory:

void Wallpaper::start(miral::toolkit::Connection connection)
{
    {
        std::lock_guard<decltype(mutex)> lock{mutex};
        this->connection = connection;
    }
    enqueue_work([this]{ create_surface(); });
    start_work();
}
void Wallpaper::stop()
{
    {
        std::lock_guard<decltype(mutex)> lock{mutex};
        surface.reset();
        connection.reset();
    }
    stop_work();
}

Most of the work happens in the create_surface() method that creates a surface of a type that will never get focus (and therefore will never be raised above anything else):

void Wallpaper::create_surface()
{
    std::lock_guard<decltype(mutex)> lock{mutex};
    auto const spec = SurfaceSpec::for_normal_surface(
        connection, 100, 100, mir_pixel_format_xrgb_8888)
        .set_buffer_usage(mir_buffer_usage_software)
        .set_type(mir_surface_type_gloss)
        .set_name("wallpaper");

    mir_surface_spec_set_fullscreen_on_output(spec, 0);

    surface = spec.create_surface();
    uint8_t pattern[4] = { 0x14, 0x48, 0xDD, 0xFF };

    MirGraphicsRegion graphics_region;
    MirBufferStream* buffer_stream = mir_surface_get_buffer_stream(surface);
    mir_buffer_stream_get_graphics_region(buffer_stream, &graphics_region);

    render_pattern(&graphics_region, pattern);
    mir_buffer_stream_swap_buffers_sync(buffer_stream);
}

This is unsophisticated, but the point is that the client API is available to do whatever rendering we like.

Now, when we run egmde we no longer get a boring black rectangle. Now we get an Ubuntu orange one.

An Example Mir Desktop Environment

The world of Mir

Mir is a set of libraries supporting the development of display servers, desktop environments, shells and implementing toolkit support for applications running on them.

As part of their “convergence” of computing devices Canonical are committed to providing Mir on a range of platforms including desktops, phones, tablets and “the internet of thing”. You can read about the work to provide it as a “snap” in the “internet of things” on Kevin Gunn’s blog [kg’s blog].

Toolkits and other “client” platforms that have support for Mir include GTK, Qt and SDL – which mean that applications using these can work on Mir. There’s a separate Xmir project to support X11 applications on Mir.

On the driver side of things Mir works with Mesa on desktop-like systems and, using libhybris, on an android based stack on a number of devices. The Mesa support is a work in progress and Ubuntu carries a set of patches to enable it. There are some notes about how to incorporate these on other platforms here: [Enabling Mir EGL]. Work is in progress to upstream Mir support.

The “Mir Abstraction Layer” (MirAL) packages the server-side functionality in a way that makes it easy to use. My previous post[MirAL] introduced MirAL in more detail.

Before we begin

If you want to experiment with this code I suggest using Ubuntu 16.04 or later on desktop for the reasons mentioned above. On an Ubuntu phone you can use the current stable version with this code, but doing development on a phone is a whole other article. On other distributions you likely need to patch Mesa and rebuild toolkits with Mir support enabled.

Having got an Ubuntu desktop you also need to install MirAL, at present this is not available from the Ubuntu archives, so you have to build and install it yourself:

$ sudo apt-get install devscripts equivs bzr
$ bzr branch lp:miral
$ sudo mk-build-deps -i --build-dep miral/debian/control
$ mkdir miral/build
$ cd miral/build
$ cmake ..
$ make
$ sudo make install

egmde [Example Mir Desktop Environment]

To illustrate MirAL I’m going to show what is involved in writing a (very simple) window manager. It runs on desktops, tablets and phones and supports keyboard, mouse and touch input. It will support applications using the GTK and Qt toolkits, SDL applications and (using Xmir) X11 applications.

The full code for this example is available on github:

$ git clone https://github.com/AlanGriffiths/egmde.git

Naturally, the code is likely to evolve, especially if I write a follow-up article but the version presented here is tagged v1.0. Assuming that you’ve MirAL installed as described above you can build it as follows:

$ mkdir egmde/build
$ cd egmde/build
$ cmake ..
$ make

The example code

A lot of the functionality (default placement of windows, menus etc.) comes with the MirAL library. For this exercise we’ll implement one class and write a main function that injects it into MirAL. The main program looks like this:

int main(int argc, char const* argv[])
{
    miral::MirRunner runner{argc, argv};

    return runner.run_with(
        {
            set_window_managment_policy<ExampleWindowManagerPolicy>()
        });
}

Yes, you’ve guessed it: the class we’ll be implementing is ExampleWindowManagerPolicy. It looks like this:

class ExampleWindowManagerPolicy : public CanonicalWindowManagerPolicy
{
public:
    using CanonicalWindowManagerPolicy::CanonicalWindowManagerPolicy;

    // Switch apps  : Alt+Tab
    // Switch window: Alt+`
    // Close window : Alt-F4
    bool handle_keyboard_event(MirKeyboardEvent const* event) override;

    // Switch apps  : click on the corresponding window
    // Switch window: click on the corresponding window
    // Move window  : Alt-leftmousebutton drag
    // Resize window: Alt-middle_button drag
    bool handle_pointer_event(MirPointerEvent const* event) override;

    // Switch apps  : tap on the corresponding window
    // Switch window: tap on the corresponding window
    // Move window  : three finger drag
    // Resize window: three finger pinch
    bool handle_touch_event(MirTouchEvent const* event) override;

private:
    void pointer_resize(Window const& window, Point cursor, Point old_cursor);
    void resize(WindowInfo& window_info, Point new_pos, Size new_size);

    // State held for move/resize by pointer
    Point old_cursor{};
    bool resizing = false;
    bool is_left_resize = false;
    bool is_top_resize = false;

    // State held for move/resize by touch
    int old_touch_pinch_top = 0;
    int old_touch_pinch_left = 0;
    int old_touch_pinch_width = 0;
    int old_touch_pinch_height = 0;
};

For this simple example the only functionality we’ll be providing is the handling of input events to allow the user to switch between applications and windows and to resize and move windows.

We only need to provide three handle_XXX_event() functions as we’re relying on the default behaviour for everything else. The first of these functions is the simplest:

bool ExampleWindowManagerPolicy::handle_keyboard_event(MirKeyboardEvent const* event)
{
    auto const action = mir_keyboard_event_action(event);
    auto const shift_state = mir_keyboard_event_modifiers(event) & shift_states;

    if (action == mir_keyboard_action_down &&
        shift_state == mir_input_event_modifier_alt)
    {
        switch (mir_keyboard_event_scan_code(event))
        {
        case KEY_F4:
            tools.ask_client_to_close(tools.active_window());
            return true;

        case KEY_TAB:
            tools.focus_next_application();
            return true;

        case KEY_GRAVE:
            tools.focus_next_within_application();
            return true;

        }
    }

    return false;
}

I don’t think this needs a lot of explanation: we select key presses while only the Alt button is pressed and act according to the button pressed.

The other two functions are a bit longer as they need to remember some state in order to interpret mouse or touchpad gestures. There’s in them nothing I consider worth pointing out here and the full code is available on github. There’s not much:

$ wc *
15 28 446 CMakeLists.txt
357 957 10528 egmde.cpp
372 985 10974 total

I have much to compare it with, bit I don’t think 372 lines is bad for a functional window manager.

Running egmde

Once egdme has been built you can run it in two modes: the simplest is the “Mir-on-X” mode:

$ ./egmde

This will produce a rather boring black “Mir-on-X” window. You can attach Mir clients to this:

$ miral-run gnome-system-monitor
$ miral-run gnome-terminal

These will run in the window and make it less boring. You can also have egmde launch them when it starts as follows:

$ ./egmde --startup gnome-terminal:gnome-system-monitor

If you don’t want to run in a window under X11 you can also give egmde access to your hardware directly by running it as root and specifying a “virtual terminal”. But I think it is probably better to use mir_demo_server in “system-compositor” mode to handle the hardware and launch egdme a normal user.

$ sudo apt install mir-demos
$ sudo mir_demo_server --vt 3 --window-manager system-compositor

You’ll find this switches you to vt3, switch back with Ctrl-Alt-F7 and, from another terminal, you can connect egdme to this. (We need the VT switch here as the host mir_demo_server is paused while X11 is in control.)

$ sudo chvt 3&&./egmde --host /tmp/mir_socket --startup gnome-terminal

You don’t have to start the gnome-terminal, but it is convenient as you can launch other applications. For example:

$ mir_demo_client_eglplasma

Conclusion

I hope that the egmde example is enough to spark someone’s interest in MirAL and to provide a starting point for developing something more ambitious.

Much of what has been discussed is a work-in-progress, especially MirAL which I hope to get added to the Ubuntu archives soon. I am optimistic this will happen as it is in the interests of both Canonical and the wider community to have a stable server ABI against which to write desktop environments (including Unity8). Indeed I’ve been working with the main developer of the QtMir abstraction layer to migrate it to MirAL.

Similarly, a significant portion of the work needed on Mesa to support Mir is similar to the work to support Wayland/Weston and everyone would benefit from cleaning this up (and, for example, removing hard dependencies on X11).