Casilda 1.0 released!

Version 1.0!

I am pleased to announce the release of Casilda version 1.0, a simple Wayland compositor widget for GTK 4 which can be used to embed other processes windows in your application.

This version includes lots of rendering improvements and optimizations together with new features and a simple API.

Etymology

Following Wayland tradition, this library is named after my hometown in Santa Fe, Argentina.

Why?

When I started working on Cambalache (a new GUI designer for GTK) one of the main design goals was to create all the preview widgets in a separate process to be able to support GTK 3 and 4 projects and avoid any situation that could take down the whole application.This requires a way to embed another process windows in a GtkWindow, initially I decided to use Broadway backend and a WebView as a stopgap measure until Cambalache was mature enough to justify investing time in making a proper Wayland compositor widget.

wlroots

After a couple of years the WebView approach started to show its limitations, so it was time to give the original idea of creating a Wayland compositor widget a try.

Thankfully, the wlroots library makes it relatively easy, especially with the help of it’s community!

You can read more about the first release of the compositor widget in the following post.

New Cambalache development release 0.91.1!

In a nutshell, Casilda utilizes wlroots to handle most of the wayland protocol, including the creation of virtual input and output devices.

It creates a wayland keyboard and pointer devices which then events are feed from a few GtkEventController in the Compositor widget.

It also creates an output device the same size as the widget allocation, which means each time the compositor widget size changes, wayland clients get a output resolution change event.

Native Rendering

Originally Casilda relied on wlroots to render the whole scene, get a pixel buffer and upload it to a cairo surface to render it in GTK. This worked, but was not optimal, for a number of reasons:

    • Shared memory buffers – Clients had to render in software mode
    • Extra copy to upload wlroots buffer to GTK for rendering

Luckily @valpackett decided to step in and added support for dmabufs which allows clients to use OpenGL or Vulkan to render.

Based on this work, I decided that it would be nice to go one step further and create a GdkTexture from each client window dmabuf and use it directly in the GTK scene graph.

This cuts all the middle men and extra copies/uploads and makes client windows directly accessible to render them in GtkWidget::snapshot()Here you can see the graphics updates of the compositor and how thanks to damage regions we do not need to update the parts that did not change.

Rendering natively adds some complexity but also gives us more flexibility. For example, I was able to easily tint the parent for transient windows.This is the actual code that tints the parent window, a simple color matrix transformation that gets converted somewhere in the rendering pipeline into a simple pixel shader.

  if (has_transient)
    {
      graphene_matrix_t matrix;
      graphene_vec4_t offset;
 
      graphene_matrix_init_from_float (&matrix,
                                       (float[16]) {
                                                     0.8, 0, 0, 0,
                                                     0, 0.8, 0, 0,
                                                     0, 0, 0.8, 0,
                                                     0, 0, 0, 1
                                                   });
 
      graphene_vec4_init (&offset, 0, 0, 0, 0);
      gtk_snapshot_push_color_matrix (data->snapshot, &matrix, &offset);
    }

Scrollable interface

CasildaCompositor now implements GtkScrollable interface, by default it does not have any adjustments set, but if you pack it inside a GtkScrolledWindow it will extend the size of the virtual output depending on the size and position of every client window.Currently it only expands the virtual output when you move a window over the left or bottom edge.

API – How to use it?

Casilda uses gi-docgen to generate it’s API reference documentation and it is available online here.

To embed another process window in your GTK 4 application all you have to do is create a CasildaCompositor widget and add it in the hierarchy just like any other widget.

You can specify which UNIX socket the compositor will listen on for client connections or use casilda_compositor_get_client_socket_fd() to get an already connected socket to the compositor.

compositor = casilda_compositor_new ("/tmp/casilda-example.sock");
gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (compositor));

Once the compositor is running you can connect to it by specifying the socket in the WAYLAND_DISPLAY environment variable.

export GDK_BACKEND=wayland
export WAYLAND_DISPLAY=/tmp/casilda-example.sock
gtk4-demo

If you do not want any client being able to connect to the compositor you can pass NULL as socket and spawn the client with casilda_compositor_spawn_async() or get an already connected socket with casilda_compositor_get_client_socket_fd() and pass it to the client with the WAYLAND_SOCKET environment variable.

compositor = casilda_compositor_new (NULL);
gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (compositor));
gtk_window_present (GTK_WINDOW (window));
 
gchar *argv[] = { "/usr/bin/gtk4-demo", NULL };
casilda_compositor_spawn_async (
    compositor, NULL, argv, NULL,
    G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL
);

Language Bindings

Casilda uses GObject introspection to enable bindings for other languages.

This is a full example in Python

#!/usr/bin/python3
 
import gi
import sys
 
gi.require_version("Gtk", "4.0")
gi.require_version("Casilda", "1.0")
from gi.repository import GLib, Gtk, Casilda
 
 
class CasildaApplication(Gtk.Application):
    def __init__(self):
        super().__init__(application_id="ar.xjuan.casilda.PyGObject.Example")
 
    def do_activate(self):
        compositor = Casilda.Compositor()
 
        window = Gtk.ApplicationWindow(
            application=self,
            title="Casilda Compositor",
            default_width=800,
            default_height=600,
            child=compositor
        )
 
        compositor.spawn_async (
            None,
            ["/usr/bin/gtk4-demo"],
            None,
            GLib.SpawnFlags.DEFAULT
        )
 
        window.present()
 
 
if __name__ == "__main__":
    app = CasildaApplication()
    sys.exit(app.run(sys.argv))

and in JavaScript

#!/usr/bin/gjs -m
 
import System from 'system';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
import Casilda from 'gi://Casilda?version=1.0';
 
 
let CasildaApplication = GObject.registerClass({},
    class CasildaApplication extends Gtk.Application {
    constructor() {
        super({
            application_id: 'ar.xjuan.casilda.Gjs.Example'
        });
    }
 
    vfunc_activate() {
        let compositor = new Casilda.Compositor();
 
        let window = new Gtk.ApplicationWindow({
            application: this,
            title: 'Casilda Compositor',
            default_width: 800,
            default_height: 600,
            child: compositor
        });
 
        window.connect('close-request', () => {
            this.quit();
        });
 
        compositor.spawn_async (
            null,
            ["/usr/bin/gtk4-demo"],
            null,
            GLib.SpawnFlags.DEFAULT,
            null
        );
 
        window.present();
    }
});
 
let app = new CasildaApplication();
app.run([System.programInvocationName].concat(ARGV));

This pretty much covers Cambalache use case and the old XEmbed protocol used by GtkSocket/GtkPlug widget classes.

All you need to do is put the widgets you want to embed in a full screen window and launch the process with spawn_async() .

The compositor will take care of the rest, including resizing the fullscreen window when the compositor size changes.

What’s next?

Besides bugfixes future releases will focus on upgrading to new wlroots releases and improving current features like keyboard layout support, touch devices, etc.

Where to get it?

Source code lives on GNOME gitlab here

git clone https://gitlab.gnome.org/jpu/casilda.git

Matrix channel

Have any question? come chat with us at #cambalache:gnome.org

Mastodon

Follow me in Mastodon @xjuan to get news related to Casilda and Cambalache development.

Happy coding!

Future of relative window positioning

With emerging display server technologies, toolkits sometimes need to adapt how they implement the features they provide. One set of features that needs adaptation is how GTK+ positions popup windows such as menus, popovers and tooltips, so that they will be placed within the work area of the monitor.

In the old days, when GTK+ wanted to position a menu, it would first look up the global position of the parent window of the menu. It would then look up the work areas of all the monitors connected. With the given work areas, the global position of the parent window, and the intended menu position relative to the parent window it wanted to place the menu, GTK+ would use a clever algorithm calculating a reasonable position for the menu to be placed so that it would be visible to the user. For example, if the File menu doesn’t have enough space to popup below the parent menu item, then GTK+ would re-position it above the parent menu item instead.

popup-flip

For various reasons the concept of “global window positions” has been removed from clients in these new display server technologies, meaning we cannot use our clever algorithm in GTK+ any more.

But we still want to make our menus, tooltips, popovers, etc. fully visible to the user expecting to interact with them, so how can we ensure this without knowing anything about where our windows are positioned?

To tackle this in GTK+, we had to solve a number of problems.

  • The positioning logic needs to be moved to GDK, while still allowing GTK+ to affect how the menu positioning behaves should the initially intended position end up outside of the work area.
  • Different GDK backends might do things differently.
  • Some types of windows need to know the position it ended up at, so they can adapt how they draw them self.
  • Some windows simply want to take up as much space as they may (for example a menu with far too many choices should not be taller than the screen).

Last year, William Hua and I started working on bringing GTK+ into the bright future of global position-less menu windows. After having come up with a set of patches doing just that, the discussion of how such an API would actually look like started. After 200-300 comments, we decided we should probably discuss this in person.

Enter GTK2016 in Toronto!

At the hackfest, we got the chance to sit down with a whiteboard in front of us and go through the different use cases, the problems that needed to be solved, how backends would work, and eventually we came up with an API.

william-draws-whiteboard(photo credit: Allison Lortie)

The API we came up with looks as this:

From the GDK side we introduce a new function (so far without any API stability promises; it’s intended to be used only by GTK+ so far) gdk_window_move_to_rect () which takes a set of arguments describing how the application wants its window to be placed in relation to some parent surface. It takes

  • a transient-for window

The parent window it is to be placed relative to an anchor rectangle on the parent window a popup or a menu will often want to be placed in relation to a rectangle on the parent, for example a right-click context menu should expand in some direction from the pixel the pointer was located at when clicking, or a file menu should be placed either below or above the file menu item rectangle on the parent window.

  • a rectangle anchor gravity

Different popup menus might want to open in a certain direction. For example a vertical menu might want to open to the right, while a horizontal menu might want to open downwards.

  • a window anchor gravity

Different popup menus might want to be aligned to the anchor rectangle of the parent anchor rectangle differently. For example, while a combo box might want to expand in a certain direction, it will want to cover the rectangle it expanded from.

  • an anchor hint

Different popup menus want to adjust their positions differently; some will want to expand in different directions from the parent anchor rectangle, some will want to just be slid into visibility, some will want to be resized, while some will want some combination of all the three.

  • an rectangle anchor offset

The offset is simply a nudge factor for a common use case where a popup menu will offset its position relative to an anchor.

By having GTK+ come up with a declarative description of how it wants its menu to be positioned, we allow GDK to implement the actual positioning differently depending on how the display server system is designed. On Mir, a MirSurfaceSpec is created, while on Wayland an xdg_positioner object is created. On X11, Windows and Mac OS X, the backends can use the available global positions as well as monitor work areas and calculate an optimal position just as before.

Application developers are, however, not expected to use this API directly yet. Normally what is wanted is to create a menu, a popover, a combo box, and for this we have introduced a set of parameters and helper functions to make this very convenient. The API consists of a few new properties:

  • GtkMenu:anchor-hints – the positioning strategy.
  • GtkMenu:rect-anchor-dx – horizontal offset to shift window.
  • GtkMenu:rect-anchor-dy – vertical offset to shift window.
  • GtkMenu:menu-type-hint – a window type – this is still needed so that the X11 backend can let the window manager know what type of popup window is being mapped.

and a few more functions:

  • gtk_menu_popup_at_rect () – given the parameters set, popup a menu relative to a given rectangle on a parent window.
  • gtk_menu_popup_at_widget () – given the parameters set, popup a menu relative to a given widget on a parent window.
  • gtk_menu_popup_at_pointer () – given the parameters set, popup a menu relative to where the user just clicked.

With these functions, developers of custom widgets can now position popup menus in a portable manner. So far, GTK+’s own popup menus have already been ported to use these new functions. There is already a basic proof-of-concept in the Mir backend, and a Wayland implementation is in progress.

Head over to the bug to see all the details of how to place your menus in the future.

gnome-sponsored-badge-shadow