FLTK 1.4.0
|
This chapter describes how the Wayland backend of FLTK works from a developer's viewpoint.
Wayland usage involves communication via a Unix domain 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 resizable 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 uses 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. Each protocol adds functionality not available in the core protocol. Wayland Explorer lists all protocols. The core protocol allows a client app to discover what protocols the connected 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. The names of symbols used by unstable protocols always begin with letter 'z'. For example, FLTK uses unstable protocol Text input to support CJK input methods; its symbol names begin with zwp_text_input_v3
.
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 (more at Listeners below).
Wayland differs noticeably from X11 in that rendering is left to clients: Wayland provides no drawing API. Instead, Wayland provides objects of type struct wl_buffer
which encapsulate a memory array of pixel values shared between the client and the compositor. The client app is expected to draw to that memory buffer with whatever means it chooses, and to instruct the compositor to map those pixels to the display when the drawing is complete. The Wayland platform of FLTK draws with the Cairo library to Fl_Window's
and Fl_Image_Surface's
, and with OpenGL to Fl_Gl_Window's
.
Wayland differs also from X11 in that the position of a window in the display is completely hidden to the client app. This prevents function Fl_Window::position()
from having any effect on a top-level window. Wayland also prevents a client app from knowing whether a window is minimized: Fl_Window::show()
has no effect on a minimized window. Subwindows can be positioned as usual relatively to their parent window. Wayland allows to create popup windows positioned relatively to a previously mapped other window. This allows FLTK to position adequately menu and tooltip windows (see Menu windows and other popups). FLTK uses also popups for the small, yellow windows that display the new scale factor value when it's changed: these are created as short-lived popups centered above Fl::first_window()
.
Wayland uses a trick of its own to handle lists of linked records. It defines type struct wl_list
and a few macros (wl_list_init()
, wl_list_for_each()
, wl_list_insert()
, wl_list_for_each_safe()
, wl_list_remove()
) to manage linked lists. Records put in these lists must contain a member variable of type struct wl_list
used to link records together and often named 'link'. Access to such a list is possible memorizing a value of type struct wl_list
computed by macro wl_list_init()
. Macro wl_list_for_each(arg1, arg2, arg3)
allows to run through all list elements with:
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 targeted 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 Fl_Wayland_Screen_Driver::output):
Overall, and ignoring for now OpenGL usage, FLTK interacts with Wayland as follows :
Fl::add_fd()
in FL_READ
mode to associate a callback function to the socket connecting the client and the compositor.libwayland-client.so
, libwayland-cursor.so
and libxkbcommon.so
shared libraries and of the libdecor library. These send suitable messages to the compositor writing to the socket. The names of these functions begin with wl_
, xkb_
or libdecor_
.wl_display_dispatch()
which interprets the read data and calls corresponding listeners.The core protocol defines also a number of mostly opaque structures whose names begin with wl_
. The names of symbols and types defined by the other protocols FLTK uses begin with xdg_
, zwp_text_input_v3
, zxdg_toplevel_decoration_
, gtk_shell1_
and gtk_surface1_
. 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
and file fl_wayland_platform_init.cxx
contain all the Wayland-specific code of the FLTK library. This code is located at src/drivers/Wayland/
in the FLTK source tree. A single C++ source file generally contains all the code of a given class. The code related to copy, paste and drag-and-drop operations, however, is gathered in file fl_wayland_clipboard_dnd.cxx
and contains a few member functions of class Fl_Wayland_Screen_Driver
. Furthermore, class Fl_Unix_System_Driver
is used by both the Wayland and the X11 FLTK platforms. File FL/fl_config.h defines preprocessor variables FLTK_USE_WAYLAND
and FLTK_USE_CAIRO
.
The public C API to Wayland, xkb, EGL and libdecor libraries are 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 at build time to generate a .c file that will be compiled into libfltk and a header file that FLTK code will include:
Similar operations are performed for FLTK to use protocols XDG decoration, Text input and GTK Shell.
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. That's reflected in file FL/fl_config.h which defines both FLTK_USE_WAYLAND
and FLTK_USE_X11
. This creates the constraint that Wayland and X11 cannot use the same type name for different purposes or the same symbol name. That is why function fl_xid(const Fl_Window*)
is deprecated in FLTK 1.4 and replaced by fl_wl_xid()
for Wayland and fl_x11_xid()
for X11. Also, global variable Window fl_window
is not used by the Wayland platform which instead uses static struct wld_window *Fl_Wayland_Window_Driver:: wld_window
. The FLTK library contains also a short 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
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 -DFLTK_BACKEND_X11=OFF
or with configure –disable-x11
. In that case, FL/fl_config.h does not define FLTK_USE_X11
.
The rest of this chapter describes what happens when the Wayland leg has been chosen.
A Wayland '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 associated to its corresponding Wayland object, usually right after the object's creation, by a call to a specific Wayland function named following the form wl_XXX_add_listener()
. For example, this code:
creates a Wayland object of type struct wl_surface
, and associates it with a 2-member listener called surface_listener
. After this, Wayland is expected to call the 2 listener members, surface_enter
or surface_leave
, each time my_wl_surface
will enter or leave, respectively, a display. The arguments of these calls, not detailed here, allow the member functions to identify which surface enters or leaves which display. The wl_surface_add_listener()
call above also associates pter_to_data
to my_wl_surface
as user data. The wl_surface
object's "user data" can be obtained later calling function wl_surface_get_user_data()
.
Wayland function wl_proxy_get_listener()
returns a pointer to a Wayland object's listener provided that object is transmitted cast to type struct wl_proxy *
. This gives a handy way to distinguish FLTK-created Wayland objects from objects of other origin: the listener of an FLTK-created object is a known FLTK listener. For example, function Fl_Wayland_Window_Driver::surface_to_window()
uses this possibility calling wl_proxy_get_listener( (struct wl_proxy *)wl_surface )
for any object of type struct wl_surface
: if that object was created as in the example above, this call returns a pointer to FLTK's surface_listener
static variable.
Establishing a Wayland connection requires environment variable XDG_RUNTIME_DIR
to be defined and to point to a directory containing a socket connected to a Wayland compositor. This variable is usually set by the login procedure of Wayland-friendly desktops. The name of the Wayland socket is determined as follows:
Fl::display(const char *display_name)
before fl_open_display()
runs or use the -display
command line argument and transmit there the socket name;WAYLAND_DISPLAY
can be defined to the socket name;"wayland-0"
is used.What socket is selected determines what compositor will be used by the client application.
Function Fl_Wayland_Screen_Driver::open_display_platform()
establishes the connection to the Wayland socket identified above calling wl_display_connect(NULL)
which returns a struct wl_display
pointer or NULL in case of failure. Such NULL return is the hint that allows the FLTK display opening procedure of the Wayland/X11 hybrid to recognize when Wayland access is not possible and to fallback to X11.
Then, function wl_registry_add_listener()
associates a 2-member listener, whose 1st member, registry_handle_global()
, will be called by Wayland a number of times to indicate each time a protocol supported by the compositor or a system feature such as displays and keyboards. This code allows to run the client until all calls to registry_handle_global()
have occurred:
A pointer to an object of type struct wl_callback
created by function wl_display_sync()
is assigned to variable registry_cb
. Then a 1-member listener is attached to this object. Wayland will run this listener's member function, sync_done()
, after all calls to registry_handle_global()
have occurred. Function sync_done()
sets to null variable registry_cb
and destroys the wl_callback
. Finally, function wl_display_dispatch()
is called as long as variable registry_cb
is not null. This makes Wayland process all its pending requests until sync_done()
runs.
The prototype of function registry_handle_global
is:
Each time Wayland calls registry_handle_global()
, interface
and version
give the name and version of a component or feature of the Wayland system. It's necessary to call each time function wl_registry_bind()
which returns a pointer to a Wayland structure that will be the client's access point to the corresponding Wayland protocol or system feature. This pointer is stored in a dedicated member variable of the unique Fl_Wayland_Screen_Driver
object of an FLTK app, or of another object accessible from this object. For example, when interface
equals "wl_compositor", the value returned by wl_registry_bind()
is stored as member wl_compositor
of the Fl_Wayland_Screen_Driver
object. registry_handle_global()
also identifies whether the Mutter, Weston, or KWin compositor is connected and stores this information in static member variable Fl_Wayland_Screen_Driver::compositor
.
Wayland calls registry_handle_global()
with its parameter interface
equals to "wl_output" once for each screen connected to the system. Each time, an object of type struct wl_output
is created, to which a 4-member listener is associated by function wl_output_add_listener()
. The 3rd member of this 4-function listener, output_done()
, runs after all initialization steps of the screen have completed and turns to true
member done
of a record of type struct
Fl_Wayland_Screen_Driver::output associated to the screen. Function sync_done()
mentioned above therefore also calls wl_display_dispatch()
until the done
member of all Fl_Wayland_Screen_Driver::output
records are true
. Overall, after return from function sync_done()
, FLTK has been made aware of all optional protocols and features of its connected Wayland compositor, and has initialized all screens of the system.
Finally, function wl_display_get_fd()
is called to obtain the file descriptor of the Wayland socket and a call to Fl::add_fd() makes FLTK listen to this descriptor in FL_READ
mode and associates function wayland_socket_callback()
from file Fl_Wayland_Screen_Driver.cxx
with it. This function calls wl_display_dispatch()
which reads and interprets data available from the file descriptor, and calls corresponding listeners. The wl_display_dispatch()
call is repeated as long as data are available for reading.
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 handling data read from the Wayland connection socket, and in overridden functions Fl_Wayland_Screen_Driver::poll_or_select_with_delay()
and Fl_Wayland_Screen_Driver::poll_or_select()
.
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". In other words, surface is the name Wayland uses for a window. Buffers allow the client app to draw to surfaces (see Wayland buffers). FLTK creates a surface each time an Fl_Window is show()'n calling function wl_compositor_create_surface()
. Static member function Fl_Wayland_Window_Driver::surface_to_window(struct wl_surface *)
gives the Fl_Window*
corresponding to the surface given in argument. FLTK recognizes 4 distinct kinds of surfaces named DECORATED, UNFRAMED, POPUP and SUBWINDOW. 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, creates for each a record of type struct wld_window
(see wld_window), and stores the window kind in member variable kind
of this record. Member variable xid
of the window's Fl_X
record stores the adress of this record.
Except for SUBWINDOW's, each surface needs a Wayland object of type struct xdg_surface
used to make it become a mapped window and stored in member xdg_surface
of the window's wld_window record. For DECORATED windows, this object is created inside libdecor and transmitted to FLTK by function libdecor_frame_get_xdg_surface()
. For UNFRAMED and POPUP windows, it's created by function xdg_wm_base_get_xdg_surface()
. Finally, each surface is also associated to one more Wayland object whose type varies with the window's kind. These explain this part of the wld_window record:
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()
which is the 1st member of a 4-member listener named libdecor_frame_iface
associated to a decorated window when it's created calling libdecor_decorate()
. Finally, a call to libdecor_frame_map()
triggers the process of mapping the newly created DECORATED surface on a display. Wayland calls handle_configure()
twice during this process. The first handle_configure()
run allows to set the window's xdg_surface
object which is returned by function libdecor_frame_get_xdg_surface()
. FLTK distinguishes the first from the second run of handle_configure()
by looking at the xdg_surface
member variable that's NULL at the beginning of the 1st run and not NULL later. Wayland calls handle_configure()
also during operations such as resizing, minimizing (see below). With the help of a few calls to libdecor functions, FLTK obtains in this function all needed information about the size and state of the mapped window. The 'configure' functions of UNFRAMED and POPUP surfaces are xdg_surface_configure()
, xdg_toplevel_configure()
and popup_configure()
. The mapping process of these surfaces is triggered by a call to wl_surface_commit()
. These 'configure' functions transmit effective window size information to FLTK. Also, they are where the window's Fl_Window_Driver::wait_for_expose_value
member variable is set to 0 to indicate that the window has been mapped to display. Caution: there are some small differences between how and when the various Wayland compositors call handle_configure()
.
When a decorated window changes size, whatever the cause of it, Wayland calls handle_configure()
which sets member variable Fl_Wayland_Window_Driver::in_handle_configure
to true and calls the window's virtual resize()
function which ultimately runs Fl_Wayland_Window_Driver::resize()
which calls Fl_Group::resize() to perform FLTK's resize operations and Fl_Wayland_Graphics_Driver::buffer_release()
to delete the existing window buffer that's not adequate for the new window size. At the end of the run of handle_configure()
, in_handle_configure
is set back to false. When the window size change is caused by the app itself calling the window's resize()
function, Fl_Wayland_Window_Driver::in_handle_configure
is false. This allows Fl_Wayland_Window_Driver::resize()
to detect that Wayland needs be informed of the desired size change, which gets done by a call to libdecor_frame_commit()
. Wayland later calls handle_configure()
and events described above unfold.
Wayland generally does not provide a way to control where the compositor should map a window in the system displays. Nevertheless, for multi-display systems, Wayland allows to control on what display should the compositor map a fullscreen window. That is done inside function handle_configure()
which calls libdecor_frame_set_fullscreen()
for DECORATED windows and inside function xdg_toplevel_configure()
which calls xdg_toplevel_set_fullscreen()
for UNFRAMED. The struct wl_output
pointer for the targeted display is transmitted as 2nd argument of these calls.
Menu windows, tiny menu title windows, and tooltip windows are implemented using Wayland's popup mechanism which allows to position a popup window relatively to a previously mapped window, itself a popup or another kind of window, with the restriction that any popup must overlap or at least touch that other window. Member function Fl_Wayland_Window_Driver::makeWindow
calls member function Fl_Wayland_Window_Driver::process_menu_or_tooltip
to create all popups.
This function gets called after FLTK has computed using a given algorithm the desired (x,y) position of the popup window's top-left corner, using coordinates centered on the top-left corner of the toplevel window from which the popup originates. This algorithm is able to prevent popups from being positioned beyond the screen borders under the assumption that the position of a toplevel window inside a screen is known. While this assumption holds for other platforms, it does not for the Wayland platform. The FLTK code for the Wayland platform therefore modifies the algorithm that FLTK uses to compute the position of menu windows. The key information used by this algorithm is obtained by member function
Fl_Window_Driver::menu_window_area
which computes the coordinates of the rectangle where menu windows are allowed to be positioned. Under other platforms, this function just returns the origin and size of the work area of the screen in use. In contrast, the Wayland platform handles two situations differently :
Fl_Wayland_Window_Driver::menu_window_area
returns large negative origin and large width and height values. This lets the standard FLTK algorithm position the menu relatively to its window of origin without concern about screen limits, and relies on Wayland's constraint mechanism described below to prevent the menu from going beyond these limits, without FLTK having to know where they are.Fl_Wayland_Window_Driver::process_menu_or_tooltip
.Function Fl_Wayland_Window_Driver::process_menu_or_tooltip
first computes origin_win
, pointer to the Fl_Window
relatively to which the popup is to be positioned. Window origin_win
is the parent menu window when the popup is a sub-menu; it's the tiny windowtitle when the popup is a menu with a title; otherwise, it's the window containing the point of origin of the popup. An object of type struct xdg_positioner
created by function xdg_wm_base_create_positioner()
is used to express the rules that will determine the popup position relatively to origin_win
as follows:
xdg_positioner_set_anchor_rect()
determines a rectangle in origin_win
relatively to which the popup is to be positioned. When the popup to be created is a menu window spawned by an Fl_Menu_Bar, that rectangle is the full area of the menu title window. Otherwise, that rectangle is an adequately located point.xdg_positioner_set_size()
sets the popup size.xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT);
and xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
calls position the popup so that its top-left corner is initially below and at right of the bottom-left corner of the origin_win
's anchor rectangle.xdg_positioner_set_offset()
further changes the popup vertical position.xdg_positioner_set_constraint_adjustment()
uses constraint flags XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X
and XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y
which mean that the compositor will move the popup horizontally and vertically if its initial position would make it expand beyond the edges of the screen. Furthermore, flag XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y is added when the popup is a menu window spawned by an Fl_Menu_Bar; this has the popup flipped above the Fl_Menu_Bar if there's not enough screen room below it for the popup.xdg_surface_get_popup()
creates the popup accounting for position rules listed above. The positioner is then deleted by xdg_positioner_destroy()
, a listener is associated to the popup surface with xdg_popup_add_listener()
, and a call to wl_surface_commit()
triggers the mapping of the popup on the display.Overall, the expected coordinates of the top-left corner of the popup relatively to origin_win
are popup_x, popup_y
. They are memorized in a record of FLTK-defined type struct win_positioner
that's associated to the popup listener. When the compositor maps the popup, function popup_configure
, the first element of the popup listener, runs and receives as arguments the coordinates of the popup top left and its size. These values account for the positioning constraints of the popup which may have moved it to avoid screen borders. This function can therefore detect whether constraints applied have modified the effective popup location in comparison to the expected coordinates which are available as member variables of the struct win_positioner
record mentioned above. That's key to the handling by FLTK of tall menu windows.
Groups of popups containing a menutitle, the associated menuwindow, and optionally a submenu window and that don't belong to an Fl_Menu_Bar are mapped in a different order: the menuwindow is mapped first, and the menutitle is mapped second above it as a child popup. Function Fl_Window_Driver::is_floating_title()
detects when such a menutitle is created, static member variable previous_floatingtitle
is assigned the value of this menutitle, and the menutitle is mapped only after the menuwindow has been mapped, as a child of it. This positions better the popup group in the display relatively to where the popup was created.
The Wayland platform of FLTK 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_X11_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's graphics content 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 Fl_Wayland_Graphics_Driver::create_wld_buffer()
which returns a pointer to a struct wld_buffer
containing
wl_buffer
;Fl_Wayland_Graphics_Driver::cairo_init()
.Each of these two objects encapsulates a byte array of the same size and the same memory layout destined to contain the Fl_Window's graphics. The Cairo image surface object is where FLTK draws. The Wayland buffer is what Wayland maps on the display. FLTK copies the Cairo surface's byte array to the Wayland buffer's byte array before beginning the mapping operation. If width
and height
are a window's dimensions in pixels,
give size
, the common size of both byte arrays.
The effective creation of the wl_buffer
object is delayed until function Fl_Wayland_Graphics_Driver::buffer_commit()
gets called. Section Buffer factories below details how FLTK creates wl_buffer
objects.
The struct Fl_Wayland_Graphics_Driver::wld_buffer
(see wld_buffer) contains a pointer to the byte array of the Cairo image surface (member draw_buffer.buffer
), information about the Wayland buffer (members wl_buffer
and data
), the common size of the Cairo surface's and Wayland buffer's byte arrays (member draw_buffer.data_size
), and other information. A pointer to this struct Fl_Wayland_Graphics_Driver::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 creates the struct wl_buffer
object calling create_shm_buffer()
if that was not done before, 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. If that region is not null, Fl_Wayland_Graphics_Driver::buffer_commit()
copies only the damaged parts of the Cairo surface to the Wayland buffer and calls function wl_surface_damage_buffer()
for these parts 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 while the compositor is using it and to refrain from calling wl_surface_commit()
more frequently than the system can process it. This 2-step mechanism works as follows:
wl_surface_frame()
to obtain a pointer to a struct wl_callback
object and stores it as member frame_cb
of the surface's wld_window. Then it calls wl_callback_add_listener()
to associate this object to the FLTK-defined, callback function surface_frame_done()
. It next calls wl_surface_commit()
. Together, these 3 calls instruct Wayland to start mapping the buffer content to the display and to call surface_frame_done()
later, when it will have become ready for another mapping operation.surface_frame_done()
runs and destroys the wl_callback
object by function wl_callback_destroy()
and sets member frame_cb
to NULL.Member variable draw_buffer_needs_commit
of the wld_buffer is also important in this mechanism : it informs FLTK that the graphics buffer has changed and needs being committed. This variable is turned true
every time a graphics operation changes the buffer content and turned false
when the buffer gets committed.
This procedure ensures that FLTK never changes the surface's Wayland buffer while it's being used by the compositor and never calls wl_surface_commit()
before Wayland gets ready for a new commit because Fl_Wayland_Window_Driver::flush()
calls Fl_Wayland_Graphics_Driver::buffer_commit()
only if frame_cb
is NULL. If it's not NULL, the exact content of function surface_frame_done()
:
has the effect that when the mapping operation eventually completes, Wayland runs surface_frame_done()
, which calls anew Fl_Wayland_Graphics_Driver::buffer_commit()
if the buffer's draw_buffer_needs_commit
member is true. The net result is that the screen shows the most recent surface content.
This synchronization mechanism is also used when performing an interactive window resize operation. During such operation, the compositor informs the client an interactive resize is being performed and sends window resize commands at high rate (~60 Hz) to the client via the socket. Libdecor turns on flag LIBDECOR_WINDOW_STATE_RESIZING
to inform the client, and runs function handle_configure()
for each received resize command. Before calling Fl_Group::resize() and later Fl_Window::draw(), handle_configure()
tests whether window->frame_cb
is NULL. When it's not because a previous resize operation is being performed, the current resize command is skipped. At the end of the interactive resize, flag LIBDECOR_WINDOW_STATE_RESIZING
is off and Wayland sends a final resize command which is not skipped. Overall, this ensures the client program resizes its window as frequently as it can without falling behind resize commands sent by the compositor.
To account for a bug in Mutter (issue #878), the window->frame_cb
object is not created when a toplevel window is being resized and is entirely covered by one subwindow.
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 frame_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 frame_cb
is NULL which means that the compositor is ready to start performing a mapping operation. This occurs when the progressive drawing operation begins. Later, frame_cb
is generally found non NULL when Fl_Wayland_Window_Driver::make_current()
runs because the compositor is busy processing the previous Wayland buffer. When the compositor has completed this processing, the client app runs surface_frame_done()
which, provided member variable draw_buffer_needs_commit
is true, calls Fl_Wayland_Graphics_Driver::buffer_commit()
. This makes the compositor map the Wayland buffer in its new, more advanced, state.
An example of progressive drawing is given by FLTK's mandelbrot test app. When set to fullscreen, this 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.
Each wld_buffer record contains boolean member in_use
which is set to true
just before the buffer gets committed, and boolean member released
which is set to true
when FLTK no longer needs the buffer and calls Fl_Wayland_Graphics_Driver::buffer_release()
. FLTK's buffer-creating function, Fl_Wayland_Graphics_Driver::create_shm_buffer()
, attaches a 1-member listener to each buffer which Wayland calls after a commit operation to indicate the client is allowed to re-use the buffer. This listener's member function, buffer_release_listener()
, turns to false member in_use
of the buffer's wld_buffer record. Since the two events 'FLTK no longer needs the buffer' and 'the client is allowed to re-use the buffer' can arrive in any order, FLTK deletes the struct wl_buffer
object by running do_buffer_release()
only after both events happened, that is, when in_use
is false
and released
is true
. That's why function do_buffer_release()
is called by both functions Fl_Wayland_Graphics_Driver::buffer_release()
and buffer_release_listener()
.
Wayland names buffer factory a software procedure that constructs objects of type struct wl_buffer
for use by a client application. FLTK creates a wl_buffer
object each time an Fl_Window is mapped on a display or resized. That's done by member function Fl_Wayland_Graphics_Driver::create_shm_buffer()
which follows this 3-step procedure to create a "buffer factory" for FLTK and to construct Wayland buffers from it:
os_create_anonymous_file(off_t size)
creates an adequate file and mmap's it. This file lives in RAM because it is created by function memfd_create()
. FLTK sets this file size to 10 MB unless the size of the buffer to be created is larger; in that case the anonymous file is sized to twice the buffer size.wl_shm_create_pool()
shares this mmap'ed memory with the Wayland compositor and returns an object of type struct wl_shm_pool
which encapsulates this memory. A record of type struct Fl_Wayland_Graphics_Driver::wld_shm_pool_data
is created and associated to the newly created wl_shm_pool
by wl_shm_pool_set_user_data()
. This record stores the starting address (pool_memory
) and size (pool_size
) of the pool's encapsulated memory. The record also contains member buffers
of type struct wl_list
which stores the access point to the linked list of wl_buffer
objects that will be created from the wl_shm_pool
.chunk_offset
represents the offset within the pool's shared memory available for the buffer being constructed. It equals 0 when the pool has just been created and is updated as detailed below when one or more buffers have been previously created from the pool. A record of type struct Fl_Wayland_Graphics_Driver::wld_buffer
is created. This record will contain (member wl_buffer
) the address of a wl_buffer
object that's created by function wl_shm_pool_create_buffer()
. This wl_buffer
object encapsulates a section of a given size of the pool's shared memory beginning at offset chunk_offset
in it. Quantity pool_memory + chunk_offset
is therefore the address of the beginning of the mmap'ed memory section encapsulated by this wl_buffer
. Member shm_pool
of the newly constructed Fl_Wayland_Graphics_Driver::wld_buffer
object is set to the address of the current wl_shm_pool
object. This record is added to the head of the linked list of current pool's buffers by a call to wl_list_insert()
. At that point, a struct Fl_Wayland_Graphics_Driver::wld_buffer
record is part of the linked list of all such records corresponding to wl_buffer
objects created from the same wl_shm_pool
object, and member shm_pool
of this record gives the address of this wl_shm_pool
. When a new struct Fl_Wayland_Graphics_Driver::wld_buffer
record is to be created, wl_buffer
. Macro wl_container_of()
gives the address of a record belonging to a linked list of records of the same type.A window's wl_buffer
is re-filled by graphics data and committed each time the window gets redrawn, and is set to be destroyed by function Fl_Wayland_Graphics_Driver::buffer_release()
when Fl_Window::hide()
runs or the window is resized. When the wl_buffer
is no longer in use, function do_buffer_release()
gets called as explained above. It destroys the wl_buffer
with wl_buffer_destroy()
, and removes the corresponding Fl_Wayland_Graphics_Driver::wld_buffer
record from the linked list of buffers from the same wl_shm_pool
. Since new Fl_Wayland_Graphics_Driver::wld_buffer
records are added at the head of the linked list, and since the record at the head of this list is used to compute the offset within the pool's mmap'ed memory available for a new wl_buffer
, destruction of the last created wl_buffer
allows to re-use the destroyed buffer's pool's memory for a new wl_buffer
.
When function do_buffer_release()
finds the list of buffers from a given pool empty, two situations can occur. 1) This pool is the current pool. Its mmap'ed memory will be re-used from offset 0 to create future wl_buffer
objects. 2) This pool is not current. It gets destroyed with wl_shm_pool_destroy()
, the pool's mmap'ed memory is munmap'ed, and the pool's associated struct wld_shm_pool_data
is freed. In situation 1) above, the next wl_buffer
to be created can need more memory than the current pool's memory size. If so, the current pool gets destroyed and replaced by a new, larger pool.
If the sum of chunk_offset
plus the buffer size is larger than the current pool's size when function create_shm_buffer()
is called, chunk_offset
is reset to 0, and a new wl_shm_pool
object is created and used by FLTK's "buffer factory". This mechanism allows to access new mmap'ed memory when chunk_offset
reaches the end of the previous mmap'ed section.
Wayland uses also wl_buffer
objects to support cursors. FLTK uses the "buffer factory" described here when creating custom cursors (see custom-cursor) with function Fl_Wayland_Window_Driver::set_cursor(const Fl_RGB_Image *,…)
which calls create_shm_buffer()
via set_cursor_4args()
, custom_offscreen()
and create_wld_buffer()
. In contrast, standard shaped-cursors (e.g., FL_CURSOR_INSERT) use their own "buffer factory" inside Wayland functions such as wl_cursor_theme_get_cursor()
. Therefore, the fact that the wl_buffer
objects behind standard cursors are never destroyed doesn't prevent disused struct wl_shm_pool
objects from being freed because those buffers come from a distinct "buffer factory". The "buffer factory" described here is also used by function offscreen_from_text()
when displaying dragged text in a DnD operation.
Wayland uses the concept of seat of type struct wl_seat
which encompasses displays, a keyboard, a mouse, and a trackpad. Although Wayland may be in principle able to deal with several seats, FLTK's Wayland platform is conceived for one seat only. That seat may contain one or more displays, which Wayland calls outputs, of type struct wl_output
.
As written above, function registry_handle_global()
discovers the available seat at start-up time. This function also associates a listener to each display connected to the system by calling function wl_output_add_listener()
. This listener's member functions run at program startup when Wayland discovers its displays (see Opening a Wayland connection). Member output_mode
runs also when the display is resized and member output_scale
also when the Wayland scale factor (see below) is changed. FLTK defines type struct
Fl_Wayland_Screen_Driver::output to store display size and scaling information. One such record is created for each display. These records are put in a struct wl_list
accessible from member outputs
of the single Fl_Wayland_Screen_Driver
object.
FLTK uses 2 distinct scaling parameters for each display:
int wld_scale;
. This member variable of the struct
Fl_Wayland_Screen_Driver::output record typically equals 1 for standard, and 2 for HighDPI displays. The effect of value n
of variable wld_scale
is that 1 Wayland graphics unit represents a block of nxn
pixels. Another effect is that a drawing buffer for a surface of size WxH units contains W * n * H * n * 4
bytes. Member function output_scale()
mentionned above sets this value for each system's display at startup time. Member function Fl_Wayland_Graphics_Driver::buffer_commit()
informs the Wayland compositor of the value of wld_scale
calling wl_surface_set_buffer_scale()
which is enough to make FLTK apps HighDPI-aware. Under the gnome and KDE desktops, 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 int(W * gui_scale) * wld_scale x int(H * gui_scale) * wld_scale
pixels on the display.
When an Fl_Window
is to be show()'n, Fl_Wayland_Window_Driver::makeWindow()
creates a struct wl_surface
with wl_compositor_create_surface()
and associates it calling wl_surface_add_listener()
with a 2-member listener called surface_listener
encharged of managing as follows the list of displays where this wl_surface
will map. The Fl_Window
possesses an initially empty linked list of displays accessible at member outputs
of the window's wld_window record. When the Fl_Window
, or more exactly its associated struct wl_surface
is mapped on a display, member surface_enter()
of surface_listener
runs. This function adds the display where the surface belongs to the end of the linked list of displays for this surface. When a surface is dragged or enlarged across the edge of a display in a multi-display system and expands on a second display, surface_enter()
runs again, and this surface's list of displays contains 2 items. When a surface leaves a display, member surface_leave()
of surface_listener
runs. It removes that display from the surface's list of displays. Each time the first item of a surface's list of displays changes, function change_scale()
is called and applies that display's gui_scale
value to that surface calling Fl_Window_Driver::screen_num(int)
. When a window is unmapped by function Fl_Wayland_Window_Driver::hide()
, the surface's list of displays is emptied.
The KWin compositor, and gnome too if specially set, allow to use fractional scaling that can take intermediate values between 100% and 200%. Wayland implements this rendering all wl_surface
's as if the scaling was at 200%, and downsizing them to the desired fractional scale value at the compositing stage. Seen from FLTK, everything runs as when wld_scale = 2
.
These commands make gnome accept fractional scaling, and turn that off:
FLTK receives information about mouse and pointer events via a 'listener' made up of 5 pointers to functions which Wayland calls when events listed in table below 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 *)
.
listener function | called by Wayland when | resulting FLTK events |
---|---|---|
pointer_enter | pointer enters a window | FL_ENTER |
pointer_leave | pointer leaves a window | FL_LEAVE |
pointer_motion | pointer moves inside a window | FL_MOVE |
pointer_button | state of mouse buttons changes | FL_PUSH, FL_RELEASE |
pointer_axis | trackpad is moved vertically or horizontally | FL_MOUSEWHEEL |
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.
The gnome desktop, via its gnome-tweaks
application, allows to determine what happens when a middle mouse button click occurs on a window titlebar. To obey this setting, FLTK implements part of the GTK Shell protocol as follows. Mutter, gnome's Wayland compositor, declares its support of the GTK Shell
protocol calling registry_handle_global()
with its interface
argument equal to "gtk_shell1"
. FLTK initializes then a static global variable gtk_shell
of type struct gtk_shell1*
.
Member functions of pointer_listener
mentionned above run for all mouse events on all wl_surface
objects. The table above describes what these functions do for mouse events on FLTK-created wl_surface
objects. But they also run for the libdecor-created wl_surface
objects corresponding to window titlebars. Thus, member function pointer_enter()
runs when the mouse enters a titlebar. It calls Fl_Wayland_Screen_Driver::event_coords_from_surface()
which calls Fl_Wayland_Window_Driver::surface_to_window()
which, as mentionned above, can distinguish FLTK-created from non FLTK-created wl_surface
objects. This allows pointer_enter()
to identify the entered surface as a titlebar and to assign static global variable gtk_shell_surface
with the titlebar's wl_surface
when the mouse enters a titlebar. Similarly, member function pointer_leave()
sets gtk_shell_surface
to NULL when the mouse leaves this titlebar. When there's a click on a titlebar, member function pointer_button()
runs this code
which ensures that what gnome-tweaks
has assigned to middle-click events is executed. At this point, FLTK obeys what libdecor
decides for right-click (display the window menu) and double-click (maximize the window) events on titlebars which may diverge from gnome-tweaks
settings.
Wayland defines types struct wl_cursor
and struct wl_cursor_theme
to hold cursor-related data. FLTK uses function init_cursors()
from file Fl_Wayland_Screen_Driver.cxx
to obtain the 'cursor theme' name using function libdecor_get_cursor_settings()
of library libdecor
. Function wl_cursor_theme_load()
then returns a pointer to an object of type struct wl_cursor_theme
stored in member variable cursor_theme
of the Fl_Wayland_Screen_Driver::seat record. Function init_cursors()
is itself called by a 'listener' called seat_capabilities()
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: output_done()
calls try_update_cursor()
calls init_cursors()
. Function output_done()
belongs to a 'listener' installed when function registry_handle_global()
receives a "wl_output"
interface.
Each time Fl_Window::cursor(Fl_Cursor)
runs, FLTK calls Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor)
which 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 wl_cursor_theme
object and a cursor name and returns a pointer to a struct wl_cursor
. Under the gnome desktop, cursor names are the files of directory /usr/share/icons/XXXX/cursors/
where XXXX
is the 'gnome cursor theme' (default= Adwaita). For example, what FLTK calls FL_CURSOR_INSERT
corresponds to file xterm
therein. The full correspondance between Fl_Cursor
values and names of files therein is found in function Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor)
. FLTK stores in member variable default_cursor
of the Fl_Wayland_Screen_Driver::seat record a pointer to the currently used wl_cursor
object, and the current Fl_Cursor
value in member standard_cursor_
of the Fl_Wayland_Window_Driver
object.
Finally, function do_set_cursor() of file Fl_Wayland_Screen_Driver.cxx
makes the system pointer use the current wl_cursor
object to draw its shape on screen. That's done with a call to wl_pointer_set_cursor()
and a few other functions.
To support custom cursors, FLTK presently uses a non-public type, struct cursor_image
, defined in file Fl_Wayland_Window_Driver.cxx
as follows:
This definition has been copied to the FLTK source code from file wayland-cursor.c of the Wayland project source code because it's not accessible via Wayland header files. It shows that a pointer to a cursor_image
object can also be viewed as a pointer to the embedded struct wl_cursor_image
object, this one being part of the public Wayland API. It also shows that a struct cursor_image
object has an associated struct wl_buffer
object used to contain the cursor's graphics.
Function Fl_Wayland_Window_Driver::set_cursor(const Fl_RGB_Image *rgb, int hotx, int hoty)
gives FLTK support of custom cursor shapes. It calls Fl_Wayland_Window_Driver::set_cursor_4args()
that creates a cursor_image
object, allocates the corresponding wl_buffer
by a call to Fl_Wayland_Graphics_Driver::create_shm_buffer()
via custom_offscreen()
and create_wld_buffer()
and draws the cursor shape into that buffer using the offscreen-drawing method of FLTK.
The public type struct wl_cursor
is essentially an array of wl_cursor_image
objects and a name:
Function Fl_Wayland_Window_Driver::set_cursor_4args()
also creates a struct wl_cursor
object containing a single wl_cursor_image
, which is in fact the cursor_image
. Finally, a struct custom_cursor_
(see wld_window) is allocated and used to memorize the struct wl_cursor
and the cursor's image and hotspot. A pointer to this struct custom_cursor_
object is stored in member custom_cursor
of the window's wld_window.
Function Fl_Wayland_Window_Driver::set_cursor_4args()
is also called when a window with a custom cursor is moved between distinct displays or when a display is rescaled to adapt the cursor size to the new display's scale factor.
Member function Fl_Wayland_Window_Driver::delete_cursor_()
is used to delete any custom cursor shape. This occurs when a window associated to a custom cursor is un-mapped and when such a window gets associated to a standard cursor or to a new custom cursor.
The "Mouse handling" section above mentionned function seat_capabilities()
that Wayland calls when the app discovers its "seat". Presence of flag WL_SEAT_CAPABILITY_KEYBOARD
in argument capabilities
of this function indicates that a keyboard is available. In that case, a call to wl_seat_get_keyboard()
returns a pointer stored in member wl_keyboard
of the Fl_Wayland_Screen_Driver::seat object, and a call to wl_keyboard_add_listener()
installs a 6-member listener of type struct wl_keyboard_listener
. These 6 FLTK-defined, callback functions are used as follows.
Function wl_keyboard_keymap()
runs when the app starts and also if the keyboard layout is changed during run-time. It allows initialization of access to this keyboard. Noticeably, member xkb_state
of type struct xkb_state*
of the current Fl_Wayland_Screen_Driver::seat record is adequately initialized.
Functions wl_keyboard_enter()
and wl_keyboard_leave()
, called when focus enters and leaves a surface, send FL_FOCUS
and FL_UNFOCUS
events to the Fl_Window
object corresponding to this surface.
Function wl_keyboard_key()
runs each time a keyboard key is pressed or released. Its argument key
, to which 8 must be added, provides the keycode via function xkb_state_key_get_one_sym()
and then the corresponding text via function xkb_state_key_get_utf8()
which is put in Fl::e_text
. Then, a few calls to functions whose name begin with xkb_compose_
are necessary to support dead and compose keys. Finally a call to Fl::handle()
sends an FL_KEYDOWN
or FL_KEYUP
event to the appropriate Fl_Window
. Also, function wl_keyboard_key()
uses global variable Fl_Int_Vector key_vector
to record all currently pressed keys. This is the base of the implementation of Fl_Wayland_Screen_Driver::event_key(int)
.
Function wl_keyboard_modifiers()
runs when a modifier key (e.g., shift, control) is pressed or released. Calls to functions xkb_state_update_mask()
and xkb_state_mod_name_is_active()
allow FLTK to set Fl::e_state
adequately.
Function wl_keyboard_repeat_info()
does not run, for now, because this would require version 4 of the wl_keyboard
object which is at version 2 in all tested Wayland compositors.
When the connected Wayland compositor supports text input methods, function registry_handle_global()
gets called with its interface
argument equal to zwp_text_input_manager_v3_interface.name
. The following call to wl_registry_bind()
returns a pointer to type struct zwp_text_input_manager_v3
that is stored as member text_input_base
of the Fl_Wayland_Screen_Driver
object.
Later, when function seat_capabilities()
runs, text_input_base
is found not NULL, which triggers a call to function zwp_text_input_manager_v3_get_text_input()
returning a value of type struct zwp_text_input_v3 *
and stored as member text_input
of the Fl_Wayland_Screen_Driver::seat object. Next, a call to zwp_text_input_v3_add_listener()
associates this text_input
with a 6-member listener of type struct zwp_text_input_v3_listener
. These 6 FLTK-defined, callback functions are used as follows.
Functions text_input_enter()
and text_input_leave()
are called when text input enters or leaves a surface (which corresponds to an Fl_Window
).
Functions text_input_preedit_string()
and text_input_commit_string()
are called when the text input method asks the client app to insert 'marked' text or regular text, respectively. Complex text input often begins by inserting temporary text which is said to be 'marked' before replacing it with the text that will stay in the document. FLTK underlines marked text to distinguish it from regular text.
Functions text_input_delete_surrounding_text()
and text_input_done()
have no effect at present, without this preventing input methods that have been tested with FLTK from working satisfactorily.
It's necessary to inform text input methods of the current location of the insertion point in the active surface. This information allows them to map their auxiliary windows next to the insertion point, where they are expected to appear. The flow of information on this topic is as follows:
fl_set_spot()
.Fl_Screen_Driver::set_spot()
. Under Wayland, this just calls Fl_Wayland_Screen_Driver::insertion_point_location(int x, int y, int height)
which calls zwp_text_input_v3_set_cursor_rectangle()
to inform the text input method about the surface position and size of the insertion point and also memorizes this information in static member variables of class Fl_Wayland_Screen_Driver
.text_input_enter()
calls Fl_Wayland_Screen_Driver::insertion_point_location(int *x, int *y, int *height)
which gives it the stored position information, and then calls zwp_text_input_v3_set_cursor_rectangle()
to inform the text input method about the position of the insertion point.FLTK uses a library called libdecor to determine whether the Wayland compositor uses CSD or SSD mode, and also to draw window titlebars when in CSD mode (see libdecor:). Libdecor
is conceived to be present in a shared library linked to the Wayland client application which itself, and if the running Wayland compositor uses CSD mode, loads another shared library intended to draw titlebars in a way that best matches the Desktop. As of late 2023, libdecor is at version 0.2.0 and contains two titlebar-drawing plugins:
libdecor-gtk
intended for the Gnome desktop;libdecor-cairo
for other situations.Because libdecor
is not yet in major Linux packages, or only at version 0.1.x, FLTK bundles the most recent source code of libdecor
and its plugins. This code is included in libfltk. FLTK uses libdecor-gtk
when software package libgtk-3-dev
is present in the build system, and libdecor-cairo
otherwise.
As of late 2023, libdecor version 0.2.0 is available in very recent Linux distributions in packages libdecor-0-dev
and libdecor-0-plugin-1-gtk
. If they are installed on the build system, preprocessor variable USE_SYSTEM_LIBDECOR
is 1, and both libdecor
and its plugin are loaded at run-time from shared libraries. When these packages are not available or are at an earlier version, FLTK uses the bundled copy of libdecor
. When CMake FLTK_USE_SYSTEM_LIBDECOR
is OFF, FLTK uses the bundled libdecor
copy even if shared libraries libdecor.so
and libdecor-gtk.so
are installed. This option is ON by default.
Libdecor
uses the Wayland protocol XDG decoration to request being decorated by a supporting compositor. If the running compositor supports SSD, libdecor
doesn't draw window titlebars because the compositor does it. That is what happens with the KWin
and Sway
compositors. However, if environment variable LIBDECOR_FORCE_CSD
is defined to value 1
when an FLTK app runs, libdecor
instructs an SSD-able compositor to refrain from decorating its windows and decorates windows itself.
Whatever the value of FLTK_USE_SYSTEM_LIBDECOR
, FLTK and libdecor
use environment variable LIBDECOR_PLUGIN_DIR
as follows: if this variable is defined and points to the name of a directory, this directory is searched for a potential libdecor
plugin in the form of a shared library; if one is found, FLTK and libdecor
load it and use it.
The libdecor
source code bundled in FLTK is identical to that of the libdecor
repository. Nevertheless, FLTK uses this code with some minor changes. For example, except if USE_SYSTEM_LIBDECOR
is 1, FLTK needs to modify function libdecor_new()
charged of loading the plugin, to make it use the plugin code that is included in libfltk if none is found as a dynamic library. This is done as follows in file libdecor/build/fl_libdecor.c
:
FLTK compiles file fl_libdecor.c
which includes libdecor.c
to the effect that all of the libdecor
code becomes part of libfltk except that function libdecor_new()
is substituted by its FLTK rewrite, without file libdecor.c
being modified at all. This trick is also used to modify function libdecor_frame_set_minimized()
to bypass a bug in the Weston compositor before version 10. Similarly, FLTK compiles file fl_libdecor-plugins.c
which includes either libdecor-gtk.c
or libdecor-cairo.c
to the effect that the desired plugin becomes part of libfltk.
To support function Fl_Widget_Surface::draw_decorated_window()
that draws a mapped window and its titlebar, FLTK needs to perform two operations: 1) identify what plugin is operating, and 2) call a function that is specific of that plugin and that returns the pixels of the drawn titlebar.
FLTK performs operation 1) above using its function get_libdecor_plugin_description()
of file fl_libdecor-plugins.c
that returns a human readable string describing the running plugin. Each plugin puts its own string in member description
of a record of type struct libdecor_plugin_description
. Although this type is public in header file libdecor-plugin.h
, accessing the symbol defined by the plugin to store a pointer to a value of this type is complicated for a reason and solved by a method detailed in a comment before function get_libdecor_plugin_description()
.
Operation 2) above is done by FLTK-defined function fl_libdecor_titlebar_buffer()
from file fl_libdecor-plugins.c
. This function calls get_libdecor_plugin_description()
seen above to get the running plugin's descriptive string. That is "GTK3 plugin"
with libdecor-gtk
. FLTK function gtk_titlebar_buffer()
is then called, and returns a pointer to the start of a byte buffer containing the titlebar graphics. That is, again, not possible with the public libdecor
API. Therefore, FLTK copies to fl_libdecor-plugins.c
the definitions of several types given in libdecor-gtk.c
or libdecor-cairo.c
such as type struct border_component
.
FLTK follows the procedure that is very well described in item "Wayland clipboard and drag &
drop" of the Documentation resources. All corresponding source code is in file src/drivers/Wayland/fl_wayland_clipboard_dnd.cxx
.
This part of the Fl_Wayland_Screen_Driver::seat record stores pointers to Wayland objects used for clipboard and D-n-D operations:
FLTK can copy or paste plain UTF-8 text or image data to/from the clipboard. Images are copied to the clipboard as image/bmp
mime type. Images in image/bmp
or image/png
mime types from the clipboard can be pasted to FLTK apps.
Files dropped are received one pathname per line, with no '\n'
after the last pathname.
Wayland uses EGL™ to interface OpenGL with the underlying native platform window system. OpenGL-using FLTK apps are therefore linked to libwayland-egl.so
and libEGL.so
in addition to libGL.so
and libGLU.so
.
EGL is initialized calling member function Fl_Wayland_Gl_Window_Driver::init()
once, the first time the Fl_Wayland_Gl_Window_Driver
c'tor runs. That is done with calls to eglGetDisplay(), eglInitialize()
, and eglBindAPI()
.
Member function Fl_Wayland_Gl_Window_Driver::find()
calls eglChooseConfig()
to filter the set of GL configurations that match the Fl_Gl_Window's
mode(), and puts in the returned Fl_Gl_Choice
object the first matching configuration. The filtering gets done with bits EGL_WINDOW_BIT
, to support the creation of window surfaces, and EGL_OPENGL_BIT
, to support the creation of OpenGL contexts.
EGL needs 2 more objects created for each Fl_Gl_Window
. They have types struct wl_egl_window
and EGLSurface
, and are created by member function Fl_Wayland_Gl_Window_Driver::make_current_before()
which runs at the beginning of Fl_Gl_Window::make_current()
. The first argument of the call to wl_egl_window_create()
therein has type struct wl_surface *
and is what connects EGL with the targeted Wayland window.
EGL creates with eglCreateContext()
an object of type EGLContext
via member function Fl_Wayland_Gl_Window_Driver::create_gl_context()
called by Fl_Gl_Window::make_current()
. Types EGLContext
and GLContext
are 2 names for the same object. The call to eglCreateContext()
is made asking for a GL context of version at least 2. This does not prevent from obtaining contexts of higher versions, namely above 3.2, which are compatible with version 2 (the so-called compatibility profile) under all tested Linux systems.
FLTK function Fl_Gl_Window::make_current()
calls overridden function Fl_Wayland_Gl_Window_Driver::set_gl_context()
which calls EGL function eglMakeCurrent()
when the cached context changes.
FLTK calls function Fl_Wayland_Gl_Window_Driver::swap_buffers()
each time it wants a GL context to be sent to the display. This function contains some pure GL code to emulate an overlay buffer to support Fl_Gl_Window objects overriding their draw_overlay()
member function. Then, it calls function eglSwapBuffers()
.
The overridden Fl_Wayland_Gl_Window_Driver::resize()
function is implemented with calls to wl_egl_window_get_attached_size()
and wl_egl_window_resize()
.
Class Fl_Wayland_Gl_Plugin
exists to allow libfltk
to call functions from libfltk_gl
, libwayland-egl.so
or libEGL.so
and without having libfltk
force linking any FLTK app with these GL-related libraries. For example, Fl_Wayland_Window_Driver::flush()
needs to call Fl_Gl_Window::valid(0)
.
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 { Fl_Window *fl_win; struct wl_list outputs; // linked list of displays where part or whole of window maps struct wl_surface *wl_surface; // the window's surface struct wl_callback *frame_cb; // non-NULL until Wayland can process new surface commit struct Fl_Wayland_Graphics_Driver::wld_buffer *buffer; // see wld_buffer struct xdg_surface *xdg_surface; enum Fl_Wayland_Window_Driver::kind kind; // DECORATED or POPUP or SUBWINDOW or UNFRAMED 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 }; struct custom_cursor_ { struct wl_cursor *wl_cursor; const Fl_RGB_Image *rgb; int hotx, hoty; } *custom_cursor; // non-null when using custom cursor 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 state; // indicates whether window is fullscreen, maximized. Used otherwise for POPUPs bool covered; // specially for Mutter and issue #878 }
Defined in file Fl_Wayland_Graphics_Driver.H
. One such record is created when an Fl_Image_Surface object is created. One such record is also embedded inside each struct Fl_Wayland_Graphics_Driver::wld_buffer
record (see wld_buffer).
struct Fl_Wayland_Graphics_Driver::draw_buffer { unsigned char *buffer; // address of the beginning of the Cairo image surface's byte array cairo_t *cairo_; // used when drawing to the Cairo image surface size_t data_size; // of buffer and wl_buffer, in bytes int stride; // bytes per line int width; // in pixels };
FLTK gives offscreen buffers the platform-dependent type Fl_Offscreen
which is in fact member cairo_
of struct Fl_Wayland_Graphics_Driver::draw_buffer
. Thus, a variable with type Fl_Offscreen
needs be cast to type cairo_t*
. Static member function struct draw_buffer *offscreen_buffer(Fl_Offscreen)
of class Fl_Wayland_Graphics_Driver
returns the draw_buffer
record corresponding to an Fl_Offscreen
value.
Defined in file Fl_Wayland_Graphics_Driver.H
. One such record is created by Fl_Wayland_Graphics_Driver::create_wld_buffer()
when an Fl_Window is show()'n or resized, when a custom cursor shape is created, or when text is dragged.
struct Fl_Wayland_Graphics_Driver::wld_buffer { struct draw_buffer draw_buffer; // see draw_buffer struct wl_list link; // links all buffers from the same wl_shm_pool struct wl_buffer *wl_buffer; // the Wayland buffer void *data; // address of the beginning of the Wayland buffer's byte array struct wl_shm_pool *shm_pool; // pter to wl_shm_pool from which this wl_buffer comes bool draw_buffer_needs_commit; // true when draw_buffer has been modified but not yet committed bool in_use; // true while being committed bool released; // true after buffer_release() was called };
Defined in Fl_Wayland_Screen_Driver.H. 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,
gives access, the Wayland way, to the linked list of displays in the system.
struct Fl_Wayland_Screen_Driver::output { // one record for each display uint32_t id; // an identifier of the display int x, y; // logical position of the top-left of display int width; // nber of horizontal pixels int height; // nber of vertical pixels float dpi; // at this point, always 96. struct wl_output *wl_output; // the Wayland object for this display int wld_scale; // Wayland scale factor float gui_scale; // FLTK scale factor bool done; // true means record members have been initialized struct wl_list link; // links these records together };
It's possible to get the FLTK-defined record associated to a display from the Wayland-associated object for the same display, say struct wl_output *wl_output
, by this call: (struct Fl_Wayland_Screen_Driver::output *)wl_output_get_user_data(wl_output)
.
Defined in file Fl_Wayland_Screen_Driver.H
. One record is created by function registry_handle_global()
when it receives a "wl_seat"
or wl_data_device_manager_interface.name
interface. A pointer to this struct is stored in member seat
of the client's unique Fl_Wayland_Screen_Driver
object.
struct Fl_Wayland_Screen_Driver::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 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 book | Extensive introduction to Wayland programming written by the author of the sway compositor, unfortunately unachieved. | |
Wayland Explorer | Documentation of all Wayland protocols, both stable and unstable. A language-independent syntax is used which makes function names usable from C or C++ not always obvious. 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. |