FLTK
|
This chapter describes how the Wayland backend of FLTK works from a developer's viewpoint.
Wayland usage involves communication via a socket between a client application and another process called the Wayland compositor which creates, moves, resizes and draws windows on the display. Diverse Wayland compositors exist. They can follow rather diverse logics. For example, FreeBSD offers Sway which is a tiling compositor where the display is always entirely filled with whatever windows are mapped at any given time. Compositors follow either the client-side decoration (CSD) rule where client apps draw window titlebars, or the server-side decoration (SSD) rule where the compositor draws titlebars. FLTK supports both CSD and SSD compositors. It bundles a library called libdecor
charged of determining whether a CSD or a SSD compositor is active, and of drawing titlebars in the first case.
Wayland is divided in various protocols that a given compositor may or may not support, although they all support the core
protocol. The core protocol allows a client app to discover what protocols its compositor supports. Protocols can be stable, which means they have a defined API that will not change but can be expanded, or unstable. For example, mapping a window on a display is not done by the core protocol but by the xdg shell
protocol which is stable. Unstable protocols are named beginning with letter 'z'. For example, the protocol FLTK uses to support CJK input methods is called zwp_text_input_v3
and is, unfortunately, unstable.
Wayland differs noticeably from X11 in that the position of a window in the display is completely hidden to the client app. Besides toplevel and sub-windows, Wayland allows to create popup windows positioned relatively to a previously mapped other window. This allows FLTK to create menus and tooltips, but it seriously complicates the algorithm to pilot menus, because the previous algorithm conceived for other platforms assumes the position of a window in the display to be known to the client app, which is wrong under Wayland.
Wayland makes intensive use of the 'listener' mechanism. A listener is a small array of pointers to FLTK-defined callback functions associated to a Wayland-defined object; Wayland calls these functions when defined events occur and transmits relevant information to the client app as parameters of these calls. Each listener is first associated to its corresponding Wayland object by a call to a specific Wayland function of the form wl_XXX_add_listener()
.
Wayland uses a trick of its own to handle lists of linked records. It defines the opaque type struct wl_list
and the macro wl_list_for_each(arg1, arg2, arg3)
where:
arg1
is a pointer variable of the type of elements of the linked list;arg2
is the address of a variable of type struct wl_list
identifying the targetted list;arg3
is the name of the member variable of these elements used to link them together.For example, wl_list_for_each()
can be used as follows to scan the linked list of all displays of the system (see output):
Fl_Wayland_Screen_Driver::output *output;
Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
wl_list_for_each(output, &(scr_driver->outputs), link) {
… work with output, a member of the linked list of all displays in the system …
}
Other macros (wl_list_init()
, wl_list_insert()
, wl_list_for_each_safe()
, wl_list_remove()
) can be used to edit linked lists.
Overall, the FLTK library interacts with Wayland calling C functions present in the libwayland-client.so
, libwayland-cursor.so
and libxkbcommon.so
shared libraries, being called by these libraries via the 'listener' mechanism, and listening via Fl::add_fd()
to data sent by the compositor to the client via the socket. The names of these functions begin with wl_
. The core protocol defines also a number of opaque structures whose names begin with wl_
. FLTK defines a few structures holding Wayland-related data. The names of FLTK-defined structures don't begin with wl_
. For example, struct wld_window
(see wld_window) is used to store all Wayland-specific data associated to a mapped Fl_Window.
Classes Fl_Wayland_Window_Driver
, Fl_Wayland_Screen_Driver
, Fl_Wayland_Graphics_Driver
, Fl_Wayland_Copy_Surface_Driver
, Fl_Wayland_Image_Surface_Driver
and Fl_Wayland_Gl_Window_Driver
contain all the Wayland-specific code of the FLTK library. This code is located at src/drivers/Wayland/
in the FLTK source tree. Furthermore, class Fl_Unix_System_Driver
is used by both the Wayland and the X11 FLTK platforms, so that a specially important element of the FLTK library, the event loop, is nearly completely identical in X11 and in Wayland.
The public Wayland C API is obtained with
as necessary.
File README.Wayland.txt
details what software packages are needed on Debian-based, Fedora and FreeBSD systems for FLTK to use Wayland. Wayland protocols are packaged as XML files accompanied by a utility program, wayland-scanner
, able to generate a header file and a necessary glue C source file from a given XML file. For example, for FLTK to use the xdg shell
protocol, these commands are run by CMake to generate a header file that FLTK code will include and a .c file that will be compiled into libfltk :
Similar operations are performed for FLTK to use protocols xdg decoration unstable v1
and text input unstable v3
.
The Wayland platform of FLTK is normally a two-legged hybrid able to use either Wayland or X11 and to choose between these possibilities at run-time, without any change to the client application. The Wayland/X11 hybrid is essentially a version of the FLTK library containing both all Wayland-specific and all X11-specific code, plus a dedicated source file, fl_wayland_platform_init.cxx
, that determines, at startup time, whether the app will run as a Wayland or as an X11 client. Function attempt_wayland()
therein performs this choice as follows :
fl_disable_wayland
and this variable is true, the X11 leg is chosen;The first condition listed above is meant to facilitate transition to FLTK 1.4 of source code written for FLTK 1.3 and containing X11-specific code : it's enough to put
FL_EXPORT bool fl_disable_wayland = true;
anywhere in the source code, for the app to run with 1.4, using the x11 leg of the hybrid platform, without any other change in the source code nor to the application's environment.
In special situations, such as with embedded systems equipped with the Wayland software but lacking the X11 library, it's possible to build the FLTK library such as it contains only the Wayland backend. This is achieved building FLTK with cmake -DOPTION_WAYLAND_ONLY=on
or with configure –disable-x11
.
The rest of this chapter describes what happens when the Wayland leg has been chosen.
Function Fl_Wayland_Screen_Driver::open_display_platform()
establishes the Wayland connection calling wl_display_connect(NULL)
which returns a struct wl_display
object. Function wl_display_get_fd()
returns the file descriptor of the Wayland socket. Then, function wl_registry_add_listener()
associates a callback function, registry_handle_global()
, that will be called a number of times to indicate each time a feature of the running Wayland compositor. This allows a client app to become aware of what protocols (and at which version) are supported by the compositor. This also allows the client app to discover system features such as displays and keyboards. Finally, Fl::add_fd() makes FLTK listen to the Wayland socket and associates function fd_callback()
from file Fl_Wayland_Screen_Driver.cxx
with it. This function essentially calls wl_display_dispatch()
which asks the Wayland client library to process requests arrived in the socket.
The event loop is run by function Fl_Unix_System_Driver::wait()
which is used by both the Wayland and X11 FLTK backends. Among various tasks, this function waits for data arriving on the file descriptors FLTK is listening. Overall, the event loop of the Wayland backend is nearly exactly the same as that used by the X11 backend. The Wayland backend differs only in the callback function called to handle data read from the Wayland connection socket, which is Wayland-specific.
Wayland defines objects called surfaces of type struct wl_surface
. A Wayland surface has a rectangular area which may be displayed on zero or more displays, present buffers, receive user input, and define a local coordinate system. Buffers allow the client app to draw to surfaces. FLTK makes no use of local coordinate systems. FLTK creates a surface with function wl_compositor_create_surface()
each time an Fl_Window is show()'n. Function wl_surface_add_listener()
associates the surface with a listener which allows to associate each surface with the display where it is mapped. FLTK recognizes 4 distinct kinds of surfaces named DECORATED, SUBWINDOW, UNFRAMED and POPUP. DECORATED are toplevel windows with a titlebar. UNFRAMED have no titlebar. POPUP correspond to menus and tooltips, SUBWINDOW to an Fl_Window embedded in another Fl_Window. Function Fl_Wayland_Window_Driver::makeWindow()
creates all these surfaces. Except for SUBWINDOW's, each surface is associated to a 'configure' function that Wayland calls one or more times when the window is going to be mapped on the display. The 'configure' function of DECORATED surfaces is handle_configure()
. Wayland calls it twice when mapping a DECORATED surface. It calls it also during operations such as resizing, minimizing. With the help of a few calls to libdecor functions, FLTK obtains in this function all needed information about the size of the mapped window. The 'configure' functions of UNFRAMED and POPUP surfaces are xdg_surface_configure()
and xdg_toplevel_configure()
. They transmit effective window size information to FLTK. Caution: there are some small differences between how and when the various Wayland compositors call these 'configure' functions.
Wayland uses an Fl_Wayland_Graphics_Driver
object for all its on-screen drawing operations. This object is created by function Fl_Graphics_Driver::newMainGraphicsDriver()
called by Fl_Display_Device::display_device()
when the library opens the display. New Fl_Wayland_Graphics_Driver
objects are also created for each Fl_Image_Surface
and each Fl_Copy_Surface
used, and deleted when these objects are deleted.
Class Fl_Wayland_Graphics_Driver
derives from class Fl_Cairo_Graphics_Driver
which implements all the FLTK drawing API for a Cairo surface. Function Fl_Wayland_Graphics_Driver::cairo_init()
creates the Cairo surface used by each Fl_Wayland_Graphics_Driver
object by calling cairo_image_surface_create_for_data()
for the window's or offscreen's draw_buffer
(see below).
Class Fl_Cairo_Graphics_Driver
is also used by the X11 leg of the hybrid Wayland-X11 platform because this leg draws to the display with an Fl_Display_Cairo_Graphics_Driver
object which derives from class Fl_Cairo_Graphics_Driver
. Finally, Fl_Cairo_Graphics_Driver
is also used, in the form of an object from its derived class Fl_PostScript_Graphics_Driver
, when the hybrid Wayland-X11 platform draws PostScript, or when the classic X11 platform uses Pango and draws PostScript. This happens when classes Fl_PostScript_File_Device
and Fl_Printer
are used.
Wayland uses buffers, objects of type struct wl_buffer
, to draw to surfaces. In principle, one or more buffers can be associated to a surface, and functions wl_surface_attach()
and wl_surface_commit()
are called to first attach one such buffer to the surface and then inform the compositor to map this buffer on the display. Wayland buffers can use various memory layouts. FLTK uses WL_SHM_FORMAT_ARGB8888, which is the same layout as what Cairo calls CAIRO_FORMAT_ARGB32.
FLTK calls function Fl_Wayland_Window_Driver::make_current()
before drawing to any Fl_Window. Member buffer
of this Fl_Window's struct wld_window
(see wld_window) is NULL when the window has just been created or resized. In that case, FLTK calls member functions create_shm_buffer()
and cairo_init()
of Fl_Wayland_Graphics_Driver
to create
A Wayland buffer is a section of a larger memory structure shared between the client app and the compositor. The shared memory structure is initially sized at 10 MB and increased by steps of 10 MB when necessary. FLTK uses a function of the libdecor library, os_create_anonymous_file()
, to create an adequate file and mmap's this file.
FLTK associates to each surface a struct fl_wld_buffer
(see fl_wld_buffer) containing a pointer to the byte array of the Cairo image surface (member draw_buffer
), a pointer to the Wayland buffer (member wl_buffer
), and other information. A pointer to this struct fl_wld_buffer
is memorized as member buffer
of the Fl_Window's wld_window. All drawing operations to the Fl_Window then modify the content of the Cairo image surface. Function Fl_Wayland_Window_Driver::flush()
is in charge of sending FLTK graphics data to the display. That is done by calling function Fl_Wayland_Graphics_Driver::buffer_commit()
which copies the byte array of the Cairo surface to the Wayland buffer's starting memory address, and calls functions wl_surface_attach()
and wl_surface_commit()
. Before calling Fl_Window::flush(), FLTK has computed a damaged region. Fl_Wayland_Window_Driver::flush()
also calls function wl_surface_damage_buffer()
with that information to inform the compositor of what parts of the surface need its attention.
An important detail here is that FLTK uses Wayland's synchronization mechanism to make sure the surface's wl_buffer
is not changed until the surface is fully mapped on the display. This 3-step mechanism first calls function wl_surface_frame()
to obtain a struct wl_callback
object and stores it as member cb
of the surface's fl_wld_buffer. Then it calls wl_callback_add_listener()
to associate this object to the FLTK-defined, callback function surface_frame_done()
that Wayland calls at the end of the mapping operation. Finally surface_frame_done()
destroys the wl_callback
object by function wl_callback_destroy()
and sets member cb
to NULL. This procedure ensures that FLTK never changes the surface's Wayland buffer while it's being used by the compositor because Fl_Wayland_Window_Driver::flush()
checks that cb
is NULL before calling Fl_Wayland_Graphics_Driver::buffer_commit()
. If it's not NULL, FLTK calls function wl_callback_destroy()
which instructs the compositor to abort the mapping operation and to get ready for processing of a new byte buffer.
FLTK supports progressive drawing when an app calls function Fl_Window::make_current() at any time and then calls the FLTK drawing API. This is made possible in function Fl_Wayland_Window_Driver::make_current()
with
Thus, buffer_commit()
runs only when cb
is NULL. If an app rapidly performs calls to Fl_Window::make_current() and to drawing functions, FLTK will copy draw_buffer
to the Wayland buffer and instruct Wayland to map it to the display when cb
is NULL which means that the compositor is ready to start performing a mapping operation, and will only modify draw_buffer
when cb
is not NULL, letting the compositor complete its ongoing mapping task. For example, FLTK's mandelbrot test app can be seen to progressively fill its window from top to bottom by blocks of lines, each block appearing when the compositor is ready to map a new buffer. When the compositor is not ready, the app does not block but continues computing and drawing in memory but not on display more lines of the desired Mandelbrot graph.
Wayland uses the concept of seat of type struct wl_seat
which encompasses displays, a keyboard, a mouse, and a trackball. It might be possible for an app to deal with several seats, but that has not been tested with FLTK yet. Each seat may contain one or more displays, which Wayland calls outputs, of type struct wl_output
.
As written above, function registry_handle_global()
discovers at start-up time available seats and outputs and initializes data for each. This function also associates a 'listener' to each display by calling function wl_output_add_listener()
. This 'listener' is an array of callback function pointers among which one (output_mode
) runs when the display is resized and another (output_scale
) when the Wayland scale factor (see below) is changed. FLTK defines type struct output
(see output) inside class Fl_Wayland_Screen_Driver
which contains display size and scaling information. One such record is created for each display. FLTK uses 2 distinct scaling parameters under Wayland:
int wld_scale;
. This member variable of struct output
typically equals 1 for standard, and 2 for HighDPI displays. Its value is set by the Wayland compositor for each display with the effect that 1 Wayland graphics unit represents a block of nxn
pixels when the value is n
. Another effect is that a drawing buffer for a surface of size WxH units contains W * n * H * n * 4
bytes. This is enough to make FLTK apps HighDPI-aware because the Wayland compositor automatically initializes parameter wld_scale
to the value adequate for each display's DPI. Under the gnome desktop, this parameter is visible in the "Settings" app, "Displays" section, "Scale" parameter which is 200% on HighDPI displays.float gui_scale;
. This other member variable is where FLTK's own GUI scaling mechanism with ctrl/+/-/0/ keystrokes and with environment variable FLTK_SCALING_FACTOR operates: when FLTK is scaled at 150%, gui_scale
is assigned value 1.5. Function Fl_Wayland_Screen_Driver::scale(int n, float f)
assigns value f
to the gui_scale
member variable of display # n
. This variable is used by function Fl_Wayland_Window_Driver::make_current()
when it calls Fl_Wayland_Graphics_Driver::set_buffer()
that scales the graphics driver by this factor with cairo_scale()
.Overall, an FLTK object, say an Fl_Window, of size WxH
FLTK units occupies W * wld_scale * gui_scale x H * wld_scale * gui_scale
pixels on the display.
TBD
FLTK receives information about mouse and pointer events via a 'listener' made up of 5 pointers to functions which Wayland calls when these events occur:
These functions receive from Wayland enough information in their parameters to generate corresponding FLTK events, that is, calls to Fl::handle(int event_type, Fl_Window *)
.
pointer_listener
is installed by a call to function wl_pointer_add_listener()
made by function seat_capabilities()
which is itself another 'listener' made up of 2 function pointers
installed by a call to function wl_seat_add_listener()
made by function registry_handle_global()
when it receives a "wl_seat"
interface.
Wayland defines types struct wl_cursor
and struct wl_cursor_theme
to hold cursor-related data. FLTK stores in member variable default_cursor
of the seat record, a pointer to the currently used cursor. Function Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor)
calls wl_cursor_theme_get_cursor()
to set the current cursor shape to one of the standard shapes from the Fl_Cursor
enumeration. This Wayland function selects a cursor shape based on the current 'cursor theme' and a cursor name. Cursor names are the files of directory /usr/share/icons/XXXX/cursors/
where XXXX
is the name of the 'cursor theme'. For example, what FLTK calls FL_CURSOR_INSERT
corresponds to file xterm
therein. The full correspondance between Fl_Cursor
values and Wayland cursor names is found in function Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor)
.
FLTK uses function init_cursors()
from file Fl_Wayland_Screen_Driver.cxx
to identify the app's 'cursor theme' using function libdecor_get_cursor_settings()
of library libdecor
, and to store it in member variable cursor_theme
of the seat record. Function init_cursors()
is itself called by a 'listener' installed when function registry_handle_global()
receives a "wl_seat"
interface, at program startup. It is also called when the value of the Wayland scaling factor changes.
Function Fl_Wayland_Window_Driver::set_cursor(const Fl_RGB_Image *rgb, int hotx, int hoty)
is used to create a custom cursor shape. This operation is relatively complex, specially because it uses a non-public structure, struct cursor_image
, defined in file wayland-cursor.c
of the Wayland project source code.
TBD
TBD
struct wld_window
is defined in Fl_Wayland_Window_Driver.H
. One such record is created for each shown()'n Fl_Window by Fl_Wayland_Window_Driver::makeWindow()
. Function fl_wl_xid(Fl_Window*)
returns a pointer to the struct wld_window
of its argument. struct wld_window { struct wl_list outputs; // linked list of outputs where this surface is mapped struct wl_surface *wl_surface; // the window's surface struct fl_wld_buffer *buffer; // see fl_wld_buffer struct xdg_surface *xdg_surface; union { struct libdecor_frame *frame; // for DECORATED windows struct wl_subsurface *subsurface; // for SUBWINDOW windows struct xdg_popup *xdg_popup; // for POPUP windows struct xdg_toplevel *xdg_toplevel; // for UNFRAMED windows }; Fl_Window *fl_win; enum Fl_Wayland_Window_Driver::kind kind; // DECORATED or POPUP or SUBWINDOW or UNFRAMED int configured_width; // used when negotiating window size with the compositor int configured_height; int floating_width; // helps restoring size after un-maximizing int floating_height; int scale; // the Wayland scale factor for HighDPI displays (1 or 2, possibly 3) int state; // indicates whether window is fullscreen, maximized. Used otherwise for POPUPs }
struct fl_wld_buffer
is defined in Fl_Wayland_Graphics_Driver.H
. One such record is created for each shown()'n or resized Fl_Window by Fl_Wayland_Graphics_Driver::create_shm_buffer()
. struct fl_wld_buffer { struct wl_buffer *wl_buffer; // the Wayland buffer void *data; // address of the beginning of the Wayland buffer's byte array size_t data_size; // of wl_buffer and draw_buffer int stride; // bytes per line int width; unsigned char *draw_buffer; // address of the beginning of the Cairo image surface's byte array struct wl_callback *cb; // non-NULL while Wayland buffer is being committed bool draw_buffer_needs_commit; // true when draw_buffer has been modfied but not yet committed cairo_t *cairo_; // used when drawing to the Cairo image surface };
struct output
is defined inside class Fl_Wayland_Screen_Driver
. One such record is created for each display of the system by function registry_handle_global()
when it receives a "wl_output"
interface. These records are kept in a linked list of them all, and an identifier of this linked list is stored in member outputs
of the unique Fl_Wayland_Screen_Driver
object FLTK uses. Thus, struct output { // one record for each display uint32_t id; // an identifier of the display short x_org; short y_org; short width; // in pixels short height; // in pixels float dpi; struct wl_output *wl_output; int wld_scale; // Wayland scale factor float gui_scale; // FLTK scale factor struct wl_list link; // links these records together };
struct seat
is defined in file Fl_Wayland_Screen_Driver.H
. One such record is created for each seat (e.g., a collection of displays, a keyboard and a mouse) of the system by function registry_handle_global()
when it receives a "wl_seat"
or wl_data_device_manager_interface.name
interface. struct seat { struct wl_seat *wl_seat; struct wl_pointer *wl_pointer; struct wl_keyboard *wl_keyboard; uint32_t keyboard_enter_serial; struct wl_surface *keyboard_surface; struct wl_list link; struct wl_list pointer_outputs; struct wl_cursor_theme *cursor_theme; struct wl_cursor *default_cursor; struct wl_surface *cursor_surface; struct wl_surface *pointer_focus; int pointer_scale; uint32_t serial; uint32_t pointer_enter_serial; struct wl_data_device_manager *data_device_manager; struct wl_data_device *data_device; struct wl_data_source *data_source; struct xkb_state *xkb_state; struct xkb_context *xkb_context; struct xkb_keymap *xkb_keymap; struct xkb_compose_state *xkb_compose_state; char *name; struct zwp_text_input_v3 *text_input; };
The Wayland Protocol | Extensive introduction to Wayland programming written by the Wayland author, unfortunately unachieved. | |
Wayland Explorer | Documentation of all Wayland protocols, both stable and unstable. A language-independent syntax is used which makes function names usable in the C language not always obvious. Also some useful functions seem undocumented here for an unclear reason. | |
Wayland Protocol Specification | Documentation for all functions of the Wayland core protocol. | |
Wayland clipboard and drag & drop | Detailed explanation of how clipboard and drag-and-drop work under Wayland. | |
Wayland and input methods | Blog article introducing to the issue of text input methods under Wayland. | |
Input Method Hub | Entry page for input method support giving newcomers a first understanding of what input methods are and how they are implemented in Wayland. |