This patch introduces a new protocol proxy, which multiplxes Wayland
clients into a single connection, allowing us to redirect calls (e.g.
create toplevel -> create subsurface). Mixed with some state tracking
and emulation, we can embed a full-featured client into the editor.
Previously, on Linux and BSD, inhibiting the screensaver was handled
using the org.freedesktop.ScreenSaver D-Bus API. Unfortunately, this API
is not available in a Flatpak sandbox. (This is because there is a
desire to tie inhibit sessions to a specific app and visible window; but
the org.freedesktop.ScreenSaver API does not support this.)
As a result, when using the Flatpak build of the Godot Editor (or a
Flatpak-ed build of a game) and using a controller to play a game, the
session will become idle after a few minutes.
The XDG desktop portal -- which is already used for color-picking, file
choosing, and querying the system theme -- has an Inhibit interface that
provides a superset of the functionality of the
org.freedesktop.ScreenSaver API, and is available to any sandboxed app.
Refactor code for making XDG portal requests that was previously
duplicated for the FileChooser and ColorPicker portal code. Check the
portal version to determine whether these portals can be used:
- FileChooser portal version 3 is required due to the use of the
"directory" parameter.
- On the Settings portal, the only addition in version 2 is the
ReadOne() method which is not used here, so version 1 suffices.
- On the Screenshot portal, the only addition in version 2 is the
"interactive" parameter to the Screenshot() method; this code only
uses the PickColor() method, so version 1 suffices.
Then, add support for the Inhibit portal. Use it if available and if
running in a sandbox. Prefer to use org.freedesktop.ScreenSaver if not
running in a sandbox, even if the portal is available, because (at least
in the GNOME 43 implementation of the portal) it does not work correctly
if the portal cannot map the request to a running app. This adds a small
amount of complexity to the implementation, but supporting both APIs is
necessary anyway (there are many systems in the wild that support
org.freedesktop.ScreenSaver but not the desktop portal).
Fixes https://github.com/godotengine/godot/issues/108634
Fixes a timing issue where dialogs got configured right before having
their parent set. This gave compositors the time to resize/rearrange the
dialog as if it were a normal window, only to be marked as "floating"
right away. On niri, this manifested as huge dialog windows.
This is achieved with the addition of a `p_parent_id` parameter to
`window_create`, akin to its popup counterpart.
`window_create` now also accepts a single `Size2i` parameter instead of
two integers, in line with the rest of the `WaylandThread` API. The
original idea was to have a very "barebones" API, akin to the `drivers/`
directory, but that didn't pan out.
Without this, the screen does go into idle after a few minutes on a RPi5 with default install (wayland w/ labwc), even
though `screen_keep_on` is set. DBUS is enabled but apparently, the screensaver call is not enough.
Before, the WSI was unfortunately quite broken and we had work around it
by manually pacing frames. Needless to say it was not an ideal solution.
Now, the WSI can make use of the new fifo_v1 protocol to work properly.
If it's available, we'll trust the WSI by disabling manual frame pacing.
While we're at it, let's clean up the suspension code a bit by removing
some duplicated stuff and handling the suspension state through a switch
case.
The backend is now mature enough to not explode with multiple windows
but the `DisplayServer` API still cannot meet some guarantees required
by the various Wayland protocols we use. To meet those guarantees this
patch adds three new elements to the DisplayServer API, with relative
handling logic for `Window` and `Popup` nodes:
- `WINDOW_EVENT_FORCE_CLOSE`, which tells a window to *forcefully*
close itself and ensure a proper cleanup of its references, as Wayland
enforces this behavior;
- `WINDOW_FLAG_POPUP_WM_HINT`, which explicitly declares a window as a
"popup", as Wayland enforces this distinction and heuristics are not
reliable enough;
- `FEATURE_SELF_FITTING_WINDOWS`, which signals that the compositor can
fit windows to the screen automatically and that nodes should not do
that themselves.
Given the size of this feature, this patch also includes various
`WaylandThread` reworks and fixes including:
- Improvements to frame wait logic, with fixes to various stalls and a
configurable (through a `#define`) timeout amount;
- A proper implementation of `window_can_draw`;
- Complete overhaul of pointer and tablet handling. Now everything is
always accumulated and handled only on each respective `frame` event.
This makes their logic simpler and more robust.
- Better handling of pointer leaving and pointer enter/exit event
sending;
- Keyboard focus tracking;
- More solid window references using IDs instead of raw pointers as
windows can be deleted at any time;
- More aggressive messaging to window nodes to enforce rects imposed by
the compositor.
There were two edge cases in the frame waiting logic (aka manual frame
throttling or emulated vsync) which would cause the editor to stall in
one way or another:
1. Waiting right after starting the editor would cause a deadlock
between both threads until something happened in the Wayland event
queue, in turn unblocking the Wayland thread and kickstartin the whole
thing;
2. Starting the editor (and probably other long-loading stuff) without
low consumption mode would suspend the window and never commit its
surfaces, thus never signaling the compositor that we want frame events.
Images don't have RIDs and this way of checking stuff broke the cursor
cache. Let's do like all other platforms and check the resource
reference instead.
Before, we would check both methods together, leading to loops.
Now we track the actual reason we suspended and only unsuspend when
that same reason triggers. For example, if we suspend because of the
suspended flag we'll unsuspend only because it got unset. Conversely, if
we suspend because of a timeout we'll unsuspend only if we get a new
frame event.
We do this because, while some compositors properly report a "suspended"
state (hinting us to stop repainting), most don't and we need a "safety
net" anyways as we do not want to constantly stay at 1fps (the max time
we'll wait before giving up) either.