2019-10-27 15:57:23 +01:00
|
|
|
#include "wayland.h"
|
|
|
|
|
|
2024-06-22 07:58:43 +02:00
|
|
|
#include <errno.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <locale.h>
|
|
|
|
|
#include <poll.h>
|
2019-10-27 19:08:48 +01:00
|
|
|
#include <stdlib.h>
|
2019-12-01 19:22:45 +01:00
|
|
|
#include <string.h>
|
2019-10-27 19:16:12 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
#include <sys/timerfd.h>
|
2019-10-27 19:11:35 +01:00
|
|
|
#include <sys/epoll.h>
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2023-07-31 16:33:16 +02:00
|
|
|
#include <cursor-shape-v1.h>
|
2019-10-27 15:57:23 +01:00
|
|
|
#include <wayland-client.h>
|
|
|
|
|
#include <wayland-cursor.h>
|
2024-06-22 07:58:43 +02:00
|
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
2019-10-27 15:57:23 +01:00
|
|
|
#include <xkbcommon/xkbcommon-compose.h>
|
|
|
|
|
|
2019-11-17 19:19:55 +01:00
|
|
|
#include <tllist.h>
|
2019-10-27 15:57:23 +01:00
|
|
|
|
|
|
|
|
#define LOG_MODULE "wayland"
|
2020-02-26 12:47:00 +01:00
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-10-27 15:57:23 +01:00
|
|
|
#include "log.h"
|
|
|
|
|
|
2019-12-31 16:12:48 +01:00
|
|
|
#include "config.h"
|
2019-10-27 18:43:07 +01:00
|
|
|
#include "terminal.h"
|
2020-10-10 22:14:35 +02:00
|
|
|
#include "ime.h"
|
2019-10-27 19:08:48 +01:00
|
|
|
#include "input.h"
|
|
|
|
|
#include "render.h"
|
|
|
|
|
#include "selection.h"
|
2021-07-11 09:54:04 +02:00
|
|
|
#include "shm.h"
|
2022-06-16 18:30:43 +02:00
|
|
|
#include "shm-formats.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
#include "util.h"
|
2020-08-08 20:34:30 +01:00
|
|
|
#include "xmalloc.h"
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2021-07-22 21:23:59 +02:00
|
|
|
static void
|
2023-06-22 14:39:49 +02:00
|
|
|
csd_reload_font(struct wl_window *win, float old_scale)
|
2021-07-22 21:23:59 +02:00
|
|
|
{
|
|
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
|
2023-06-22 14:39:49 +02:00
|
|
|
const float scale = term->scale;
|
2021-07-22 21:23:59 +02:00
|
|
|
|
|
|
|
|
bool enable_csd = win->csd_mode == CSD_YES && !win->is_fullscreen;
|
|
|
|
|
if (!enable_csd)
|
|
|
|
|
return;
|
|
|
|
|
if (win->csd.font != NULL && scale == old_scale)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
fcft_destroy(win->csd.font);
|
|
|
|
|
|
2021-07-22 23:21:31 +02:00
|
|
|
const char *patterns[conf->csd.font.count];
|
|
|
|
|
for (size_t i = 0; i < conf->csd.font.count; i++)
|
|
|
|
|
patterns[i] = conf->csd.font.arr[i].pattern;
|
|
|
|
|
|
2021-07-22 21:23:59 +02:00
|
|
|
char pixelsize[32];
|
2023-06-22 14:39:49 +02:00
|
|
|
snprintf(pixelsize, sizeof(pixelsize), "pixelsize=%u",
|
2023-07-25 15:56:30 +02:00
|
|
|
(int)roundf(conf->csd.title_height * scale * 1 / 2));
|
2021-07-22 21:23:59 +02:00
|
|
|
|
2023-06-22 14:39:49 +02:00
|
|
|
LOG_DBG("loading CSD font \"%s:%s\" (old-scale=%.2f, scale=%.2f)",
|
2021-07-22 23:21:31 +02:00
|
|
|
patterns[0], pixelsize, old_scale, scale);
|
2021-07-22 21:23:59 +02:00
|
|
|
|
2021-07-22 23:21:31 +02:00
|
|
|
win->csd.font = fcft_from_name(conf->csd.font.count, patterns, pixelsize);
|
2021-07-22 21:23:59 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-26 12:39:17 +01:00
|
|
|
static void
|
|
|
|
|
csd_instantiate(struct wl_window *win)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = win->term->wl;
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(wayl != NULL);
|
2020-02-26 12:39:17 +01:00
|
|
|
|
2021-02-12 11:39:25 +01:00
|
|
|
for (size_t i = 0; i < CSD_SURF_MINIMIZE; i++) {
|
2022-04-16 17:41:14 +02:00
|
|
|
bool ret = wayl_win_subsurface_new(win, &win->csd.surface[i], true);
|
2021-02-12 11:39:25 +01:00
|
|
|
xassert(ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = CSD_SURF_MINIMIZE; i < CSD_SURF_COUNT; i++) {
|
|
|
|
|
bool ret = wayl_win_subsurface_new_with_custom_parent(
|
2023-06-26 16:10:40 +02:00
|
|
|
win, win->csd.surface[CSD_SURF_TITLE].surface.surf, &win->csd.surface[i],
|
2022-04-16 17:41:14 +02:00
|
|
|
true);
|
2021-02-12 11:39:25 +01:00
|
|
|
xassert(ret);
|
|
|
|
|
}
|
2021-07-22 21:23:59 +02:00
|
|
|
|
2023-06-22 14:39:49 +02:00
|
|
|
csd_reload_font(win, -1.);
|
2020-02-26 12:39:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
csd_destroy(struct wl_window *win)
|
|
|
|
|
{
|
2021-07-11 09:59:25 +02:00
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
2021-07-22 21:23:59 +02:00
|
|
|
fcft_destroy(term->window->csd.font);
|
|
|
|
|
term->window->csd.font = NULL;
|
|
|
|
|
|
2021-07-18 16:46:43 +02:00
|
|
|
for (size_t i = 0; i < ALEN(win->csd.surface); i++)
|
2021-02-12 11:39:25 +01:00
|
|
|
wayl_win_subsurface_destroy(&win->csd.surface[i]);
|
2021-07-18 16:46:43 +02:00
|
|
|
shm_purge(term->render.chains.csd);
|
2020-02-26 12:39:17 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-16 17:47:47 +02:00
|
|
|
static void
|
|
|
|
|
seat_add_data_device(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
if (seat->wayl->data_device_manager == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (seat->data_device != NULL) {
|
|
|
|
|
/* TODO: destroy old device + clipboard data? */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wl_data_device *data_device = wl_data_device_manager_get_data_device(
|
|
|
|
|
seat->wayl->data_device_manager, seat->wl_seat);
|
|
|
|
|
|
|
|
|
|
if (data_device == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
seat->data_device = data_device;
|
|
|
|
|
wl_data_device_add_listener(data_device, &data_device_listener, seat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
seat_add_primary_selection(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
if (seat->wayl->primary_selection_device_manager == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (seat->primary_selection_device != NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct zwp_primary_selection_device_v1 *primary_selection_device
|
|
|
|
|
= zwp_primary_selection_device_manager_v1_get_device(
|
|
|
|
|
seat->wayl->primary_selection_device_manager, seat->wl_seat);
|
|
|
|
|
|
|
|
|
|
if (primary_selection_device == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
seat->primary_selection_device = primary_selection_device;
|
|
|
|
|
zwp_primary_selection_device_v1_add_listener(
|
|
|
|
|
primary_selection_device, &primary_selection_device_listener, seat);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-10 22:14:35 +02:00
|
|
|
static void
|
|
|
|
|
seat_add_text_input(struct seat *seat)
|
|
|
|
|
{
|
2020-12-03 18:36:56 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2020-10-10 22:14:35 +02:00
|
|
|
if (seat->wayl->text_input_manager == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct zwp_text_input_v3 *text_input
|
|
|
|
|
= zwp_text_input_manager_v3_get_text_input(
|
|
|
|
|
seat->wayl->text_input_manager, seat->wl_seat);
|
|
|
|
|
|
|
|
|
|
if (text_input == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
seat->wl_text_input = text_input;
|
|
|
|
|
zwp_text_input_v3_add_listener(text_input, &text_input_listener, seat);
|
2020-12-03 18:36:56 +01:00
|
|
|
#endif
|
2020-10-10 22:14:35 +02:00
|
|
|
}
|
|
|
|
|
|
2021-02-07 14:46:29 +01:00
|
|
|
static void
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
seat_add_key_bindings(struct seat *seat)
|
2021-02-07 14:46:29 +01:00
|
|
|
{
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
key_binding_new_for_seat(seat->wayl->key_binding_manager, seat);
|
2022-02-07 19:52:15 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
static void
|
|
|
|
|
seat_destroy(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
if (seat == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-12-12 19:05:24 +01:00
|
|
|
tll_free(seat->mouse.buttons);
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
key_binding_remove_seat(seat->wayl->key_binding_manager, seat);
|
2020-08-09 22:40:53 +02:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->kbd.xkb_compose_state != NULL)
|
|
|
|
|
xkb_compose_state_unref(seat->kbd.xkb_compose_state);
|
|
|
|
|
if (seat->kbd.xkb_compose_table != NULL)
|
|
|
|
|
xkb_compose_table_unref(seat->kbd.xkb_compose_table);
|
|
|
|
|
if (seat->kbd.xkb_keymap != NULL)
|
|
|
|
|
xkb_keymap_unref(seat->kbd.xkb_keymap);
|
|
|
|
|
if (seat->kbd.xkb_state != NULL)
|
|
|
|
|
xkb_state_unref(seat->kbd.xkb_state);
|
|
|
|
|
if (seat->kbd.xkb != NULL)
|
|
|
|
|
xkb_context_unref(seat->kbd.xkb);
|
|
|
|
|
|
|
|
|
|
if (seat->kbd.repeat.fd >= 0)
|
|
|
|
|
fdm_del(seat->wayl->fdm, seat->kbd.repeat.fd);
|
|
|
|
|
|
|
|
|
|
if (seat->pointer.theme != NULL)
|
|
|
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
2023-06-26 16:10:40 +02:00
|
|
|
if (seat->pointer.surface.surf != NULL)
|
|
|
|
|
wl_surface_destroy(seat->pointer.surface.surf);
|
|
|
|
|
if (seat->pointer.surface.viewport != NULL)
|
|
|
|
|
wp_viewport_destroy(seat->pointer.surface.viewport);
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->pointer.xcursor_callback != NULL)
|
|
|
|
|
wl_callback_destroy(seat->pointer.xcursor_callback);
|
|
|
|
|
|
2020-07-08 18:41:09 +02:00
|
|
|
if (seat->clipboard.data_source != NULL)
|
|
|
|
|
wl_data_source_destroy(seat->clipboard.data_source);
|
|
|
|
|
if (seat->clipboard.data_offer != NULL)
|
|
|
|
|
wl_data_offer_destroy(seat->clipboard.data_offer);
|
|
|
|
|
if (seat->primary.data_source != NULL)
|
|
|
|
|
zwp_primary_selection_source_v1_destroy(seat->primary.data_source);
|
|
|
|
|
if (seat->primary.data_offer != NULL)
|
|
|
|
|
zwp_primary_selection_offer_v1_destroy(seat->primary.data_offer);
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->primary_selection_device != NULL)
|
|
|
|
|
zwp_primary_selection_device_v1_destroy(seat->primary_selection_device);
|
|
|
|
|
if (seat->data_device != NULL)
|
2020-11-09 19:59:05 +01:00
|
|
|
wl_data_device_release(seat->data_device);
|
2023-06-27 17:25:57 +02:00
|
|
|
if (seat->pointer.shape_device != NULL)
|
|
|
|
|
wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device);
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_keyboard != NULL)
|
2020-11-09 19:59:05 +01:00
|
|
|
wl_keyboard_release(seat->wl_keyboard);
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_pointer != NULL)
|
2020-11-09 19:59:05 +01:00
|
|
|
wl_pointer_release(seat->wl_pointer);
|
2023-07-05 00:19:21 +08:00
|
|
|
if (seat->wl_touch != NULL)
|
|
|
|
|
wl_touch_release(seat->wl_touch);
|
2020-12-03 18:36:56 +01:00
|
|
|
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2020-10-10 22:14:35 +02:00
|
|
|
if (seat->wl_text_input != NULL)
|
|
|
|
|
zwp_text_input_v3_destroy(seat->wl_text_input);
|
2020-12-03 18:36:56 +01:00
|
|
|
#endif
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_seat != NULL)
|
2020-11-09 19:59:05 +01:00
|
|
|
wl_seat_release(seat->wl_seat);
|
2020-07-08 16:45:26 +02:00
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
ime_reset_pending(seat);
|
2020-07-08 16:45:26 +02:00
|
|
|
free(seat->clipboard.text);
|
|
|
|
|
free(seat->primary.text);
|
2023-06-28 13:25:08 +02:00
|
|
|
free(seat->pointer.last_custom_xcursor);
|
2020-07-08 16:45:26 +02:00
|
|
|
free(seat->name);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
static void
|
|
|
|
|
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
|
|
|
|
|
{
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
|
|
|
|
|
switch (format) {
|
|
|
|
|
case WL_SHM_FORMAT_XRGB2101010: wayl->shm_have_xrgb2101010 = true; break;
|
|
|
|
|
case WL_SHM_FORMAT_ARGB2101010: wayl->shm_have_argb2101010 = true; break;
|
|
|
|
|
case WL_SHM_FORMAT_XBGR2101010: wayl->shm_have_xbgr2101010 = true; break;
|
|
|
|
|
case WL_SHM_FORMAT_ABGR2101010: wayl->shm_have_abgr2101010 = true; break;
|
2025-05-01 09:37:47 +02:00
|
|
|
case WL_SHM_FORMAT_XBGR16161616: wayl->shm_have_xbgr161616 = true; break;
|
|
|
|
|
case WL_SHM_FORMAT_ABGR16161616: wayl->shm_have_abgr161616 = true; break;
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-16 18:30:43 +02:00
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
bool have_description = false;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ALEN(shm_formats); i++) {
|
|
|
|
|
if (shm_formats[i].format == format) {
|
|
|
|
|
LOG_DBG("shm: 0x%08x: %s", format, shm_formats[i].description);
|
|
|
|
|
have_description = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!have_description)
|
|
|
|
|
LOG_DBG("shm: 0x%08x: unknown", format);
|
|
|
|
|
#endif
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_shm_listener shm_listener = {
|
|
|
|
|
.format = &shm_format,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
|
|
|
|
|
{
|
|
|
|
|
LOG_DBG("wm base ping");
|
|
|
|
|
xdg_wm_base_pong(shell, serial);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
|
|
|
|
.ping = &xdg_wm_base_ping,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
|
|
|
|
|
enum wl_seat_capability caps)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->wl_seat == wl_seat);
|
2020-07-08 19:30:34 +02:00
|
|
|
|
2023-07-05 00:19:21 +08:00
|
|
|
LOG_DBG("%s: keyboard=%s, pointer=%s, touch=%s", seat->name,
|
2020-07-08 19:30:34 +02:00
|
|
|
(caps & WL_SEAT_CAPABILITY_KEYBOARD) ? "yes" : "no",
|
2023-07-05 00:19:21 +08:00
|
|
|
(caps & WL_SEAT_CAPABILITY_POINTER) ? "yes" : "no",
|
|
|
|
|
(caps & WL_SEAT_CAPABILITY_TOUCH) ? "yes" : "no");
|
2019-10-27 19:08:48 +01:00
|
|
|
|
|
|
|
|
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_keyboard == NULL) {
|
|
|
|
|
seat->wl_keyboard = wl_seat_get_keyboard(wl_seat);
|
|
|
|
|
wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, seat);
|
2020-03-03 18:20:53 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_keyboard != NULL) {
|
|
|
|
|
wl_keyboard_release(seat->wl_keyboard);
|
|
|
|
|
seat->wl_keyboard = NULL;
|
2020-03-03 18:20:53 +01:00
|
|
|
}
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (caps & WL_SEAT_CAPABILITY_POINTER) {
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_pointer == NULL) {
|
2023-06-26 16:10:40 +02:00
|
|
|
xassert(seat->pointer.surface.surf == NULL);
|
2023-06-26 17:31:14 +02:00
|
|
|
seat->pointer.surface.surf =
|
|
|
|
|
wl_compositor_create_surface(seat->wayl->compositor);
|
2020-07-10 19:24:33 +02:00
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
if (seat->pointer.surface.surf == NULL) {
|
2020-07-10 19:24:33 +02:00
|
|
|
LOG_ERR("%s: failed to create pointer surface", seat->name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-28 15:32:42 +02:00
|
|
|
if (seat->wayl->viewporter != NULL) {
|
|
|
|
|
xassert(seat->pointer.surface.viewport == NULL);
|
|
|
|
|
seat->pointer.surface.viewport = wp_viewporter_get_viewport(
|
|
|
|
|
seat->wayl->viewporter, seat->pointer.surface.surf);
|
|
|
|
|
|
|
|
|
|
if (seat->pointer.surface.viewport == NULL) {
|
|
|
|
|
LOG_ERR("%s: failed to create pointer viewport", seat->name);
|
|
|
|
|
wl_surface_destroy(seat->pointer.surface.surf);
|
|
|
|
|
seat->pointer.surface.surf = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-06-26 17:31:14 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->wl_pointer = wl_seat_get_pointer(wl_seat);
|
|
|
|
|
wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat);
|
2023-06-27 17:25:57 +02:00
|
|
|
|
|
|
|
|
if (seat->wayl->cursor_shape_manager != NULL) {
|
|
|
|
|
xassert(seat->pointer.shape_device == NULL);
|
|
|
|
|
seat->pointer.shape_device = wp_cursor_shape_manager_v1_get_pointer(
|
|
|
|
|
seat->wayl->cursor_shape_manager, seat->wl_pointer);
|
|
|
|
|
}
|
2020-03-03 18:20:53 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_pointer != NULL) {
|
2023-07-18 21:09:24 +08:00
|
|
|
if (seat->pointer.shape_device != NULL) {
|
|
|
|
|
wp_cursor_shape_device_v1_destroy(seat->pointer.shape_device);
|
|
|
|
|
seat->pointer.shape_device = NULL;
|
|
|
|
|
}
|
2023-06-27 17:25:57 +02:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
wl_pointer_release(seat->wl_pointer);
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_destroy(seat->pointer.surface.surf);
|
2020-07-10 19:24:33 +02:00
|
|
|
|
2023-07-28 15:32:42 +02:00
|
|
|
if (seat->pointer.surface.viewport != NULL) {
|
|
|
|
|
wp_viewport_destroy(seat->pointer.surface.viewport);
|
|
|
|
|
seat->pointer.surface.viewport = NULL;
|
|
|
|
|
}
|
2023-06-26 17:31:14 +02:00
|
|
|
|
2020-07-10 19:32:48 +02:00
|
|
|
if (seat->pointer.theme != NULL)
|
|
|
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
|
|
|
|
|
2023-08-19 23:01:53 +08:00
|
|
|
if (seat->wl_touch != NULL &&
|
|
|
|
|
seat->touch.state == TOUCH_STATE_INHIBITED)
|
|
|
|
|
{
|
|
|
|
|
seat->touch.state = TOUCH_STATE_IDLE;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
seat->wl_pointer = NULL;
|
2023-06-26 16:10:40 +02:00
|
|
|
seat->pointer.surface.surf = NULL;
|
2020-07-10 19:32:48 +02:00
|
|
|
seat->pointer.theme = NULL;
|
2020-07-10 19:24:33 +02:00
|
|
|
seat->pointer.cursor = NULL;
|
2020-03-03 18:20:53 +01:00
|
|
|
}
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
2023-07-05 00:19:21 +08:00
|
|
|
|
|
|
|
|
if (caps & WL_SEAT_CAPABILITY_TOUCH) {
|
|
|
|
|
if (seat->wl_touch == NULL) {
|
|
|
|
|
seat->wl_touch = wl_seat_get_touch(wl_seat);
|
|
|
|
|
wl_touch_add_listener(seat->wl_touch, &touch_listener, seat);
|
|
|
|
|
|
|
|
|
|
seat->touch.state = TOUCH_STATE_IDLE;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (seat->wl_touch != NULL) {
|
|
|
|
|
wl_touch_release(seat->wl_touch);
|
|
|
|
|
seat->wl_touch = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat->touch.state = TOUCH_STATE_INHIBITED;
|
|
|
|
|
}
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name)
|
|
|
|
|
{
|
2020-07-08 16:45:26 +02:00
|
|
|
struct seat *seat = data;
|
|
|
|
|
free(seat->name);
|
2020-08-08 20:34:30 +01:00
|
|
|
seat->name = xstrdup(name);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_seat_listener seat_listener = {
|
|
|
|
|
.capabilities = seat_handle_capabilities,
|
|
|
|
|
.name = seat_handle_name,
|
|
|
|
|
};
|
|
|
|
|
|
2020-02-15 19:05:33 +01:00
|
|
|
static void
|
|
|
|
|
update_term_for_output_change(struct terminal *term)
|
|
|
|
|
{
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
const float old_scale = term->scale;
|
2024-02-07 16:22:33 +01:00
|
|
|
const float logical_width = term->width / old_scale;
|
|
|
|
|
const float logical_height = term->height / old_scale;
|
2021-05-13 11:10:51 +02:00
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
/* Note: order matters! term_update_scale() must come first */
|
|
|
|
|
bool scale_updated = term_update_scale(term);
|
|
|
|
|
bool fonts_updated = term_font_dpi_changed(term, old_scale);
|
2020-04-20 18:37:59 +02:00
|
|
|
term_font_subpixel_changed(term);
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
|
2021-07-22 21:23:59 +02:00
|
|
|
csd_reload_font(term->window, old_scale);
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
|
2024-02-07 16:22:33 +01:00
|
|
|
enum resize_options resize_opts = RESIZE_KEEP_GRID;
|
2024-01-17 15:00:14 -05:00
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
if (fonts_updated) {
|
|
|
|
|
/*
|
|
|
|
|
* If the fonts have been updated, the cell dimensions have
|
2024-02-06 12:36:45 +01:00
|
|
|
* changed. This requires a "forced" resize, since the surface
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
* buffer dimensions may not have been updated (in which case
|
2024-01-17 15:00:14 -05:00
|
|
|
* render_resize() normally shortcuts and returns early).
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
*/
|
2024-01-17 15:00:14 -05:00
|
|
|
resize_opts |= RESIZE_FORCE;
|
|
|
|
|
} else if (!scale_updated) {
|
|
|
|
|
/* No need to resize if neither scale nor fonts have changed */
|
|
|
|
|
return;
|
|
|
|
|
} else if (term->conf->dpi_aware) {
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
/*
|
2024-01-17 15:00:14 -05:00
|
|
|
* If fonts are sized according to DPI, it is possible for the cell
|
|
|
|
|
* size to remain the same when display scale changes. This will not
|
|
|
|
|
* change the surface buffer dimensions, but will change the logical
|
|
|
|
|
* size of the window. To ensure that the compositor is made aware of
|
|
|
|
|
* the proper logical size, force a resize rather than allowing
|
|
|
|
|
* render_resize() to shortcut the notification if the buffer
|
|
|
|
|
* dimensions remain the same.
|
|
|
|
|
*/
|
|
|
|
|
resize_opts |= RESIZE_FORCE;
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
}
|
2024-01-17 15:00:14 -05:00
|
|
|
|
|
|
|
|
render_resize(
|
|
|
|
|
term,
|
2024-02-07 16:22:33 +01:00
|
|
|
(int)roundf(logical_width),
|
|
|
|
|
(int)roundf(logical_height),
|
2024-01-17 15:00:14 -05:00
|
|
|
resize_opts);
|
2020-02-15 19:05:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
update_terms_on_monitor(struct monitor *mon)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = mon->wayl;
|
|
|
|
|
|
|
|
|
|
tll_foreach(wayl->terms, it) {
|
|
|
|
|
struct terminal *term = it->item;
|
|
|
|
|
|
|
|
|
|
tll_foreach(term->window->on_outputs, it2) {
|
|
|
|
|
if (it2->item == mon) {
|
|
|
|
|
update_term_for_output_change(term);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-05 19:35:34 +01:00
|
|
|
static void
|
|
|
|
|
output_update_ppi(struct monitor *mon)
|
|
|
|
|
{
|
2022-09-22 18:33:24 +02:00
|
|
|
if (mon->dim.mm.width <= 0 || mon->dim.mm.height <= 0)
|
2020-03-11 16:10:55 +01:00
|
|
|
return;
|
|
|
|
|
|
2022-11-04 17:42:52 +01:00
|
|
|
double x_inches = mon->dim.mm.width * 0.03937008;
|
|
|
|
|
double y_inches = mon->dim.mm.height * 0.03937008;
|
2020-07-26 07:41:57 +02:00
|
|
|
|
2023-07-17 16:19:14 +02:00
|
|
|
const int width = mon->dim.px_real.width;
|
|
|
|
|
const int height = mon->dim.px_real.height;
|
|
|
|
|
|
2020-03-11 16:10:55 +01:00
|
|
|
mon->ppi.real.x = mon->dim.px_real.width / x_inches;
|
|
|
|
|
mon->ppi.real.y = mon->dim.px_real.height / y_inches;
|
|
|
|
|
|
2021-01-11 17:53:27 +01:00
|
|
|
/* The *logical* size is affected by the transform */
|
|
|
|
|
switch (mon->transform) {
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_90:
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_270:
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_FLIPPED_270: {
|
|
|
|
|
int swap = x_inches;
|
|
|
|
|
x_inches = y_inches;
|
|
|
|
|
y_inches = swap;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_NORMAL:
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_180:
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_FLIPPED:
|
|
|
|
|
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-17 16:19:14 +02:00
|
|
|
const int scaled_width = mon->dim.px_scaled.width;
|
|
|
|
|
const int scaled_height = mon->dim.px_scaled.height;
|
2021-12-13 19:15:17 +01:00
|
|
|
|
|
|
|
|
mon->ppi.scaled.x = scaled_width / x_inches;
|
|
|
|
|
mon->ppi.scaled.y = scaled_height / y_inches;
|
2020-07-26 07:41:57 +02:00
|
|
|
|
2023-07-17 16:19:14 +02:00
|
|
|
const double px_diag_physical = sqrt(pow(width, 2) + pow(height, 2));
|
|
|
|
|
mon->dpi.physical = width == 0 && height == 0
|
|
|
|
|
? 96.
|
|
|
|
|
: px_diag_physical / mon->inch;
|
|
|
|
|
|
|
|
|
|
const double px_diag_scaled = sqrt(pow(scaled_width, 2) + pow(scaled_height, 2));
|
|
|
|
|
mon->dpi.scaled = scaled_width == 0 && scaled_height == 0
|
|
|
|
|
? 96.
|
|
|
|
|
: px_diag_scaled / mon->inch * mon->scale;
|
|
|
|
|
|
|
|
|
|
if (mon->dpi.physical > 1000) {
|
|
|
|
|
if (mon->name != NULL) {
|
|
|
|
|
LOG_WARN("%s: DPI=%f (physical) is unreasonable, using 96 instead",
|
|
|
|
|
mon->name, mon->dpi.physical);
|
|
|
|
|
}
|
|
|
|
|
mon->dpi.physical = 96;
|
|
|
|
|
}
|
2022-11-04 17:45:43 +01:00
|
|
|
|
2023-07-17 16:19:14 +02:00
|
|
|
if (mon->dpi.scaled > 1000) {
|
2022-11-04 17:45:43 +01:00
|
|
|
if (mon->name != NULL) {
|
2023-07-17 16:19:14 +02:00
|
|
|
LOG_WARN("%s: DPI=%f (logical) is unreasonable, using 96 instead",
|
|
|
|
|
mon->name, mon->dpi.scaled);
|
2022-11-04 17:45:43 +01:00
|
|
|
}
|
2023-07-17 16:19:14 +02:00
|
|
|
mon->dpi.scaled = 96;
|
2022-11-04 17:45:43 +01:00
|
|
|
}
|
2019-12-05 19:35:34 +01:00
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
static void
|
|
|
|
|
output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y,
|
|
|
|
|
int32_t physical_width, int32_t physical_height,
|
|
|
|
|
int32_t subpixel, const char *make, const char *model,
|
|
|
|
|
int32_t transform)
|
|
|
|
|
{
|
|
|
|
|
struct monitor *mon = data;
|
2021-04-07 08:04:24 +02:00
|
|
|
|
|
|
|
|
free(mon->make);
|
|
|
|
|
free(mon->model);
|
|
|
|
|
|
2020-03-11 16:10:55 +01:00
|
|
|
mon->dim.mm.width = physical_width;
|
|
|
|
|
mon->dim.mm.height = physical_height;
|
|
|
|
|
mon->inch = sqrt(pow(mon->dim.mm.width, 2) + pow(mon->dim.mm.height, 2)) * 0.03937008;
|
2020-08-08 20:34:30 +01:00
|
|
|
mon->make = make != NULL ? xstrdup(make) : NULL;
|
|
|
|
|
mon->model = model != NULL ? xstrdup(model) : NULL;
|
2020-04-20 18:37:59 +02:00
|
|
|
mon->subpixel = subpixel;
|
2021-01-11 17:53:27 +01:00
|
|
|
mon->transform = transform;
|
2021-04-07 08:04:24 +02:00
|
|
|
|
2019-12-05 19:35:34 +01:00
|
|
|
output_update_ppi(mon);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_mode(void *data, struct wl_output *wl_output, uint32_t flags,
|
|
|
|
|
int32_t width, int32_t height, int32_t refresh)
|
|
|
|
|
{
|
|
|
|
|
if ((flags & WL_OUTPUT_MODE_CURRENT) == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct monitor *mon = data;
|
|
|
|
|
mon->refresh = (float)refresh / 1000;
|
2020-03-11 16:10:55 +01:00
|
|
|
mon->dim.px_real.width = width;
|
|
|
|
|
mon->dim.px_real.height = height;
|
2020-02-15 21:31:23 +01:00
|
|
|
output_update_ppi(mon);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_done(void *data, struct wl_output *wl_output)
|
|
|
|
|
{
|
2020-02-15 19:05:33 +01:00
|
|
|
struct monitor *mon = data;
|
|
|
|
|
update_terms_on_monitor(mon);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_scale(void *data, struct wl_output *wl_output, int32_t factor)
|
|
|
|
|
{
|
|
|
|
|
struct monitor *mon = data;
|
|
|
|
|
mon->scale = factor;
|
2020-07-26 07:41:57 +02:00
|
|
|
output_update_ppi(mon);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-13 19:17:48 +01:00
|
|
|
#if defined(WL_OUTPUT_NAME_SINCE_VERSION)
|
|
|
|
|
static void
|
|
|
|
|
output_name(void *data, struct wl_output *wl_output, const char *name)
|
|
|
|
|
{
|
|
|
|
|
struct monitor *mon = data;
|
|
|
|
|
free(mon->name);
|
|
|
|
|
mon->name = name != NULL ? xstrdup(name) : NULL;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION)
|
|
|
|
|
static void
|
|
|
|
|
output_description(void *data, struct wl_output *wl_output,
|
|
|
|
|
const char *description)
|
|
|
|
|
{
|
|
|
|
|
struct monitor *mon = data;
|
|
|
|
|
free(mon->description);
|
|
|
|
|
mon->description = description != NULL ? xstrdup(description) : NULL;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
static const struct wl_output_listener output_listener = {
|
|
|
|
|
.geometry = &output_geometry,
|
|
|
|
|
.mode = &output_mode,
|
|
|
|
|
.done = &output_done,
|
|
|
|
|
.scale = &output_scale,
|
2021-12-13 19:17:48 +01:00
|
|
|
#if defined(WL_OUTPUT_NAME_SINCE_VERSION)
|
|
|
|
|
.name = &output_name,
|
|
|
|
|
#endif
|
|
|
|
|
#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION)
|
|
|
|
|
.description = &output_description,
|
|
|
|
|
#endif
|
2019-10-27 19:08:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_output_handle_logical_position(
|
|
|
|
|
void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y)
|
|
|
|
|
{
|
|
|
|
|
struct monitor *mon = data;
|
|
|
|
|
mon->x = x;
|
|
|
|
|
mon->y = y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output,
|
|
|
|
|
int32_t width, int32_t height)
|
|
|
|
|
{
|
2020-03-11 16:10:55 +01:00
|
|
|
struct monitor *mon = data;
|
|
|
|
|
mon->dim.px_scaled.width = width;
|
|
|
|
|
mon->dim.px_scaled.height = height;
|
|
|
|
|
output_update_ppi(mon);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
|
|
|
|
|
{
|
2023-07-23 20:03:31 +02:00
|
|
|
struct monitor *mon = data;
|
|
|
|
|
update_terms_on_monitor(mon);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
|
|
|
|
|
const char *name)
|
|
|
|
|
{
|
|
|
|
|
struct monitor *mon = data;
|
2021-04-07 08:04:24 +02:00
|
|
|
free(mon->name);
|
2020-08-08 20:34:30 +01:00
|
|
|
mon->name = name != NULL ? xstrdup(name) : NULL;
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output,
|
|
|
|
|
const char *description)
|
|
|
|
|
{
|
2020-06-25 17:30:51 +02:00
|
|
|
struct monitor *mon = data;
|
2021-04-07 08:04:24 +02:00
|
|
|
free(mon->description);
|
2020-08-08 20:34:30 +01:00
|
|
|
mon->description = description != NULL ? xstrdup(description) : NULL;
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
2019-12-31 15:43:15 +01:00
|
|
|
static const struct zxdg_output_v1_listener xdg_output_listener = {
|
2019-10-27 19:08:48 +01:00
|
|
|
.logical_position = xdg_output_handle_logical_position,
|
|
|
|
|
.logical_size = xdg_output_handle_logical_size,
|
|
|
|
|
.done = xdg_output_handle_done,
|
|
|
|
|
.name = xdg_output_handle_name,
|
|
|
|
|
.description = xdg_output_handle_description,
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-31 15:39:40 +01:00
|
|
|
static void
|
|
|
|
|
clock_id(void *data, struct wp_presentation *wp_presentation, uint32_t clk_id)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
wayl->presentation_clock_id = clk_id;
|
|
|
|
|
LOG_DBG("presentation clock ID: %u", clk_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wp_presentation_listener presentation_listener = {
|
|
|
|
|
.clock_id = &clock_id,
|
|
|
|
|
};
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
static void
|
|
|
|
|
color_manager_create_image_description(struct wayland *wayl)
|
|
|
|
|
{
|
|
|
|
|
if (!wayl->color_management.have_feat_parametric)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!wayl->color_management.have_primaries_srgb)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!wayl->color_management.have_tf_ext_linear)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct wp_image_description_creator_params_v1 *params =
|
|
|
|
|
wp_color_manager_v1_create_parametric_creator(wayl->color_management.manager);
|
|
|
|
|
|
|
|
|
|
wp_image_description_creator_params_v1_set_tf_named(
|
|
|
|
|
params, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR);
|
|
|
|
|
|
|
|
|
|
wp_image_description_creator_params_v1_set_primaries_named(
|
|
|
|
|
params, WP_COLOR_MANAGER_V1_PRIMARIES_SRGB);
|
|
|
|
|
|
|
|
|
|
wayl->color_management.img_description =
|
|
|
|
|
wp_image_description_creator_params_v1_create(params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
color_manager_supported_intent(void *data,
|
|
|
|
|
struct wp_color_manager_v1 *wp_color_manager_v1,
|
|
|
|
|
uint32_t render_intent)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
if (render_intent == WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL)
|
|
|
|
|
wayl->color_management.have_intent_perceptual = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
color_manager_supported_feature(void *data,
|
|
|
|
|
struct wp_color_manager_v1 *wp_color_manager_v1,
|
|
|
|
|
uint32_t feature)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
|
|
|
|
|
if (feature == WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC)
|
|
|
|
|
wayl->color_management.have_feat_parametric = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
color_manager_supported_tf_named(void *data,
|
|
|
|
|
struct wp_color_manager_v1 *wp_color_manager_v1,
|
|
|
|
|
uint32_t tf)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
if (tf == WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR)
|
|
|
|
|
wayl->color_management.have_tf_ext_linear = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
color_manager_supported_primaries_named(void *data,
|
|
|
|
|
struct wp_color_manager_v1 *wp_color_manager_v1,
|
|
|
|
|
uint32_t primaries)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
if (primaries == WP_COLOR_MANAGER_V1_PRIMARIES_SRGB)
|
|
|
|
|
wayl->color_management.have_primaries_srgb = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
color_manager_done(void *data,
|
|
|
|
|
struct wp_color_manager_v1 *wp_color_manager_v1)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
color_manager_create_image_description(wayl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wp_color_manager_v1_listener color_manager_listener = {
|
|
|
|
|
.supported_intent = &color_manager_supported_intent,
|
|
|
|
|
.supported_feature = &color_manager_supported_feature,
|
|
|
|
|
.supported_primaries_named = &color_manager_supported_primaries_named,
|
|
|
|
|
.supported_tf_named = &color_manager_supported_tf_named,
|
|
|
|
|
.done = &color_manager_done,
|
|
|
|
|
};
|
|
|
|
|
|
2019-11-03 15:39:26 +01:00
|
|
|
static bool
|
|
|
|
|
verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
|
|
|
|
|
{
|
|
|
|
|
if (version >= wanted)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
LOG_ERR("%s: need interface version %u, but compositor only implements %u",
|
|
|
|
|
iface, wanted, version);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
static void
|
|
|
|
|
surface_enter(void *data, struct wl_surface *wl_surface,
|
|
|
|
|
struct wl_output *wl_output)
|
2019-10-27 15:57:23 +01:00
|
|
|
{
|
2020-01-03 13:40:37 +01:00
|
|
|
struct wl_window *win = data;
|
|
|
|
|
struct terminal *term = win->term;
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2020-01-03 13:40:37 +01:00
|
|
|
tll_foreach(term->wl->monitors, it) {
|
2019-10-27 19:08:48 +01:00
|
|
|
if (it->item.output == wl_output) {
|
|
|
|
|
LOG_DBG("mapped on %s", it->item.name);
|
|
|
|
|
tll_push_back(term->window->on_outputs, &it->item);
|
2020-02-15 19:05:33 +01:00
|
|
|
update_term_for_output_change(term);
|
2019-10-27 19:08:48 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR("mapped on unknown output");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
surface_leave(void *data, struct wl_surface *wl_surface,
|
|
|
|
|
struct wl_output *wl_output)
|
|
|
|
|
{
|
2020-01-03 13:40:37 +01:00
|
|
|
struct wl_window *win = data;
|
|
|
|
|
struct terminal *term = win->term;
|
2019-10-27 19:08:48 +01:00
|
|
|
|
|
|
|
|
tll_foreach(term->window->on_outputs, it) {
|
|
|
|
|
if (it->item->output != wl_output)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("unmapped from %s", it->item->name);
|
|
|
|
|
tll_remove(term->window->on_outputs, it);
|
2020-02-15 19:05:33 +01:00
|
|
|
update_term_for_output_change(term);
|
2019-10-27 19:08:48 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-06 19:25:54 +01:00
|
|
|
LOG_WARN("unmapped from unknown output");
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-09 22:51:17 -06:00
|
|
|
#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION)
|
|
|
|
|
static void
|
|
|
|
|
surface_preferred_buffer_scale(void *data, struct wl_surface *surface,
|
|
|
|
|
int32_t scale)
|
|
|
|
|
{
|
|
|
|
|
struct wl_window *win = data;
|
|
|
|
|
|
|
|
|
|
if (win->preferred_buffer_scale == scale)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("wl_surface preferred scale: %d -> %d", win->preferred_buffer_scale, scale);
|
|
|
|
|
|
|
|
|
|
win->preferred_buffer_scale = scale;
|
|
|
|
|
update_term_for_output_change(win->term);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
surface_preferred_buffer_transform(void *data, struct wl_surface *surface,
|
|
|
|
|
uint32_t transform)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
|
|
|
.enter = &surface_enter,
|
|
|
|
|
.leave = &surface_leave,
|
2024-01-09 22:51:17 -06:00
|
|
|
#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION)
|
|
|
|
|
.preferred_buffer_scale = &surface_preferred_buffer_scale,
|
|
|
|
|
.preferred_buffer_transform = &surface_preferred_buffer_transform,
|
|
|
|
|
#endif
|
2019-10-27 19:08:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
|
|
|
|
|
int32_t width, int32_t height, struct wl_array *states)
|
|
|
|
|
{
|
2020-01-03 13:54:44 +01:00
|
|
|
bool is_activated = false;
|
2020-02-26 12:39:17 +01:00
|
|
|
bool is_fullscreen = false;
|
2020-02-26 13:23:00 +01:00
|
|
|
bool is_maximized = false;
|
2021-01-10 15:40:11 +01:00
|
|
|
bool is_resizing = false;
|
2020-10-20 20:58:03 +02:00
|
|
|
bool is_tiled_top = false;
|
|
|
|
|
bool is_tiled_bottom = false;
|
|
|
|
|
bool is_tiled_left = false;
|
|
|
|
|
bool is_tiled_right = false;
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
bool is_constrained_top = false;
|
|
|
|
|
bool is_constrained_bottom = false;
|
|
|
|
|
bool is_constrained_left = false;
|
|
|
|
|
bool is_constrained_right = false;
|
2023-07-03 14:36:03 +02:00
|
|
|
bool is_suspended UNUSED = false;
|
2020-01-03 13:54:44 +01:00
|
|
|
|
2020-01-03 12:52:18 +01:00
|
|
|
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
2020-01-03 12:49:04 +01:00
|
|
|
char state_str[2048];
|
|
|
|
|
int state_chars = 0;
|
|
|
|
|
|
|
|
|
|
static const char *const strings[] = {
|
|
|
|
|
[XDG_TOPLEVEL_STATE_MAXIMIZED] = "maximized",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_FULLSCREEN] = "fullscreen",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_RESIZING] = "resizing",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_ACTIVATED] = "activated",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_TILED_LEFT] = "tiled:left",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_TILED_RIGHT] = "tiled:right",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_TILED_TOP] = "tiled:top",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_TILED_BOTTOM] = "tiled:bottom",
|
2023-07-03 14:36:03 +02:00
|
|
|
#if defined(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) /* wayland-protocols >= 1.32 */
|
|
|
|
|
[XDG_TOPLEVEL_STATE_SUSPENDED] = "suspended",
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
#endif
|
|
|
|
|
#if defined(XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION)
|
|
|
|
|
[XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT] = "constrained:left",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT] = "constrained:right",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_CONSTRAINED_TOP] = "constrained:top",
|
|
|
|
|
[XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM] = "constrained:bottom",
|
2023-07-03 14:36:03 +02:00
|
|
|
#endif
|
2020-01-03 12:49:04 +01:00
|
|
|
};
|
|
|
|
|
#endif
|
2020-01-02 17:38:50 +01:00
|
|
|
|
2020-01-02 16:06:35 +01:00
|
|
|
enum xdg_toplevel_state *state;
|
|
|
|
|
wl_array_for_each(state, states) {
|
2020-01-03 12:49:04 +01:00
|
|
|
switch (*state) {
|
2020-10-20 20:58:03 +02:00
|
|
|
case XDG_TOPLEVEL_STATE_MAXIMIZED: is_maximized = true; break;
|
2023-07-03 14:36:03 +02:00
|
|
|
case XDG_TOPLEVEL_STATE_FULLSCREEN: is_fullscreen = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_RESIZING: is_resizing = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_ACTIVATED: is_activated = true; break;
|
2020-10-20 20:58:03 +02:00
|
|
|
case XDG_TOPLEVEL_STATE_TILED_LEFT: is_tiled_left = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_RIGHT: is_tiled_right = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_TOP: is_tiled_top = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_BOTTOM: is_tiled_bottom = true; break;
|
2023-07-03 14:36:03 +02:00
|
|
|
|
|
|
|
|
#if defined(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION)
|
|
|
|
|
case XDG_TOPLEVEL_STATE_SUSPENDED: is_suspended = true; break;
|
2025-04-06 15:48:29 +09:00
|
|
|
#endif
|
|
|
|
|
#if defined(XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION)
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: is_constrained_left = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: is_constrained_right = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: is_constrained_top = true; break;
|
|
|
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: is_constrained_bottom = true; break;
|
2023-07-03 14:36:03 +02:00
|
|
|
#endif
|
2023-10-02 16:34:54 +02:00
|
|
|
}
|
2020-01-03 12:49:04 +01:00
|
|
|
|
2020-01-03 12:52:18 +01:00
|
|
|
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
2023-07-03 14:36:03 +02:00
|
|
|
if (*state >= 0 && *state < ALEN(strings)) {
|
2020-01-03 12:49:04 +01:00
|
|
|
state_chars += snprintf(
|
|
|
|
|
&state_str[state_chars], sizeof(state_str) - state_chars,
|
2023-07-03 14:36:03 +02:00
|
|
|
"%s, ",
|
|
|
|
|
strings[*state] != NULL ? strings[*state] : "<unknown>");
|
2020-01-03 12:49:04 +01:00
|
|
|
}
|
|
|
|
|
#endif
|
2020-01-02 16:06:35 +01:00
|
|
|
}
|
|
|
|
|
|
2020-01-03 12:52:18 +01:00
|
|
|
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
2020-01-03 12:49:04 +01:00
|
|
|
if (state_chars > 2)
|
|
|
|
|
state_str[state_chars - 2] = '\0';
|
2020-02-15 19:30:59 +01:00
|
|
|
else
|
|
|
|
|
state_str[0] = '\0';
|
2020-01-03 12:49:04 +01:00
|
|
|
|
2020-10-20 20:58:03 +02:00
|
|
|
LOG_DBG("xdg-toplevel: configure: size=%dx%d, states=[%s]",
|
2020-01-03 12:49:04 +01:00
|
|
|
width, height, state_str);
|
2020-01-03 12:52:18 +01:00
|
|
|
#endif
|
2020-01-03 12:49:04 +01:00
|
|
|
|
2020-01-03 13:54:44 +01:00
|
|
|
/*
|
|
|
|
|
* Changes done here are ignored until the configure event has
|
|
|
|
|
* been ack:ed in xdg_surface_configure().
|
|
|
|
|
*
|
|
|
|
|
* So, just store the config data and apply it later, in
|
|
|
|
|
* xdg_surface_configure() after we've ack:ed the event.
|
|
|
|
|
*/
|
2020-01-03 13:46:15 +01:00
|
|
|
struct wl_window *win = data;
|
2020-02-26 12:26:03 +01:00
|
|
|
|
2020-01-03 13:54:44 +01:00
|
|
|
win->configure.is_activated = is_activated;
|
2020-02-26 12:39:17 +01:00
|
|
|
win->configure.is_fullscreen = is_fullscreen;
|
2020-02-26 13:23:00 +01:00
|
|
|
win->configure.is_maximized = is_maximized;
|
2021-01-10 15:40:11 +01:00
|
|
|
win->configure.is_resizing = is_resizing;
|
2020-10-20 20:58:03 +02:00
|
|
|
win->configure.is_tiled_top = is_tiled_top;
|
|
|
|
|
win->configure.is_tiled_bottom = is_tiled_bottom;
|
|
|
|
|
win->configure.is_tiled_left = is_tiled_left;
|
|
|
|
|
win->configure.is_tiled_right = is_tiled_right;
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
win->configure.is_constrained_top = is_constrained_top;
|
|
|
|
|
win->configure.is_constrained_bottom = is_constrained_bottom;
|
|
|
|
|
win->configure.is_constrained_left = is_constrained_left;
|
|
|
|
|
win->configure.is_constrained_right = is_constrained_right;
|
2020-01-03 13:54:44 +01:00
|
|
|
win->configure.width = width;
|
|
|
|
|
win->configure.height = height;
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
|
|
|
|
|
{
|
2020-01-03 13:46:15 +01:00
|
|
|
struct wl_window *win = data;
|
|
|
|
|
struct terminal *term = win->term;
|
2019-10-27 19:08:48 +01:00
|
|
|
LOG_DBG("xdg-toplevel: close");
|
2019-10-30 20:05:34 +01:00
|
|
|
term_shutdown(term);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
2022-08-12 16:12:36 +02:00
|
|
|
#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION)
|
2022-05-06 20:03:30 +02:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_configure_bounds(void *data,
|
|
|
|
|
struct xdg_toplevel *xdg_toplevel,
|
|
|
|
|
int32_t width, int32_t height)
|
|
|
|
|
{
|
|
|
|
|
/* TODO: ensure we don't pick a bigger size */
|
|
|
|
|
}
|
2022-08-12 16:12:36 +02:00
|
|
|
#endif
|
2022-05-06 20:03:30 +02:00
|
|
|
|
2022-05-06 20:05:04 +02:00
|
|
|
#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
|
|
|
|
|
static void
|
|
|
|
|
xdg_toplevel_wm_capabilities(void *data,
|
|
|
|
|
struct xdg_toplevel *xdg_toplevel,
|
|
|
|
|
struct wl_array *caps)
|
|
|
|
|
{
|
|
|
|
|
struct wl_window *win = data;
|
|
|
|
|
|
|
|
|
|
win->wm_capabilities.maximize = false;
|
|
|
|
|
win->wm_capabilities.minimize = false;
|
|
|
|
|
|
2023-10-02 16:34:54 +02:00
|
|
|
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
|
|
|
|
char cap_str[2048];
|
|
|
|
|
int cap_chars = 0;
|
|
|
|
|
|
|
|
|
|
static const char *const strings[] = {
|
|
|
|
|
[XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU] = "window-menu",
|
|
|
|
|
[XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE] = "maximize",
|
|
|
|
|
[XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN] = "fullscreen",
|
|
|
|
|
[XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE] = "minimize",
|
|
|
|
|
};
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
enum xdg_toplevel_wm_capabilities *cap;
|
|
|
|
|
wl_array_for_each(cap, caps) {
|
|
|
|
|
switch (*cap) {
|
2022-05-06 20:05:04 +02:00
|
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE:
|
|
|
|
|
win->wm_capabilities.maximize = true;
|
|
|
|
|
break;
|
2023-10-02 16:34:54 +02:00
|
|
|
|
2022-05-06 20:05:04 +02:00
|
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE:
|
|
|
|
|
win->wm_capabilities.minimize = true;
|
|
|
|
|
break;
|
2023-10-02 16:34:54 +02:00
|
|
|
|
|
|
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU:
|
|
|
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN:
|
|
|
|
|
break;
|
2022-05-06 20:05:04 +02:00
|
|
|
}
|
2023-10-02 16:34:54 +02:00
|
|
|
|
|
|
|
|
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
|
|
|
|
if (*cap >= 0 && *cap < ALEN(strings)) {
|
|
|
|
|
cap_chars += snprintf(
|
|
|
|
|
&cap_str[cap_chars], sizeof(cap_str) - cap_chars,
|
|
|
|
|
"%s, ",
|
|
|
|
|
strings[*cap] != NULL ? strings[*cap] : "<unknown>");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2022-05-06 20:05:04 +02:00
|
|
|
}
|
2023-10-02 16:34:54 +02:00
|
|
|
|
|
|
|
|
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
|
|
|
|
if (cap_chars > 2)
|
|
|
|
|
cap_str[cap_chars - 2] = '\0';
|
|
|
|
|
else
|
|
|
|
|
cap_str[0] = '\0';
|
|
|
|
|
|
|
|
|
|
LOG_DBG("xdg-toplevel: wm-capabilities=[%s]", cap_str);
|
|
|
|
|
#endif
|
2022-05-06 20:05:04 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
|
|
|
.configure = &xdg_toplevel_configure,
|
2024-02-06 12:36:45 +01:00
|
|
|
/*.close = */&xdg_toplevel_close, /* epoll-shim defines a macro 'close'... */
|
2022-08-12 16:12:36 +02:00
|
|
|
#if defined(XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION)
|
2022-05-06 20:03:30 +02:00
|
|
|
.configure_bounds = &xdg_toplevel_configure_bounds,
|
2022-08-12 16:12:36 +02:00
|
|
|
#endif
|
2022-05-06 20:05:04 +02:00
|
|
|
#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
|
|
|
|
|
.wm_capabilities = xdg_toplevel_wm_capabilities,
|
|
|
|
|
#endif
|
2019-10-27 19:08:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_surface_configure(void *data, struct xdg_surface *xdg_surface,
|
|
|
|
|
uint32_t serial)
|
|
|
|
|
{
|
2020-01-03 12:45:58 +01:00
|
|
|
LOG_DBG("xdg-surface: configure");
|
|
|
|
|
|
2020-01-03 13:41:35 +01:00
|
|
|
struct wl_window *win = data;
|
|
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
2022-12-29 11:32:21 +01:00
|
|
|
if (win->unmapped) {
|
|
|
|
|
/*
|
|
|
|
|
* https://codeberg.org/dnkl/foot/issues/1249
|
|
|
|
|
* https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3487
|
|
|
|
|
* https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3719
|
|
|
|
|
* https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/108
|
|
|
|
|
*/
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-30 17:22:57 +02:00
|
|
|
bool wasnt_configured = !win->is_configured;
|
2021-01-10 15:41:01 +01:00
|
|
|
bool was_resizing = win->is_resizing;
|
2021-06-22 18:58:38 +02:00
|
|
|
bool csd_was_enabled = win->csd_mode == CSD_YES && !win->is_fullscreen;
|
|
|
|
|
int new_width = win->configure.width;
|
|
|
|
|
int new_height = win->configure.height;
|
2021-01-10 15:41:01 +01:00
|
|
|
|
2020-01-03 18:55:13 +01:00
|
|
|
win->is_configured = true;
|
2020-02-26 13:23:00 +01:00
|
|
|
win->is_maximized = win->configure.is_maximized;
|
2021-06-22 18:58:38 +02:00
|
|
|
win->is_fullscreen = win->configure.is_fullscreen;
|
2021-01-10 15:40:11 +01:00
|
|
|
win->is_resizing = win->configure.is_resizing;
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
|
2020-10-20 20:58:03 +02:00
|
|
|
win->is_tiled_top = win->configure.is_tiled_top;
|
|
|
|
|
win->is_tiled_bottom = win->configure.is_tiled_bottom;
|
|
|
|
|
win->is_tiled_left = win->configure.is_tiled_left;
|
|
|
|
|
win->is_tiled_right = win->configure.is_tiled_right;
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
|
|
|
|
|
win->is_constrained_top = win->configure.is_constrained_top;
|
|
|
|
|
win->is_constrained_bottom = win->configure.is_constrained_bottom;
|
|
|
|
|
win->is_constrained_left = win->configure.is_constrained_left;
|
|
|
|
|
win->is_constrained_right = win->configure.is_constrained_right;
|
|
|
|
|
|
2021-06-22 18:58:38 +02:00
|
|
|
win->is_tiled = (win->is_tiled_top ||
|
|
|
|
|
win->is_tiled_bottom ||
|
|
|
|
|
win->is_tiled_left ||
|
|
|
|
|
win->is_tiled_right);
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
|
2021-06-22 18:58:38 +02:00
|
|
|
win->csd_mode = win->configure.csd_mode;
|
|
|
|
|
|
|
|
|
|
bool enable_csd = win->csd_mode == CSD_YES && !win->is_fullscreen;
|
|
|
|
|
if (!csd_was_enabled && enable_csd)
|
|
|
|
|
csd_instantiate(win);
|
|
|
|
|
else if (csd_was_enabled && !enable_csd)
|
|
|
|
|
csd_destroy(win);
|
2020-01-03 18:55:13 +01:00
|
|
|
|
2022-04-16 11:15:10 +02:00
|
|
|
if (enable_csd && new_width > 0 && new_height > 0) {
|
2022-04-16 11:26:28 +02:00
|
|
|
if (wayl_win_csd_titlebar_visible(win))
|
|
|
|
|
new_height -= win->term->conf->csd.title_height;
|
2020-02-26 12:39:17 +01:00
|
|
|
|
2022-04-16 11:26:28 +02:00
|
|
|
if (wayl_win_csd_borders_visible(win)) {
|
2022-04-16 11:15:10 +02:00
|
|
|
new_height -= 2 * win->term->conf->csd.border_width_visible;
|
|
|
|
|
new_width -= 2 * win->term->conf->csd.border_width_visible;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-25 19:53:06 +01:00
|
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
2020-07-13 15:01:27 +02:00
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
enum resize_options opts = RESIZE_BY_CELLS;
|
|
|
|
|
|
2021-01-17 16:12:54 +01:00
|
|
|
#if 1
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* TODO: decide if we should do the last "forced" call when ending
|
2021-01-17 16:12:54 +01:00
|
|
|
* an interactive resize.
|
|
|
|
|
*
|
|
|
|
|
* Without it, the last TIOCSWINSZ sent to the client will be a
|
|
|
|
|
* scheduled one. I.e. there will be a small delay after the user
|
|
|
|
|
* has *stopped* resizing, and the client application receives the
|
|
|
|
|
* final size.
|
2021-01-18 09:50:23 +01:00
|
|
|
*
|
|
|
|
|
* Note: if we also disable content centering while resizing, then
|
|
|
|
|
* the last, forced, resize *is* necessary.
|
2021-01-17 16:12:54 +01:00
|
|
|
*/
|
2024-01-17 15:00:14 -05:00
|
|
|
if (was_resizing && !win->is_resizing)
|
|
|
|
|
opts |= RESIZE_FORCE;
|
2021-01-17 16:12:54 +01:00
|
|
|
#endif
|
2020-01-03 18:58:26 +01:00
|
|
|
|
2024-01-17 15:00:14 -05:00
|
|
|
bool resized = render_resize(term, new_width, new_height, opts);
|
|
|
|
|
|
2020-01-03 13:54:44 +01:00
|
|
|
if (win->configure.is_activated)
|
|
|
|
|
term_visual_focus_in(term);
|
|
|
|
|
else
|
|
|
|
|
term_visual_focus_out(term);
|
2020-02-25 19:53:06 +01:00
|
|
|
|
2025-05-21 13:01:30 +02:00
|
|
|
if (!resized && !term->render.pending.grid) {
|
2020-02-25 19:53:06 +01:00
|
|
|
/*
|
2020-07-13 15:01:27 +02:00
|
|
|
* If we didn't resize, we won't be committing a new surface
|
wayland: configure: work around GNOME/mutter weirdness
When resizing the window under mutter, mutter seems to expect a
configure ack *and* a surface commit *right* away, or things get out
of sync.
Unlike kwin, which is requires a commit for each configure ack, but is
fine with having the commit arrive later (after we've rendered it),
mutter is not.
I even tried delaying the configure ack until just before the commit,
but still no go.
So for now, detect when we're running under mutter and always do a
surface commit right away.
This can *not* be done on any other compositor as it breaks the CSD
and main surface synchronization; we've resized the CSDs, but not the
main surface.
I.e. this *should* not work, but for some reason is the *only* way to
make things work on mutter.
Interestingly, doing it any other way on mutter causes visual
glitches; window jumping around when resizing, or de-synchronized
CSDs/main surface.
2020-02-28 18:43:33 +01:00
|
|
|
* anytime soon. Some compositors require a commit in
|
|
|
|
|
* combination with an ack - make them happy.
|
2020-02-25 19:53:06 +01:00
|
|
|
*/
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_commit(win->surface.surf);
|
2020-02-25 19:53:06 +01:00
|
|
|
}
|
2020-04-30 17:22:57 +02:00
|
|
|
|
|
|
|
|
if (wasnt_configured)
|
|
|
|
|
term_window_configured(term);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct xdg_surface_listener xdg_surface_listener = {
|
|
|
|
|
.configure = &xdg_surface_configure,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_toplevel_decoration_configure(void *data,
|
|
|
|
|
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
|
|
|
|
|
uint32_t mode)
|
|
|
|
|
{
|
2020-02-23 14:17:48 +01:00
|
|
|
struct wl_window *win = data;
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(win->term->conf->csd.preferred != CONF_CSD_PREFER_NONE);
|
2020-10-11 18:47:26 +02:00
|
|
|
switch (mode) {
|
|
|
|
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
|
|
|
|
|
LOG_INFO("using CSD decorations");
|
2021-06-22 18:58:38 +02:00
|
|
|
win->configure.csd_mode = CSD_YES;
|
2020-10-11 18:47:26 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
|
|
|
|
|
LOG_INFO("using SSD decorations");
|
2021-06-22 18:58:38 +02:00
|
|
|
win->configure.csd_mode = CSD_NO;
|
2020-10-11 18:47:26 +02:00
|
|
|
break;
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2020-10-11 18:47:26 +02:00
|
|
|
default:
|
|
|
|
|
LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode);
|
|
|
|
|
break;
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = {
|
|
|
|
|
.configure = &xdg_toplevel_decoration_configure,
|
|
|
|
|
};
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
static bool
|
|
|
|
|
fdm_repeat(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct seat *seat = data;
|
|
|
|
|
uint64_t expiration_count;
|
|
|
|
|
ssize_t ret = read(
|
|
|
|
|
seat->kbd.repeat.fd, &expiration_count, sizeof(expiration_count));
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO("failed to read repeat key from repeat timer fd");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat->kbd.repeat.dont_re_repeat = true;
|
|
|
|
|
for (size_t i = 0; i < expiration_count; i++)
|
|
|
|
|
input_repeat(seat, seat->kbd.repeat.key);
|
|
|
|
|
seat->kbd.repeat.dont_re_repeat = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-27 20:18:03 +02:00
|
|
|
static void
|
|
|
|
|
handle_global(void *data, struct wl_registry *registry,
|
|
|
|
|
uint32_t name, const char *interface, uint32_t version)
|
|
|
|
|
{
|
2020-04-27 20:46:40 +02:00
|
|
|
LOG_DBG("global: 0x%08x, interface=%s, version=%u", name, interface, version);
|
2020-04-27 20:18:03 +02:00
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
if (streq(interface, wl_compositor_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 4;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
2024-01-09 22:51:17 -06:00
|
|
|
#if defined (WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION)
|
|
|
|
|
const uint32_t preferred = WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION;
|
|
|
|
|
#else
|
|
|
|
|
const uint32_t preferred = required;
|
|
|
|
|
#endif
|
2020-04-27 20:18:03 +02:00
|
|
|
wayl->compositor = wl_registry_bind(
|
2024-01-09 22:51:17 -06:00
|
|
|
wayl->registry, name, &wl_compositor_interface, min(version, preferred));
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wl_subcompositor_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->sub_compositor = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wl_subcompositor_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wl_shm_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
2024-07-18 08:07:32 +02:00
|
|
|
#if defined(WL_SHM_RELEASE_SINCE_VERSION)
|
|
|
|
|
const uint32_t preferred = WL_SHM_RELEASE_SINCE_VERSION;
|
|
|
|
|
#else
|
|
|
|
|
const uint32_t preferred = required;
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-04-27 20:18:03 +02:00
|
|
|
wayl->shm = wl_registry_bind(
|
2024-07-18 08:07:32 +02:00
|
|
|
wayl->registry, name, &wl_shm_interface, min(version, preferred));
|
2020-04-27 20:18:03 +02:00
|
|
|
wl_shm_add_listener(wayl->shm, &shm_listener, wayl);
|
2024-07-18 14:27:40 +02:00
|
|
|
#if defined(WL_SHM_RELEASE_SINCE_VERSION)
|
2024-07-18 08:07:32 +02:00
|
|
|
wayl->use_shm_release = version >= WL_SHM_RELEASE_SINCE_VERSION;
|
2024-07-18 14:27:40 +02:00
|
|
|
#else
|
|
|
|
|
wayl->use_shm_release = false;
|
|
|
|
|
#endif
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, xdg_wm_base_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
2020-10-20 20:58:03 +02:00
|
|
|
/*
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
* We *require* version 1, but _can_ use version 2, 5 or 7, if
|
|
|
|
|
* available.
|
|
|
|
|
*
|
|
|
|
|
* Version 2 adds 'tiled' window states. We use this
|
|
|
|
|
* information to restore the window size when window is
|
|
|
|
|
* un-tiled.
|
|
|
|
|
*
|
|
|
|
|
* Version 5 adds 'wm_capabilities'. We use this information
|
|
|
|
|
* to draw window decorations.
|
|
|
|
|
*
|
|
|
|
|
* Version 7 adds 'constrained' window states. We use this
|
|
|
|
|
* information to determine whether to allow window resize
|
|
|
|
|
* (via CSDs) or not.
|
2020-10-20 20:58:03 +02:00
|
|
|
*/
|
wayland+input: add support for toplevel edge constraints
Edge constraints are new (not yet available in a wayland-protocols
release) toplevel states, acting as a complement to the existing tiled
states.
Tiled tells us we shouldn't draw shadows etc *outside our window
geometry*.
Constrained tells us the window cannot be resized in the constrained
direction.
This patch does a couple of things:
* Recognize the new states when debug logging
* Change is_top_left() etc to look at the new constrained state
instead of the tiled state. These functions are used when both
choosing cursor shape, and when determining if/how to resize a
window on a CSD edge click-and-drag.
* Update cursor shape selection to use the default (left_ptr) shape
when on a constrained edge (or corner).
* Update CSD resize triggering, to not trigger a resize when attempted
on a constrained edge (or corner).
See
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677:
An edge constraint is an complementery state to the tiled state,
meaning that it's not only tiled, but constrained in a way that it
can't resize in that direction.
This typically means that the constrained edge is tiled against a
monitor edge. An example configuration is two windows tiled next
to each other on a single monitor. Together they cover the whole
work area.
The left window would have the following tiled and edge constraint
state:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_left ]
while the right window would have the following:
[ tiled_top, tiled_right, tiled_bottom, tiled_left,
constrained_top, constrained_bottom, constrained_right ]
This aims to replace and deprecate the
`gtk_surface1.configure_edges` event and the
`gtk_surface1.edge_constraint` enum.
2025-04-07 13:32:30 +02:00
|
|
|
#if defined(XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION)
|
|
|
|
|
const uint32_t preferred = XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION;
|
|
|
|
|
#elif defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
|
2022-05-06 20:05:04 +02:00
|
|
|
const uint32_t preferred = XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION;
|
|
|
|
|
#elif defined(XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION)
|
|
|
|
|
const uint32_t preferred = XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION;
|
|
|
|
|
#else
|
|
|
|
|
const uint32_t preferred = required;
|
|
|
|
|
#endif
|
2020-10-20 20:58:03 +02:00
|
|
|
|
2020-04-27 20:18:03 +02:00
|
|
|
wayl->shell = wl_registry_bind(
|
2022-05-06 20:05:04 +02:00
|
|
|
wayl->registry, name, &xdg_wm_base_interface, min(version, preferred));
|
2020-04-27 20:18:03 +02:00
|
|
|
xdg_wm_base_add_listener(wayl->shell, &xdg_wm_base_listener, wayl);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, zxdg_decoration_manager_v1_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->xdg_decoration_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &zxdg_decoration_manager_v1_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wl_seat_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 5;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
2024-06-15 10:17:01 +02:00
|
|
|
#if defined(WL_POINTER_AXIS_VALUE120_SINCE_VERSION)
|
|
|
|
|
const uint32_t preferred = WL_POINTER_AXIS_VALUE120_SINCE_VERSION;
|
|
|
|
|
#else
|
|
|
|
|
const uint32_t preferred = required;
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
int repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
|
|
|
|
|
if (repeat_fd == -1) {
|
|
|
|
|
LOG_ERRNO("failed to create keyboard repeat timer FD");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wl_seat *wl_seat = wl_registry_bind(
|
2024-06-15 10:17:01 +02:00
|
|
|
wayl->registry, name, &wl_seat_interface, min(version, preferred));
|
2020-07-08 16:45:26 +02:00
|
|
|
|
|
|
|
|
tll_push_back(wayl->seats, ((struct seat){
|
|
|
|
|
.wayl = wayl,
|
|
|
|
|
.wl_seat = wl_seat,
|
|
|
|
|
.wl_name = name,
|
|
|
|
|
.kbd = {
|
|
|
|
|
.repeat = {
|
|
|
|
|
.fd = repeat_fd,
|
|
|
|
|
},
|
2020-07-16 17:47:47 +02:00
|
|
|
}}));
|
2020-07-08 16:45:26 +02:00
|
|
|
|
|
|
|
|
struct seat *seat = &tll_back(wayl->seats);
|
|
|
|
|
|
|
|
|
|
if (!fdm_add(wayl->fdm, repeat_fd, EPOLLIN, &fdm_repeat, seat)) {
|
|
|
|
|
close(repeat_fd);
|
|
|
|
|
seat->kbd.repeat.fd = -1;
|
|
|
|
|
seat_destroy(seat);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-22 07:58:43 +02:00
|
|
|
seat->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
|
|
|
if (seat->kbd.xkb != NULL) {
|
|
|
|
|
seat->kbd.xkb_compose_table = xkb_compose_table_new_from_locale(
|
|
|
|
|
seat->kbd.xkb, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS);
|
|
|
|
|
|
|
|
|
|
if (seat->kbd.xkb_compose_table != NULL) {
|
|
|
|
|
seat->kbd.xkb_compose_state = xkb_compose_state_new(
|
|
|
|
|
seat->kbd.xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
|
|
|
|
} else {
|
|
|
|
|
LOG_WARN("failed to instantiate compose table; dead keys (compose) will not work");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-16 17:47:47 +02:00
|
|
|
seat_add_data_device(seat);
|
|
|
|
|
seat_add_primary_selection(seat);
|
2020-10-10 22:14:35 +02:00
|
|
|
seat_add_text_input(seat);
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
seat_add_key_bindings(seat);
|
2020-07-08 16:45:26 +02:00
|
|
|
wl_seat_add_listener(wl_seat, &seat_listener, seat);
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, zxdg_output_manager_v1_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->xdg_output_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &zxdg_output_manager_v1_interface,
|
|
|
|
|
min(version, 2));
|
2020-09-04 17:56:40 +02:00
|
|
|
|
|
|
|
|
tll_foreach(wayl->monitors, it) {
|
|
|
|
|
struct monitor *mon = &it->item;
|
|
|
|
|
mon->xdg = zxdg_output_manager_v1_get_xdg_output(
|
|
|
|
|
wayl->xdg_output_manager, mon->output);
|
|
|
|
|
zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon);
|
|
|
|
|
}
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wl_output_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 2;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
2022-01-06 20:00:00 +01:00
|
|
|
#if defined(WL_OUTPUT_NAME_SINCE_VERSION)
|
|
|
|
|
const uint32_t preferred = WL_OUTPUT_NAME_SINCE_VERSION;
|
|
|
|
|
#elif defined(WL_OUTPUT_RELEASE_SINCE_VERSION)
|
|
|
|
|
const uint32_t preferred = WL_OUTPUT_RELEASE_SINCE_VERSION;
|
|
|
|
|
#else
|
|
|
|
|
const uint32_t preferred = required;
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-04-27 20:18:03 +02:00
|
|
|
struct wl_output *output = wl_registry_bind(
|
2022-01-06 20:00:00 +01:00
|
|
|
wayl->registry, name, &wl_output_interface, min(version, preferred));
|
2020-04-27 20:18:03 +02:00
|
|
|
|
|
|
|
|
tll_push_back(
|
2020-04-27 20:46:40 +02:00
|
|
|
wayl->monitors,
|
2020-11-24 00:00:43 +01:00
|
|
|
((struct monitor){.wayl = wayl, .output = output, .wl_name = name,
|
2021-07-25 21:46:31 -05:00
|
|
|
.scale = 1,
|
2020-11-24 00:00:43 +01:00
|
|
|
.use_output_release = version >= WL_OUTPUT_RELEASE_SINCE_VERSION}));
|
2020-04-27 20:18:03 +02:00
|
|
|
|
|
|
|
|
struct monitor *mon = &tll_back(wayl->monitors);
|
|
|
|
|
wl_output_add_listener(output, &output_listener, mon);
|
|
|
|
|
|
|
|
|
|
if (wayl->xdg_output_manager != NULL) {
|
|
|
|
|
mon->xdg = zxdg_output_manager_v1_get_xdg_output(
|
|
|
|
|
wayl->xdg_output_manager, mon->output);
|
|
|
|
|
zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wl_data_device_manager_interface.name)) {
|
2020-10-26 21:02:24 +01:00
|
|
|
const uint32_t required = 3;
|
2020-04-27 20:18:03 +02:00
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->data_device_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wl_data_device_manager_interface, required);
|
2020-07-16 17:47:47 +02:00
|
|
|
|
|
|
|
|
tll_foreach(wayl->seats, it)
|
|
|
|
|
seat_add_data_device(&it->item);
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, zwp_primary_selection_device_manager_v1_interface.name)) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->primary_selection_device_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name,
|
|
|
|
|
&zwp_primary_selection_device_manager_v1_interface, required);
|
2020-07-16 17:47:47 +02:00
|
|
|
|
|
|
|
|
tll_foreach(wayl->seats, it)
|
|
|
|
|
seat_add_primary_selection(&it->item);
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wp_presentation_interface.name)) {
|
2022-04-17 16:29:30 +02:00
|
|
|
if (wayl->presentation_timings) {
|
2020-04-27 20:18:03 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->presentation = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wp_presentation_interface, required);
|
|
|
|
|
wp_presentation_add_listener(
|
|
|
|
|
wayl->presentation, &presentation_listener, wayl);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-10 22:14:35 +02:00
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, xdg_activation_v1_interface.name)) {
|
2021-05-09 12:13:14 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->xdg_activation = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &xdg_activation_v1_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wp_viewporter_interface.name)) {
|
2023-03-08 10:44:03 +01:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->viewporter = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wp_viewporter_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wp_fractional_scale_manager_v1_interface.name)) {
|
2023-03-08 10:44:03 +01:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->fractional_scale_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name,
|
|
|
|
|
&wp_fractional_scale_manager_v1_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, wp_cursor_shape_manager_v1_interface.name)) {
|
2023-06-27 17:25:57 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
2025-05-21 15:25:28 +02:00
|
|
|
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) /* 1.42 */
|
|
|
|
|
const uint32_t preferred = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION;
|
|
|
|
|
#else
|
|
|
|
|
const uint32_t preferred = required;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
wayl->shape_manager_version = min(required, preferred);
|
2023-06-27 17:25:57 +02:00
|
|
|
wayl->cursor_shape_manager = wl_registry_bind(
|
2025-05-21 15:25:28 +02:00
|
|
|
wayl->registry, name, &wp_cursor_shape_manager_v1_interface,
|
|
|
|
|
min(required, preferred));
|
2023-06-27 17:25:57 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-21 16:09:34 +02:00
|
|
|
else if (streq(interface, wp_single_pixel_buffer_manager_v1_interface.name)) {
|
|
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->single_pixel_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name,
|
|
|
|
|
&wp_single_pixel_buffer_manager_v1_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 11:18:30 +02:00
|
|
|
else if (streq(interface, xdg_toplevel_icon_v1_interface.name)) {
|
|
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->toplevel_icon_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &xdg_toplevel_icon_v1_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-17 10:10:10 +01:00
|
|
|
else if (streq(interface, xdg_system_bell_v1_interface.name)) {
|
|
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->system_bell = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &xdg_system_bell_v1_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
else if (streq(interface, wp_color_manager_v1_interface.name)) {
|
|
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->color_management.manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wp_color_manager_v1_interface, required);
|
|
|
|
|
|
|
|
|
|
wp_color_manager_v1_add_listener(
|
|
|
|
|
wayl->color_management.manager, &color_manager_listener, wayl);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-03 18:36:56 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2024-01-24 23:17:28 +00:00
|
|
|
else if (streq(interface, zwp_text_input_manager_v3_interface.name)) {
|
2020-10-10 22:14:35 +02:00
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->text_input_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &zwp_text_input_manager_v3_interface, required);
|
|
|
|
|
|
|
|
|
|
tll_foreach(wayl->seats, it)
|
|
|
|
|
seat_add_text_input(&it->item);
|
|
|
|
|
}
|
2020-12-03 18:36:56 +01:00
|
|
|
#endif
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-27 20:46:40 +02:00
|
|
|
static void
|
|
|
|
|
monitor_destroy(struct monitor *mon)
|
|
|
|
|
{
|
|
|
|
|
if (mon->xdg != NULL)
|
|
|
|
|
zxdg_output_v1_destroy(mon->xdg);
|
2020-11-24 00:00:43 +01:00
|
|
|
if (mon->output != NULL) {
|
|
|
|
|
if (mon->use_output_release)
|
|
|
|
|
wl_output_release(mon->output);
|
|
|
|
|
else
|
|
|
|
|
wl_output_destroy(mon->output);
|
|
|
|
|
}
|
2020-04-27 20:46:40 +02:00
|
|
|
free(mon->make);
|
|
|
|
|
free(mon->model);
|
2020-06-25 17:30:51 +02:00
|
|
|
free(mon->name);
|
|
|
|
|
free(mon->description);
|
2020-04-27 20:46:40 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
static void
|
|
|
|
|
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
|
|
|
|
{
|
2020-04-28 19:05:10 +02:00
|
|
|
LOG_DBG("global removed: 0x%08x", name);
|
2020-04-27 20:46:40 +02:00
|
|
|
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
|
2020-07-09 08:47:56 +02:00
|
|
|
/* Check if this is an output */
|
2020-04-27 20:46:40 +02:00
|
|
|
tll_foreach(wayl->monitors, it) {
|
2020-04-28 19:05:10 +02:00
|
|
|
struct monitor *mon = &it->item;
|
|
|
|
|
|
|
|
|
|
if (mon->wl_name != name)
|
2020-04-27 20:46:40 +02:00
|
|
|
continue;
|
|
|
|
|
|
2020-11-09 19:59:05 +01:00
|
|
|
LOG_INFO("monitor unplugged or disabled: %s", mon->name);
|
2020-04-27 20:46:40 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Update all terminals that are mapped here. On Sway 1.4,
|
|
|
|
|
* surfaces are *not* unmapped before the output is removed
|
|
|
|
|
*/
|
|
|
|
|
|
2020-11-06 19:25:54 +01:00
|
|
|
tll_foreach(wayl->terms, t) {
|
|
|
|
|
tll_foreach(t->item->window->on_outputs, o) {
|
|
|
|
|
if (o->item->output == mon->output) {
|
|
|
|
|
surface_leave(t->item->window, NULL, mon->output);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-27 20:46:40 +02:00
|
|
|
|
2020-07-09 08:47:56 +02:00
|
|
|
monitor_destroy(mon);
|
2020-04-27 20:46:40 +02:00
|
|
|
tll_remove(wayl->monitors, it);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-09 08:47:56 +02:00
|
|
|
/* A seat? */
|
|
|
|
|
tll_foreach(wayl->seats, it) {
|
|
|
|
|
struct seat *seat = &it->item;
|
|
|
|
|
|
|
|
|
|
if (seat->wl_name != name)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
LOG_INFO("seat destroyed: %s", seat->name);
|
|
|
|
|
|
|
|
|
|
if (seat->kbd_focus != NULL) {
|
2020-07-09 08:52:39 +02:00
|
|
|
LOG_WARN("compositor destroyed seat '%s' "
|
2020-08-10 18:57:17 +02:00
|
|
|
"without sending a keyboard leave event",
|
2020-07-09 08:52:39 +02:00
|
|
|
seat->name);
|
2020-07-09 08:47:56 +02:00
|
|
|
|
2020-07-09 08:52:39 +02:00
|
|
|
if (seat->wl_keyboard != NULL)
|
|
|
|
|
keyboard_listener.leave(
|
2023-06-26 16:10:40 +02:00
|
|
|
seat, seat->wl_keyboard, -1, seat->kbd_focus->window->surface.surf);
|
2020-08-10 18:57:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (seat->mouse_focus != NULL) {
|
|
|
|
|
LOG_WARN("compositor destroyed seat '%s' "
|
|
|
|
|
"without sending a pointer leave event",
|
|
|
|
|
seat->name);
|
2020-07-09 08:47:56 +02:00
|
|
|
|
2020-07-09 08:52:39 +02:00
|
|
|
if (seat->wl_pointer != NULL)
|
|
|
|
|
pointer_listener.leave(
|
2023-06-26 16:10:40 +02:00
|
|
|
seat, seat->wl_pointer, -1, seat->mouse_focus->window->surface.surf);
|
2020-07-09 08:47:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seat_destroy(seat);
|
|
|
|
|
tll_remove(wayl->seats, it);
|
2020-08-10 18:58:39 +02:00
|
|
|
return;
|
2020-07-09 08:47:56 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-27 20:46:40 +02:00
|
|
|
LOG_WARN("unknown global removed: 0x%08x", name);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_registry_listener registry_listener = {
|
|
|
|
|
.global = &handle_global,
|
|
|
|
|
.global_remove = &handle_global_remove,
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-04 23:27:59 +01:00
|
|
|
static void
|
|
|
|
|
fdm_hook(struct fdm *fdm, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
wayl_flush(wayl);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:11:35 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_wayl(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
2020-01-03 21:02:12 +01:00
|
|
|
int event_count = 0;
|
|
|
|
|
|
2020-01-04 23:32:00 +01:00
|
|
|
if (events & EPOLLIN) {
|
2020-01-10 19:23:08 +01:00
|
|
|
if (wl_display_read_events(wayl->display) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to read events from the Wayland socket");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-01-03 21:02:12 +01:00
|
|
|
|
2025-05-02 13:43:59 +02:00
|
|
|
wl_display_dispatch_pending(wayl->display);
|
|
|
|
|
|
2021-05-24 20:51:30 +02:00
|
|
|
while (wl_display_prepare_read(wayl->display) != 0) {
|
|
|
|
|
if (wl_display_dispatch_pending(wayl->display) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to dispatch pending Wayland events");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-04 23:32:00 +01:00
|
|
|
}
|
2019-10-27 19:11:35 +01:00
|
|
|
|
|
|
|
|
if (events & EPOLLHUP) {
|
2019-11-23 13:56:11 +01:00
|
|
|
LOG_WARN("disconnected from Wayland");
|
2021-07-24 20:31:14 +02:00
|
|
|
/*
|
|
|
|
|
* Do *not* call wl_display_cancel_read() here.
|
|
|
|
|
*
|
|
|
|
|
* Doing so causes later calls to wayl_roundtrip() (called
|
|
|
|
|
* from term_destroy() -> wayl_win_destroy()) to hang
|
|
|
|
|
* indefinitely.
|
|
|
|
|
*
|
|
|
|
|
* https://codeberg.org/dnkl/foot/issues/651
|
|
|
|
|
*/
|
2019-10-27 19:11:35 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 20:02:06 +01:00
|
|
|
return event_count != -1;
|
2019-10-27 19:11:35 +01:00
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
struct wayland *
|
2022-04-17 16:29:30 +02:00
|
|
|
wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager,
|
|
|
|
|
bool presentation_timings)
|
2019-10-27 19:08:48 +01:00
|
|
|
{
|
2020-08-09 08:55:20 +01:00
|
|
|
struct wayland *wayl = calloc(1, sizeof(*wayl));
|
|
|
|
|
if (unlikely(wayl == NULL)) {
|
|
|
|
|
LOG_ERRNO("calloc() failed");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
wayl->fdm = fdm;
|
key-binding: new API, for handling sets of key bindings
Up until now, our Wayland seats have been tracking key bindings. This
makes sense, since the seat’s keymap determines how the key bindings
are resolved.
However, tying bindings to the seat/keymap alone isn’t enough, since
we also depend on the current configuration (i.e. user settings) when
resolving a key binding.
This means configurations that doesn’t match the wayland object’s
configuration, currently don’t resolve key bindings correctly. This
applies to footclients where the user has overridden key bindings on
the command line (e.g. --override key-bindings.foo=bar).
Thus, to correctly resolve key bindings, each set of key bindings must
be tied *both* to a seat/keymap, *and* a configuration.
This patch introduces a key-binding manager, with an API to
add/remove/lookup, and load/unload keymaps from sets of key bindings.
In the API, sets are tied to a seat and terminal instance, since this
makes the most sense (we need to instantiate, or incref a set whenever
a new terminal instance is created). Internally, the set is tied to a
seat and the terminal’s configuration.
Sets are *added* when a new seat is added, and when a new terminal
instance is created. Since there can only be one instance of each
seat, sets are always removed when a seat is removed.
Terminals on the other hand can re-use the same configuration (and
typically do). Thus, sets ref-count the configuration. In other words,
when instantiating a new terminal, we may not have to instantiate a
new set of key bindings, but can often be incref:ed instead.
Whenever the keymap changes on a seat, all key bindings sets
associated with that seat reloads (re-resolves) their key bindings.
Closes #931
2022-04-17 15:39:51 +02:00
|
|
|
wayl->key_binding_manager = key_binding_manager;
|
2020-03-15 13:36:35 +01:00
|
|
|
wayl->fd = -1;
|
2022-04-17 16:29:30 +02:00
|
|
|
wayl->presentation_timings = presentation_timings;
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2020-03-09 18:45:43 +01:00
|
|
|
if (!fdm_hook_add(fdm, &fdm_hook, wayl, FDM_HOOK_PRIORITY_LOW)) {
|
|
|
|
|
LOG_ERR("failed to add FDM hook");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
wayl->display = wl_display_connect(NULL);
|
|
|
|
|
if (wayl->display == NULL) {
|
|
|
|
|
LOG_ERR("failed to connect to wayland; no compositor running?");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wayl->registry = wl_display_get_registry(wayl->display);
|
|
|
|
|
if (wayl->registry == NULL) {
|
|
|
|
|
LOG_ERR("failed to get wayland registry");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wl_registry_add_listener(wayl->registry, ®istry_listener, wayl);
|
|
|
|
|
wl_display_roundtrip(wayl->display);
|
|
|
|
|
|
|
|
|
|
if (wayl->compositor == NULL) {
|
|
|
|
|
LOG_ERR("no compositor");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-11-03 15:39:26 +01:00
|
|
|
if (wayl->sub_compositor == NULL) {
|
|
|
|
|
LOG_ERR("no sub compositor");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2019-10-27 19:08:48 +01:00
|
|
|
if (wayl->shm == NULL) {
|
|
|
|
|
LOG_ERR("no shared memory buffers interface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
if (wayl->shell == NULL) {
|
|
|
|
|
LOG_ERR("no XDG shell interface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
if (wayl->data_device_manager == NULL) {
|
|
|
|
|
LOG_ERR("no clipboard available "
|
|
|
|
|
"(wl_data_device_manager not implemented by server)");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2021-11-07 11:29:25 +01:00
|
|
|
if (tll_length(wayl->seats) == 0) {
|
|
|
|
|
LOG_ERR("no seats available (wl_seat interface too old?)");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2023-07-17 16:19:21 +02:00
|
|
|
if (tll_length(wayl->monitors) == 0) {
|
|
|
|
|
LOG_ERR("no monitors available");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 08:45:54 +02:00
|
|
|
if (presentation_timings && wayl->presentation == NULL) {
|
|
|
|
|
LOG_ERR("compositor does not implement the presentation time interface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
if (wayl->primary_selection_device_manager == NULL)
|
2024-09-13 08:45:54 +02:00
|
|
|
LOG_WARN("compositor does not implement the primary selection interface");
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2022-04-17 16:29:30 +02:00
|
|
|
if (wayl->xdg_activation == NULL) {
|
2021-05-09 12:13:14 +02:00
|
|
|
LOG_WARN(
|
2024-09-13 08:45:54 +02:00
|
|
|
"compositor does not implement XDG activation, "
|
2021-05-09 12:13:14 +02:00
|
|
|
"bell.urgent will fall back to coloring the window margins red");
|
2023-06-26 18:37:49 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-31 16:32:53 +02:00
|
|
|
if (wayl->fractional_scale_manager == NULL || wayl->viewporter == NULL)
|
2024-09-13 08:45:54 +02:00
|
|
|
LOG_WARN("compositor does not implement fractional scaling");
|
2021-05-09 12:13:14 +02:00
|
|
|
|
2023-06-27 17:25:57 +02:00
|
|
|
if (wayl->cursor_shape_manager == NULL) {
|
2024-09-13 08:45:54 +02:00
|
|
|
LOG_WARN("compositor does not implement server-side cursors, "
|
2023-06-27 17:25:57 +02:00
|
|
|
"falling back to client-side cursors");
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 08:45:54 +02:00
|
|
|
if (wayl->toplevel_icon_manager == NULL) {
|
|
|
|
|
LOG_WARN("compositor does not implement the XDG toplevel icon protocol");
|
2020-03-09 18:46:09 +01:00
|
|
|
}
|
2020-01-01 16:09:16 +01:00
|
|
|
|
2020-12-25 22:03:52 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
|
|
|
|
if (wayl->text_input_manager == NULL) {
|
|
|
|
|
LOG_WARN("text input interface not implemented by compositor; "
|
|
|
|
|
"IME will be disabled");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-07-08 18:20:34 +02:00
|
|
|
/* Trigger listeners registered when handling globals */
|
2020-07-08 16:45:26 +02:00
|
|
|
wl_display_roundtrip(wayl->display);
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
tll_foreach(wayl->monitors, it) {
|
2019-12-05 19:35:34 +01:00
|
|
|
LOG_INFO(
|
2023-07-17 16:19:14 +02:00
|
|
|
"%s: %dx%d+%dx%d@%dHz %s %.2f\" scale=%d, DPI=%.2f/%.2f (physical/scaled)",
|
2020-03-11 16:10:55 +01:00
|
|
|
it->item.name, it->item.dim.px_real.width, it->item.dim.px_real.height,
|
2023-07-25 15:56:30 +02:00
|
|
|
it->item.x, it->item.y, (int)roundf(it->item.refresh),
|
2020-06-25 17:30:51 +02:00
|
|
|
it->item.model != NULL ? it->item.model : it->item.description,
|
|
|
|
|
it->item.inch, it->item.scale,
|
2023-07-17 16:19:14 +02:00
|
|
|
it->item.dpi.physical, it->item.dpi.scaled);
|
2019-10-27 19:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
2020-03-15 13:36:35 +01:00
|
|
|
wayl->fd = wl_display_get_fd(wayl->display);
|
|
|
|
|
if (fcntl(wayl->fd, F_SETFL, fcntl(wayl->fd, F_GETFL) | O_NONBLOCK) < 0) {
|
2020-01-10 19:22:59 +01:00
|
|
|
LOG_ERRNO("failed to make Wayland socket non-blocking");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-15 13:37:56 +01:00
|
|
|
if (!fdm_add(fdm, wayl->fd, EPOLLIN, &fdm_wayl, wayl))
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
if (wl_display_prepare_read(wayl->display) != 0) {
|
|
|
|
|
LOG_ERRNO("failed to prepare for reading wayland events");
|
2019-10-27 19:16:12 +01:00
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
return wayl;
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
if (wayl != NULL)
|
|
|
|
|
wayl_destroy(wayl);
|
|
|
|
|
return NULL;
|
2019-10-27 15:57:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
wayl_destroy(struct wayland *wayl)
|
|
|
|
|
{
|
2019-10-27 19:16:25 +01:00
|
|
|
if (wayl == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-11-01 20:25:08 +01:00
|
|
|
tll_foreach(wayl->terms, it) {
|
|
|
|
|
static bool have_warned = false;
|
|
|
|
|
if (!have_warned) {
|
|
|
|
|
have_warned = true;
|
|
|
|
|
LOG_WARN("there are terminals still running");
|
|
|
|
|
term_destroy(it->item);
|
|
|
|
|
}
|
2019-10-27 19:16:12 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-01 20:25:08 +01:00
|
|
|
tll_free(wayl->terms);
|
|
|
|
|
|
2020-01-04 23:27:59 +01:00
|
|
|
fdm_hook_del(wayl->fdm, &fdm_hook, FDM_HOOK_PRIORITY_LOW);
|
|
|
|
|
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_foreach(wayl->monitors, it) {
|
2020-04-27 20:46:40 +02:00
|
|
|
monitor_destroy(&it->item);
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_remove(wayl->monitors, it);
|
|
|
|
|
}
|
2019-10-27 15:57:23 +01:00
|
|
|
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_foreach(wayl->seats, it) {
|
2020-07-08 16:45:26 +02:00
|
|
|
seat_destroy(&it->item);
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_remove(wayl->seats, it);
|
|
|
|
|
}
|
2020-01-04 22:01:19 +01:00
|
|
|
|
2020-12-03 18:36:56 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2020-10-10 22:14:35 +02:00
|
|
|
if (wayl->text_input_manager != NULL)
|
|
|
|
|
zwp_text_input_manager_v3_destroy(wayl->text_input_manager);
|
2020-12-03 18:36:56 +01:00
|
|
|
#endif
|
|
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
if (wayl->color_management.img_description != NULL)
|
|
|
|
|
wp_image_description_v1_destroy(wayl->color_management.img_description);
|
|
|
|
|
if (wayl->color_management.manager != NULL)
|
|
|
|
|
wp_color_manager_v1_destroy(wayl->color_management.manager);
|
2025-01-17 10:10:10 +01:00
|
|
|
if (wayl->system_bell != NULL)
|
|
|
|
|
xdg_system_bell_v1_destroy(wayl->system_bell);
|
2024-09-08 11:18:30 +02:00
|
|
|
if (wayl->toplevel_icon_manager != NULL)
|
|
|
|
|
xdg_toplevel_icon_manager_v1_destroy(wayl->toplevel_icon_manager);
|
2024-05-21 16:09:34 +02:00
|
|
|
if (wayl->single_pixel_manager != NULL)
|
|
|
|
|
wp_single_pixel_buffer_manager_v1_destroy(wayl->single_pixel_manager);
|
2023-03-08 10:44:03 +01:00
|
|
|
if (wayl->fractional_scale_manager != NULL)
|
|
|
|
|
wp_fractional_scale_manager_v1_destroy(wayl->fractional_scale_manager);
|
|
|
|
|
if (wayl->viewporter != NULL)
|
|
|
|
|
wp_viewporter_destroy(wayl->viewporter);
|
2023-06-27 17:25:57 +02:00
|
|
|
if (wayl->cursor_shape_manager != NULL)
|
|
|
|
|
wp_cursor_shape_manager_v1_destroy(wayl->cursor_shape_manager);
|
2021-05-09 12:13:14 +02:00
|
|
|
if (wayl->xdg_activation != NULL)
|
|
|
|
|
xdg_activation_v1_destroy(wayl->xdg_activation);
|
2019-10-27 15:57:23 +01:00
|
|
|
if (wayl->xdg_output_manager != NULL)
|
|
|
|
|
zxdg_output_manager_v1_destroy(wayl->xdg_output_manager);
|
2019-10-27 18:43:07 +01:00
|
|
|
if (wayl->shell != NULL)
|
|
|
|
|
xdg_wm_base_destroy(wayl->shell);
|
|
|
|
|
if (wayl->xdg_decoration_manager != NULL)
|
|
|
|
|
zxdg_decoration_manager_v1_destroy(wayl->xdg_decoration_manager);
|
2019-12-31 15:39:40 +01:00
|
|
|
if (wayl->presentation != NULL)
|
|
|
|
|
wp_presentation_destroy(wayl->presentation);
|
2019-10-27 15:57:23 +01:00
|
|
|
if (wayl->data_device_manager != NULL)
|
|
|
|
|
wl_data_device_manager_destroy(wayl->data_device_manager);
|
|
|
|
|
if (wayl->primary_selection_device_manager != NULL)
|
|
|
|
|
zwp_primary_selection_device_manager_v1_destroy(wayl->primary_selection_device_manager);
|
2024-07-18 08:07:32 +02:00
|
|
|
if (wayl->shm != NULL) {
|
2024-07-18 14:27:40 +02:00
|
|
|
#if defined(WL_SHM_RELEASE_SINCE_VERSION)
|
2024-07-18 08:07:32 +02:00
|
|
|
if (wayl->use_shm_release)
|
|
|
|
|
wl_shm_release(wayl->shm);
|
|
|
|
|
else
|
2024-07-18 14:27:40 +02:00
|
|
|
#endif
|
2024-07-18 08:07:32 +02:00
|
|
|
wl_shm_destroy(wayl->shm);
|
|
|
|
|
}
|
2019-10-27 15:57:23 +01:00
|
|
|
if (wayl->sub_compositor != NULL)
|
|
|
|
|
wl_subcompositor_destroy(wayl->sub_compositor);
|
|
|
|
|
if (wayl->compositor != NULL)
|
|
|
|
|
wl_compositor_destroy(wayl->compositor);
|
|
|
|
|
if (wayl->registry != NULL)
|
|
|
|
|
wl_registry_destroy(wayl->registry);
|
2020-03-15 13:36:35 +01:00
|
|
|
if (wayl->fd != -1)
|
|
|
|
|
fdm_del_no_close(wayl->fdm, wayl->fd);
|
2020-11-09 19:59:05 +01:00
|
|
|
if (wayl->display != NULL) {
|
|
|
|
|
wayl_flush(wayl);
|
2019-10-27 15:57:23 +01:00
|
|
|
wl_display_disconnect(wayl->display);
|
2020-11-09 19:59:05 +01:00
|
|
|
}
|
2019-10-27 19:16:42 +01:00
|
|
|
|
|
|
|
|
free(wayl);
|
2019-10-27 15:57:23 +01:00
|
|
|
}
|
2019-10-27 16:01:03 +01:00
|
|
|
|
2023-07-31 16:32:53 +02:00
|
|
|
static void
|
|
|
|
|
fractional_scale_preferred_scale(
|
2023-03-08 10:44:03 +01:00
|
|
|
void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1,
|
|
|
|
|
uint32_t scale)
|
|
|
|
|
{
|
|
|
|
|
struct wl_window *win = data;
|
2023-06-26 17:32:33 +02:00
|
|
|
|
2023-07-17 16:12:34 +02:00
|
|
|
const float new_scale = (float)scale / 120.;
|
|
|
|
|
|
|
|
|
|
if (win->scale == new_scale)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("fractional scale: %.2f -> %.2f", win->scale, new_scale);
|
|
|
|
|
|
|
|
|
|
win->scale = new_scale;
|
2023-06-26 17:32:33 +02:00
|
|
|
update_term_for_output_change(win->term);
|
2023-03-08 10:44:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
|
|
|
|
|
.preferred_scale = &fractional_scale_preferred_scale,
|
|
|
|
|
};
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
struct wl_window *
|
2021-10-28 17:51:44 -07:00
|
|
|
wayl_win_init(struct terminal *term, const char *token)
|
2019-10-27 19:08:48 +01:00
|
|
|
{
|
2020-01-03 13:37:03 +01:00
|
|
|
struct wayland *wayl = term->wl;
|
2020-04-01 18:40:51 +02:00
|
|
|
const struct config *conf = term->conf;
|
2020-01-03 13:37:03 +01:00
|
|
|
|
2020-08-09 08:55:20 +01:00
|
|
|
struct wl_window *win = calloc(1, sizeof(*win));
|
|
|
|
|
if (unlikely(win == NULL)) {
|
|
|
|
|
LOG_ERRNO("calloc() failed");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-03 13:37:03 +01:00
|
|
|
win->term = term;
|
2021-06-22 18:58:38 +02:00
|
|
|
win->csd_mode = CSD_UNKNOWN;
|
2020-02-29 12:52:55 +01:00
|
|
|
win->csd.move_timeout_fd = -1;
|
2021-01-17 16:12:54 +01:00
|
|
|
win->resize_timeout_fd = -1;
|
2023-06-26 17:31:39 +02:00
|
|
|
win->scale = -1.;
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2022-05-06 20:05:04 +02:00
|
|
|
win->wm_capabilities.maximize = true;
|
|
|
|
|
win->wm_capabilities.minimize = true;
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
win->surface.surf = wl_compositor_create_surface(wayl->compositor);
|
|
|
|
|
if (win->surface.surf == NULL) {
|
2019-10-27 19:08:48 +01:00
|
|
|
LOG_ERR("failed to create wayland surface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
osc: update font subpixel mode, and window opaque compositor hint, on alpha changes
When background alpha is changed at runtime (using OSC-11), we (may)
have to update the opaque hint we send to the compositor.
We must also update the subpixel mode used when rendering font
glyphs.
Why?
When the window is fully opaque, we use wl_surface_set_opaque_region()
on the entire surface, to hint to the compositor that it doesn’t have
to blend the window content with whatever is behind the
window. Obviously, if alpha is changed from opaque, to transparent (or
semi-transparent), that hint must be removed.
Sub-pixel mode is harder to explain, but in short, we can’t do
subpixel hinting with a (semi-)transparent background. Thus, similar
to the opaque hint, subpixel antialiasing must be enabled/disabled
when background alpha is changed.
2023-05-25 18:39:32 +02:00
|
|
|
wayl_win_alpha_changed(win);
|
2020-01-03 21:53:38 +01:00
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_add_listener(win->surface.surf, &surface_listener, win);
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2023-03-08 10:44:03 +01:00
|
|
|
if (wayl->fractional_scale_manager != NULL && wayl->viewporter != NULL) {
|
2023-06-26 16:10:40 +02:00
|
|
|
win->surface.viewport = wp_viewporter_get_viewport(wayl->viewporter, win->surface.surf);
|
2023-03-08 10:44:03 +01:00
|
|
|
|
|
|
|
|
win->fractional_scale =
|
|
|
|
|
wp_fractional_scale_manager_v1_get_fractional_scale(
|
2023-06-26 16:10:40 +02:00
|
|
|
wayl->fractional_scale_manager, win->surface.surf);
|
2023-03-08 10:44:03 +01:00
|
|
|
wp_fractional_scale_v1_add_listener(
|
|
|
|
|
win->fractional_scale, &fractional_scale_listener, win);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface.surf);
|
2020-01-03 13:41:35 +01:00
|
|
|
xdg_surface_add_listener(win->xdg_surface, &xdg_surface_listener, win);
|
2019-10-27 19:08:48 +01:00
|
|
|
|
|
|
|
|
win->xdg_toplevel = xdg_surface_get_toplevel(win->xdg_surface);
|
2020-01-03 13:46:15 +01:00
|
|
|
xdg_toplevel_add_listener(win->xdg_toplevel, &xdg_toplevel_listener, win);
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2020-04-01 18:40:51 +02:00
|
|
|
xdg_toplevel_set_app_id(win->xdg_toplevel, conf->app_id);
|
2019-10-27 19:08:48 +01:00
|
|
|
|
wayland: set toplevel icon
If the xdg-toplevel-icon-v1 protocol is available, and we have the
corresponding manager global, set the toplevel icon to "foot".
Note: we do *not* provide any pixel data. This is by design; we want
to keep things simple.
To be able to provide pixel data, we would have to either:
* embed the raw pixel data in the foot binary
* link against either libpng or/and e.g. nanosvg, locate, at run-time,
the paths to our own icons, and load them at run-time.
* link against either libpng or/and e.g. nanosvg, and, at run-time, do
a full icon lookup. This would also require us to add a config option
for which icon theme to use.
Of the two, I would prefer the first option. But, let's skip this
completely for now.
By providing the icon as a name, the compositor will have to lookup
the icon itself. Compositors supporting icons is likely to already
support this.
So what do we gain by implementing this protocol? Compositors no
longer has to parse .desktop files and map our app-id to find the icon
to use.
There's one question remaining. With this patch, the icon name is
hardcoded to "foot", just like our .desktop files. But, perhaps we
should use the app-id instead? And if so, should we also change the
icon when the app-id changes?
My gut feeling is, yes, we should use the app-id instead, and yes, we
should update the icon when the app-id is changed at run-time.
2024-09-08 13:15:21 +02:00
|
|
|
if (wayl->toplevel_icon_manager != NULL) {
|
2024-09-08 18:25:07 +02:00
|
|
|
const char *app_id =
|
|
|
|
|
term->app_id != NULL ? term->app_id : term->conf->app_id;
|
|
|
|
|
|
wayland: set toplevel icon
If the xdg-toplevel-icon-v1 protocol is available, and we have the
corresponding manager global, set the toplevel icon to "foot".
Note: we do *not* provide any pixel data. This is by design; we want
to keep things simple.
To be able to provide pixel data, we would have to either:
* embed the raw pixel data in the foot binary
* link against either libpng or/and e.g. nanosvg, locate, at run-time,
the paths to our own icons, and load them at run-time.
* link against either libpng or/and e.g. nanosvg, and, at run-time, do
a full icon lookup. This would also require us to add a config option
for which icon theme to use.
Of the two, I would prefer the first option. But, let's skip this
completely for now.
By providing the icon as a name, the compositor will have to lookup
the icon itself. Compositors supporting icons is likely to already
support this.
So what do we gain by implementing this protocol? Compositors no
longer has to parse .desktop files and map our app-id to find the icon
to use.
There's one question remaining. With this patch, the icon name is
hardcoded to "foot", just like our .desktop files. But, perhaps we
should use the app-id instead? And if so, should we also change the
icon when the app-id changes?
My gut feeling is, yes, we should use the app-id instead, and yes, we
should update the icon when the app-id is changed at run-time.
2024-09-08 13:15:21 +02:00
|
|
|
struct xdg_toplevel_icon_v1 *icon =
|
|
|
|
|
xdg_toplevel_icon_manager_v1_create_icon(wayl->toplevel_icon_manager);
|
2024-09-08 18:25:07 +02:00
|
|
|
xdg_toplevel_icon_v1_set_name(icon, streq(
|
|
|
|
|
app_id, "footclient") ? "foot" : app_id);
|
wayland: set toplevel icon
If the xdg-toplevel-icon-v1 protocol is available, and we have the
corresponding manager global, set the toplevel icon to "foot".
Note: we do *not* provide any pixel data. This is by design; we want
to keep things simple.
To be able to provide pixel data, we would have to either:
* embed the raw pixel data in the foot binary
* link against either libpng or/and e.g. nanosvg, locate, at run-time,
the paths to our own icons, and load them at run-time.
* link against either libpng or/and e.g. nanosvg, and, at run-time, do
a full icon lookup. This would also require us to add a config option
for which icon theme to use.
Of the two, I would prefer the first option. But, let's skip this
completely for now.
By providing the icon as a name, the compositor will have to lookup
the icon itself. Compositors supporting icons is likely to already
support this.
So what do we gain by implementing this protocol? Compositors no
longer has to parse .desktop files and map our app-id to find the icon
to use.
There's one question remaining. With this patch, the icon name is
hardcoded to "foot", just like our .desktop files. But, perhaps we
should use the app-id instead? And if so, should we also change the
icon when the app-id changes?
My gut feeling is, yes, we should use the app-id instead, and yes, we
should update the icon when the app-id is changed at run-time.
2024-09-08 13:15:21 +02:00
|
|
|
xdg_toplevel_icon_manager_v1_set_icon(
|
|
|
|
|
wayl->toplevel_icon_manager, win->xdg_toplevel, icon);
|
|
|
|
|
xdg_toplevel_icon_v1_destroy(icon);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-01 08:09:08 +02:00
|
|
|
if (term->conf->gamma_correct) {
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
if (wayl->color_management.img_description != NULL) {
|
|
|
|
|
xassert(wayl->color_management.manager != NULL);
|
|
|
|
|
|
|
|
|
|
win->surface.color_management = wp_color_manager_v1_get_surface(
|
|
|
|
|
term->wl->color_management.manager, win->surface.surf);
|
|
|
|
|
|
|
|
|
|
wp_color_management_surface_v1_set_image_description(
|
|
|
|
|
win->surface.color_management, wayl->color_management.img_description,
|
|
|
|
|
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
|
2025-05-01 08:09:08 +02:00
|
|
|
} else {
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
if (wayl->color_management.manager == NULL) {
|
|
|
|
|
LOG_WARN(
|
|
|
|
|
"gamma-corrected-blending: disabling; "
|
|
|
|
|
"compositor does not implement the color-management protocol");
|
|
|
|
|
} else {
|
|
|
|
|
LOG_WARN(
|
|
|
|
|
"gamma-corrected-blending: disabling; "
|
|
|
|
|
"compositor does not implement all required color-management features");
|
|
|
|
|
LOG_WARN("use e.g. 'wayland-info' and verify the compositor implements:");
|
|
|
|
|
LOG_WARN(" - feature: parametric");
|
|
|
|
|
LOG_WARN(" - render intent: perceptual");
|
|
|
|
|
LOG_WARN(" - TF: ext_linear");
|
|
|
|
|
LOG_WARN(" - primaries: sRGB");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-11 18:47:26 +02:00
|
|
|
if (conf->csd.preferred == CONF_CSD_PREFER_NONE) {
|
|
|
|
|
/* User specifically do *not* want decorations */
|
2021-06-22 18:58:38 +02:00
|
|
|
win->csd_mode = CSD_NO;
|
2020-10-11 18:47:26 +02:00
|
|
|
LOG_INFO("window decorations disabled by user");
|
|
|
|
|
} else if (wayl->xdg_decoration_manager != NULL) {
|
2020-02-25 19:55:50 +01:00
|
|
|
win->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
|
|
|
|
wayl->xdg_decoration_manager, win->xdg_toplevel);
|
2020-03-02 18:42:49 +01:00
|
|
|
|
2020-03-06 19:18:59 +01:00
|
|
|
LOG_INFO("requesting %s decorations",
|
2020-03-02 18:42:49 +01:00
|
|
|
conf->csd.preferred == CONF_CSD_PREFER_SERVER ? "SSD" : "CSD");
|
|
|
|
|
|
2020-02-25 19:55:50 +01:00
|
|
|
zxdg_toplevel_decoration_v1_set_mode(
|
2020-03-02 18:42:49 +01:00
|
|
|
win->xdg_toplevel_decoration,
|
|
|
|
|
(conf->csd.preferred == CONF_CSD_PREFER_SERVER
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
|
|
|
|
|
: ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE));
|
2020-03-02 18:42:49 +01:00
|
|
|
|
2020-02-25 19:55:50 +01:00
|
|
|
zxdg_toplevel_decoration_v1_add_listener(
|
|
|
|
|
win->xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, win);
|
wayland: instantiate sub-surfaces on-demand
While most compositors handle instantiated but not-yet-mapped
sub-surfaces correctly, e.g. kwin does not.
For example, it will incorrectly offset the main surface both
horizontally and vertically with a couple of pixels, leaving two
transparent areas at the top and left, between the SSDs and the main
surface.
Note that a workaround is to position the sub-surfaces inside the main
surface while they're unmapped. However, since the surfaces may be
larger than the main surface (the CSDs, for examples, always are),
this doesn't quite work since kwin, at least, resizes the window to
include the sub-surfaces, even when unmapped.
So, instead we instantiate all sub-surfaces on demand, when we know
where to position them, and when they should be mapped.
2020-02-26 12:22:16 +01:00
|
|
|
} else {
|
|
|
|
|
/* No decoration manager - thus we *must* draw our own decorations */
|
2021-06-24 23:02:40 +02:00
|
|
|
win->configure.csd_mode = CSD_YES;
|
2020-02-28 18:37:07 +01:00
|
|
|
LOG_WARN("no decoration manager available - using CSDs unconditionally");
|
2020-02-25 19:55:50 +01:00
|
|
|
}
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_commit(win->surface.surf);
|
2020-08-13 18:35:17 +02:00
|
|
|
|
2021-10-28 17:51:44 -07:00
|
|
|
/* Complete XDG startup notification */
|
2024-07-23 06:58:37 +02:00
|
|
|
wayl_activate(wayl, win, token);
|
2021-10-28 17:51:44 -07:00
|
|
|
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
Search mode and ‘flash’ (OSC-555) both achieves similar visual
effects: flash tints the entire window yellow, and search mode dims
it (except the search match).
But, they do so in completely different ways. Search mode is detected
in render_cell(), and the colors are then dimmed there.
Flash is implemented by blending a yellow, semi-transparent color on
top of the rendered grid.
This patch replaces those two implementations with a single one. We
add a new sub-surface, called the ‘overlay’. In normal mode, it’s
unmapped.
When either search mode, or flash, is enabled, we enable it, and
fill it with a semi-transparent color. Yellow for ‘flash’, and
“black” (i.e. no color) for search mode.
The compositor then blends it with the grid. Hopefully on the GPU,
meaning it’ll be faster than if we blend in software.
There are more performance benefits however. By using a separate
surface, we can do much better damage tracking.
The normal grid rendering code no longer have to care about neither
search mode, nor flash. Thus, we get rid of a couple of ‘if’
statements in render_cell(), which is nice. But more importantly, we
can drop full grid repaints in a couple of circumstances:
* Entering/exiting search mode
* Every frame while flash is active
Now, when rendering the search mode overlay, we do want to do some
damage tracking, also of the overlay.
This, since search mode doesn’t dim the *entire* window. The search
match is *not* dimmed. This is implemented by punching a hole in the
overlay sub-surface. That is, we make part of it *fully*
transparent. The basic idea is to set a clip region that excludes the
search match, and then dim the rest of the overlay.
It’s slightly more complicated than that however, if we want to reuse
the last frame’s overlay buffer (i.e we don’t want to re-render
the *entire* overlay every frame).
In short, we need to:
* Clear (punch hole) in areas that are part of this frame’s search
match, but not the last frame’s (since those parts are _already_
cleared).
* Dim the areas that were part of the last frame’s search match, but
aren’t anymore (the rest of the overlay should already be dimmed).
To do this, we save the last frame’s “holes” (as a pixman
region). Then, when rendering the next frame, we first calculate the
new frame’s “holes” region.
The region to clear is “this frame’s holes minus last frame’s holes”
The region to dim is “last frame’s holes minus this frames holes”.
Finally, we compute the bounding box of all modified cells by taking
the union of the two diff regions mentioned above. This allows us to
limit the buffer damage sent to the compositor.
2022-04-16 17:49:46 +02:00
|
|
|
if (!wayl_win_subsurface_new(win, &win->overlay, false)) {
|
|
|
|
|
LOG_ERR("failed to create overlay surface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-13 12:08:20 +01:00
|
|
|
switch (conf->tweak.render_timer) {
|
|
|
|
|
case RENDER_TIMER_OSD:
|
|
|
|
|
case RENDER_TIMER_BOTH:
|
2022-04-16 17:41:14 +02:00
|
|
|
if (!wayl_win_subsurface_new(win, &win->render_timer, false)) {
|
2020-08-13 18:35:17 +02:00
|
|
|
LOG_ERR("failed to create render timer surface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2022-01-13 12:08:20 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case RENDER_TIMER_NONE:
|
|
|
|
|
case RENDER_TIMER_LOG:
|
|
|
|
|
break;
|
2020-08-13 18:35:17 +02:00
|
|
|
}
|
2022-01-13 12:08:20 +01:00
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
return win;
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
if (win != NULL)
|
|
|
|
|
wayl_win_destroy(win);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 16:01:03 +01:00
|
|
|
void
|
|
|
|
|
wayl_win_destroy(struct wl_window *win)
|
|
|
|
|
{
|
2019-10-30 20:25:16 +01:00
|
|
|
if (win == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-07-11 09:54:04 +02:00
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
2020-02-29 12:52:55 +01:00
|
|
|
if (win->csd.move_timeout_fd != -1)
|
|
|
|
|
close(win->csd.move_timeout_fd);
|
|
|
|
|
|
2019-11-01 20:19:53 +01:00
|
|
|
/*
|
|
|
|
|
* First, unmap all surfaces to trigger things like
|
|
|
|
|
* keyboard_leave() and wl_pointer_leave().
|
|
|
|
|
*
|
|
|
|
|
* This ensures we remove all references to *this* window from the
|
|
|
|
|
* global wayland struct (since it no longer has neither keyboard
|
|
|
|
|
* nor mouse focus).
|
|
|
|
|
*/
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
if (win->render_timer.surface.surf != NULL) {
|
|
|
|
|
wl_surface_attach(win->render_timer.surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->render_timer.surface.surf);
|
2020-08-13 18:35:17 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
if (win->scrollback_indicator.surface.surf != NULL) {
|
|
|
|
|
wl_surface_attach(win->scrollback_indicator.surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->scrollback_indicator.surface.surf);
|
2020-07-26 10:00:50 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-01 20:19:53 +01:00
|
|
|
/* Scrollback search */
|
2023-06-26 16:10:40 +02:00
|
|
|
if (win->search.surface.surf != NULL) {
|
|
|
|
|
wl_surface_attach(win->search.surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->search.surface.surf);
|
wayland: instantiate sub-surfaces on-demand
While most compositors handle instantiated but not-yet-mapped
sub-surfaces correctly, e.g. kwin does not.
For example, it will incorrectly offset the main surface both
horizontally and vertically with a couple of pixels, leaving two
transparent areas at the top and left, between the SSDs and the main
surface.
Note that a workaround is to position the sub-surfaces inside the main
surface while they're unmapped. However, since the surfaces may be
larger than the main surface (the CSDs, for examples, always are),
this doesn't quite work since kwin, at least, resizes the window to
include the sub-surfaces, even when unmapped.
So, instead we instantiate all sub-surfaces on demand, when we know
where to position them, and when they should be mapped.
2020-02-26 12:22:16 +01:00
|
|
|
}
|
2020-02-23 14:17:48 +01:00
|
|
|
|
2021-07-15 18:45:25 +02:00
|
|
|
/* URLs */
|
|
|
|
|
tll_foreach(win->urls, it) {
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(it->item.surf.surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(it->item.surf.surface.surf);
|
2021-07-15 18:45:25 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-23 14:17:48 +01:00
|
|
|
/* CSD */
|
wayland: instantiate sub-surfaces on-demand
While most compositors handle instantiated but not-yet-mapped
sub-surfaces correctly, e.g. kwin does not.
For example, it will incorrectly offset the main surface both
horizontally and vertically with a couple of pixels, leaving two
transparent areas at the top and left, between the SSDs and the main
surface.
Note that a workaround is to position the sub-surfaces inside the main
surface while they're unmapped. However, since the surfaces may be
larger than the main surface (the CSDs, for examples, always are),
this doesn't quite work since kwin, at least, resizes the window to
include the sub-surfaces, even when unmapped.
So, instead we instantiate all sub-surfaces on demand, when we know
where to position them, and when they should be mapped.
2020-02-26 12:22:16 +01:00
|
|
|
for (size_t i = 0; i < ALEN(win->csd.surface); i++) {
|
2023-06-26 16:10:40 +02:00
|
|
|
if (win->csd.surface[i].surface.surf != NULL) {
|
|
|
|
|
wl_surface_attach(win->csd.surface[i].surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->csd.surface[i].surface.surf);
|
wayland: instantiate sub-surfaces on-demand
While most compositors handle instantiated but not-yet-mapped
sub-surfaces correctly, e.g. kwin does not.
For example, it will incorrectly offset the main surface both
horizontally and vertically with a couple of pixels, leaving two
transparent areas at the top and left, between the SSDs and the main
surface.
Note that a workaround is to position the sub-surfaces inside the main
surface while they're unmapped. However, since the surfaces may be
larger than the main surface (the CSDs, for examples, always are),
this doesn't quite work since kwin, at least, resizes the window to
include the sub-surfaces, even when unmapped.
So, instead we instantiate all sub-surfaces on demand, when we know
where to position them, and when they should be mapped.
2020-02-26 12:22:16 +01:00
|
|
|
}
|
2020-02-23 14:17:48 +01:00
|
|
|
}
|
|
|
|
|
|
2020-01-03 21:02:12 +01:00
|
|
|
wayl_roundtrip(win->term->wl);
|
2019-11-01 20:19:53 +01:00
|
|
|
|
2020-02-23 14:17:48 +01:00
|
|
|
/* Main window */
|
2022-12-29 11:32:21 +01:00
|
|
|
win->unmapped = true;
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_attach(win->surface.surf, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->surface.surf);
|
2020-01-03 21:02:12 +01:00
|
|
|
wayl_roundtrip(win->term->wl);
|
2019-11-01 20:19:53 +01:00
|
|
|
|
2019-10-27 16:01:03 +01:00
|
|
|
tll_free(win->on_outputs);
|
2020-02-23 14:17:48 +01:00
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
tll_foreach(win->urls, it) {
|
2021-02-12 11:31:31 +01:00
|
|
|
wayl_win_subsurface_destroy(&it->item.surf);
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_remove(win->urls, it);
|
2021-01-31 11:12:07 +01:00
|
|
|
}
|
|
|
|
|
|
wayland: instantiate sub-surfaces on-demand
While most compositors handle instantiated but not-yet-mapped
sub-surfaces correctly, e.g. kwin does not.
For example, it will incorrectly offset the main surface both
horizontally and vertically with a couple of pixels, leaving two
transparent areas at the top and left, between the SSDs and the main
surface.
Note that a workaround is to position the sub-surfaces inside the main
surface while they're unmapped. However, since the surfaces may be
larger than the main surface (the CSDs, for examples, always are),
this doesn't quite work since kwin, at least, resizes the window to
include the sub-surfaces, even when unmapped.
So, instead we instantiate all sub-surfaces on demand, when we know
where to position them, and when they should be mapped.
2020-02-26 12:22:16 +01:00
|
|
|
csd_destroy(win);
|
2021-02-12 12:00:40 +01:00
|
|
|
wayl_win_subsurface_destroy(&win->search);
|
|
|
|
|
wayl_win_subsurface_destroy(&win->scrollback_indicator);
|
|
|
|
|
wayl_win_subsurface_destroy(&win->render_timer);
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
Search mode and ‘flash’ (OSC-555) both achieves similar visual
effects: flash tints the entire window yellow, and search mode dims
it (except the search match).
But, they do so in completely different ways. Search mode is detected
in render_cell(), and the colors are then dimmed there.
Flash is implemented by blending a yellow, semi-transparent color on
top of the rendered grid.
This patch replaces those two implementations with a single one. We
add a new sub-surface, called the ‘overlay’. In normal mode, it’s
unmapped.
When either search mode, or flash, is enabled, we enable it, and
fill it with a semi-transparent color. Yellow for ‘flash’, and
“black” (i.e. no color) for search mode.
The compositor then blends it with the grid. Hopefully on the GPU,
meaning it’ll be faster than if we blend in software.
There are more performance benefits however. By using a separate
surface, we can do much better damage tracking.
The normal grid rendering code no longer have to care about neither
search mode, nor flash. Thus, we get rid of a couple of ‘if’
statements in render_cell(), which is nice. But more importantly, we
can drop full grid repaints in a couple of circumstances:
* Entering/exiting search mode
* Every frame while flash is active
Now, when rendering the search mode overlay, we do want to do some
damage tracking, also of the overlay.
This, since search mode doesn’t dim the *entire* window. The search
match is *not* dimmed. This is implemented by punching a hole in the
overlay sub-surface. That is, we make part of it *fully*
transparent. The basic idea is to set a clip region that excludes the
search match, and then dim the rest of the overlay.
It’s slightly more complicated than that however, if we want to reuse
the last frame’s overlay buffer (i.e we don’t want to re-render
the *entire* overlay every frame).
In short, we need to:
* Clear (punch hole) in areas that are part of this frame’s search
match, but not the last frame’s (since those parts are _already_
cleared).
* Dim the areas that were part of the last frame’s search match, but
aren’t anymore (the rest of the overlay should already be dimmed).
To do this, we save the last frame’s “holes” (as a pixman
region). Then, when rendering the next frame, we first calculate the
new frame’s “holes” region.
The region to clear is “this frame’s holes minus last frame’s holes”
The region to dim is “last frame’s holes minus this frames holes”.
Finally, we compute the bounding box of all modified cells by taking
the union of the two diff regions mentioned above. This allows us to
limit the buffer damage sent to the compositor.
2022-04-16 17:49:46 +02:00
|
|
|
wayl_win_subsurface_destroy(&win->overlay);
|
2021-02-12 11:47:49 +01:00
|
|
|
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
shm_purge(term->render.chains.search);
|
|
|
|
|
shm_purge(term->render.chains.scrollback_indicator);
|
|
|
|
|
shm_purge(term->render.chains.render_timer);
|
|
|
|
|
shm_purge(term->render.chains.grid);
|
2021-07-18 16:46:43 +02:00
|
|
|
shm_purge(term->render.chains.url);
|
|
|
|
|
shm_purge(term->render.chains.csd);
|
2021-07-11 10:01:22 +02:00
|
|
|
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
tll_foreach(win->xdg_tokens, it) {
|
|
|
|
|
xdg_activation_token_v1_destroy(it->item->xdg_token);
|
|
|
|
|
free(it->item);
|
|
|
|
|
|
|
|
|
|
tll_remove(win->xdg_tokens, it);
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (win->surface.color_management != NULL)
|
|
|
|
|
wp_color_management_surface_v1_destroy(win->surface.color_management);
|
2023-03-08 10:44:03 +01:00
|
|
|
if (win->fractional_scale != NULL)
|
|
|
|
|
wp_fractional_scale_v1_destroy(win->fractional_scale);
|
2023-06-26 16:10:40 +02:00
|
|
|
if (win->surface.viewport != NULL)
|
|
|
|
|
wp_viewport_destroy(win->surface.viewport);
|
2019-10-27 16:01:03 +01:00
|
|
|
if (win->frame_callback != NULL)
|
|
|
|
|
wl_callback_destroy(win->frame_callback);
|
|
|
|
|
if (win->xdg_toplevel_decoration != NULL)
|
|
|
|
|
zxdg_toplevel_decoration_v1_destroy(win->xdg_toplevel_decoration);
|
|
|
|
|
if (win->xdg_toplevel != NULL)
|
|
|
|
|
xdg_toplevel_destroy(win->xdg_toplevel);
|
|
|
|
|
if (win->xdg_surface != NULL)
|
|
|
|
|
xdg_surface_destroy(win->xdg_surface);
|
2023-06-26 16:10:40 +02:00
|
|
|
if (win->surface.surf != NULL)
|
|
|
|
|
wl_surface_destroy(win->surface.surf);
|
2019-11-01 20:19:53 +01:00
|
|
|
|
2020-01-03 21:02:12 +01:00
|
|
|
wayl_roundtrip(win->term->wl);
|
2021-01-17 16:12:54 +01:00
|
|
|
|
|
|
|
|
if (win->resize_timeout_fd >= 0)
|
|
|
|
|
fdm_del(win->term->wl->fdm, win->resize_timeout_fd);
|
2019-10-27 19:16:42 +01:00
|
|
|
free(win);
|
2019-10-27 16:01:03 +01:00
|
|
|
}
|
2019-10-27 18:43:07 +01:00
|
|
|
|
2020-07-08 19:52:17 +02:00
|
|
|
bool
|
2023-06-22 14:23:53 +02:00
|
|
|
wayl_reload_xcursor_theme(struct seat *seat, float new_scale)
|
2019-11-28 19:35:47 +01:00
|
|
|
{
|
2020-07-08 19:52:17 +02:00
|
|
|
if (seat->pointer.theme != NULL && seat->pointer.scale == new_scale) {
|
|
|
|
|
/* We already have a theme loaded, and the scale hasn't changed */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
if (seat->pointer.theme != NULL) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(seat->pointer.scale != new_scale);
|
2020-07-08 18:08:39 +02:00
|
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
|
|
|
|
seat->pointer.theme = NULL;
|
|
|
|
|
seat->pointer.cursor = NULL;
|
2019-11-28 19:35:47 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-16 15:30:29 +01:00
|
|
|
if (seat->pointer.shape_device != NULL) {
|
|
|
|
|
/* Using server side cursors */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-10 12:06:55 +02:00
|
|
|
int xcursor_size = 24;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const char *env_cursor_size = getenv("XCURSOR_SIZE");
|
|
|
|
|
if (env_cursor_size != NULL) {
|
2022-01-29 17:31:00 +05:30
|
|
|
errno = 0;
|
|
|
|
|
char *end;
|
|
|
|
|
int size = (int)strtol(env_cursor_size, &end, 10);
|
|
|
|
|
|
|
|
|
|
if (errno == 0 && *end == '\0' && size > 0)
|
2020-07-10 12:06:55 +02:00
|
|
|
xcursor_size = size;
|
2022-01-29 17:31:00 +05:30
|
|
|
else
|
|
|
|
|
LOG_WARN("XCURSOR_SIZE '%s' is invalid, defaulting to 24",
|
|
|
|
|
env_cursor_size);
|
2020-07-10 12:06:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-29 16:18:19 +05:30
|
|
|
const char *xcursor_theme = getenv("XCURSOR_THEME");
|
|
|
|
|
|
2023-06-22 14:23:53 +02:00
|
|
|
LOG_INFO("cursor theme: %s, size: %d, scale: %.2f",
|
2023-01-06 23:43:51 +00:00
|
|
|
xcursor_theme ? xcursor_theme : "(null)",
|
|
|
|
|
xcursor_size, new_scale);
|
2019-11-28 19:35:47 +01:00
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
seat->pointer.theme = wl_cursor_theme_load(
|
2020-07-10 12:06:55 +02:00
|
|
|
xcursor_theme, xcursor_size * new_scale, seat->wayl->shm);
|
2019-11-28 19:35:47 +01:00
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
if (seat->pointer.theme == NULL) {
|
2019-11-28 19:35:47 +01:00
|
|
|
LOG_ERR("failed to load cursor theme");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 19:52:17 +02:00
|
|
|
seat->pointer.scale = new_scale;
|
|
|
|
|
return true;
|
2019-11-28 19:35:47 +01:00
|
|
|
}
|
2019-10-27 19:36:45 +01:00
|
|
|
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
void
|
|
|
|
|
wayl_flush(struct wayland *wayl)
|
|
|
|
|
{
|
|
|
|
|
while (true) {
|
|
|
|
|
int r = wl_display_flush(wayl->display);
|
2020-01-04 23:36:32 +01:00
|
|
|
if (r >= 0) {
|
|
|
|
|
/* Most likely code path - the flush succeed */
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
return;
|
2020-01-04 23:36:32 +01:00
|
|
|
}
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
|
2020-01-04 23:36:32 +01:00
|
|
|
if (errno == EINTR) {
|
|
|
|
|
/* Unlikely */
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
continue;
|
2020-01-04 23:36:32 +01:00
|
|
|
}
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
|
|
|
|
|
if (errno != EAGAIN) {
|
2025-05-02 13:46:18 +02:00
|
|
|
const int saved_errno = errno;
|
|
|
|
|
|
|
|
|
|
if (errno == EPIPE) {
|
|
|
|
|
wl_display_read_events(wayl->display);
|
|
|
|
|
wl_display_dispatch_pending(wayl->display);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO_P(saved_errno, "failed to flush wayland socket");
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 23:36:32 +01:00
|
|
|
/* Socket buffer is full - need to wait for it to become
|
|
|
|
|
writeable again */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(errno == EAGAIN);
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
|
|
|
|
|
while (true) {
|
2020-03-15 13:36:35 +01:00
|
|
|
struct pollfd fds[] = {{.fd = wayl->fd, .events = POLLOUT}};
|
2020-01-04 23:36:32 +01:00
|
|
|
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
|
|
|
|
|
|
|
|
|
if (r < 0) {
|
|
|
|
|
if (errno == EINTR)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO("failed to poll");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fds[0].revents & POLLHUP)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(fds[0].revents & POLLOUT);
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-03 21:02:12 +01:00
|
|
|
void
|
|
|
|
|
wayl_roundtrip(struct wayland *wayl)
|
|
|
|
|
{
|
|
|
|
|
wl_display_cancel_read(wayl->display);
|
2021-05-24 20:42:55 +02:00
|
|
|
if (wl_display_roundtrip(wayl->display) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to roundtrip Wayland display");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-01-03 21:02:12 +01:00
|
|
|
|
2020-01-04 23:33:50 +01:00
|
|
|
/* I suspect the roundtrip above clears the pending queue, and
|
|
|
|
|
* that prepare_read() will always succeed in the first call. But,
|
|
|
|
|
* better safe than sorry... */
|
|
|
|
|
|
2021-05-24 20:42:55 +02:00
|
|
|
while (wl_display_prepare_read(wayl->display) != 0) {
|
|
|
|
|
if (wl_display_dispatch_pending(wayl->display) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to dispatch pending Wayland events");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
wayland: wl_display_flush() never blocks
Since it doesn't block, we need to detect EAGAIN failures and ensure
we actually flush everything.
If we don't, we sooner or later end up in a wayland client library
call that aborts due to the socket buffer being full.
Ideally, we'd simply enable POLLOUT in the FDM. However, we cannot
write *anything* to the wayland socket until we've actually managed to
send everything. This means enabling POLLOUT in the FDM wont work
since we may (*will*) end up trying to write more data to it before
we've flushed it.
So, add a wrapper function, wayl_flush(), that acts as a blocking
variant of wl_display_flush(), by detecting EAGAIN failiures and
calling poll() itself, on the wayland socket only, until all data has
been sent.
2020-01-04 21:10:08 +01:00
|
|
|
wayl_flush(wayl);
|
2020-01-03 21:02:12 +01:00
|
|
|
}
|
2021-02-12 11:29:36 +01:00
|
|
|
|
2023-07-29 08:18:00 +02:00
|
|
|
static void
|
|
|
|
|
surface_scale_explicit_width_height(
|
2023-06-26 21:06:47 +02:00
|
|
|
const struct wl_window *win, const struct wayl_surface *surf,
|
2023-07-29 08:18:00 +02:00
|
|
|
int width, int height, float scale, bool verify)
|
2023-06-26 15:51:04 +02:00
|
|
|
{
|
2023-07-25 15:56:30 +02:00
|
|
|
if (term_fractional_scaling(win->term)) {
|
2023-06-26 21:09:18 +02:00
|
|
|
LOG_DBG("scaling by a factor of %.2f using fractional scaling "
|
|
|
|
|
"(width=%d, height=%d) ", scale, width, height);
|
|
|
|
|
|
2023-07-29 08:18:00 +02:00
|
|
|
if (verify) {
|
|
|
|
|
if ((int)roundf(scale * (int)roundf(width / scale)) != width) {
|
|
|
|
|
BUG("width=%d is not valid with scaling factor %.2f (%d != %d)",
|
|
|
|
|
width, scale,
|
|
|
|
|
(int)roundf(scale * (int)roundf(width / scale)),
|
|
|
|
|
width);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((int)roundf(scale * (int)roundf(height / scale)) != height) {
|
|
|
|
|
BUG("height=%d is not valid with scaling factor %.2f (%d != %d)",
|
|
|
|
|
height, scale,
|
|
|
|
|
(int)roundf(scale * (int)roundf(height / scale)),
|
|
|
|
|
height);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-28 15:28:10 +02:00
|
|
|
|
2024-05-21 16:09:34 +02:00
|
|
|
xassert(surf->viewport != NULL);
|
2023-06-26 21:09:30 +02:00
|
|
|
wl_surface_set_buffer_scale(surf->surf, 1);
|
2023-06-26 17:32:01 +02:00
|
|
|
wp_viewport_set_destination(
|
2023-07-28 15:28:10 +02:00
|
|
|
surf->viewport, roundf(width / scale), roundf(height / scale));
|
2023-06-26 15:51:04 +02:00
|
|
|
} else {
|
2024-01-24 19:59:30 +01:00
|
|
|
const char *mode UNUSED = term_preferred_buffer_scale(win->term)
|
2024-01-09 22:51:17 -06:00
|
|
|
? "wl_surface.preferred_buffer_scale"
|
|
|
|
|
: "legacy mode";
|
|
|
|
|
LOG_DBG("scaling by a factor of %.2f using %s "
|
|
|
|
|
"(width=%d, height=%d)" , scale, mode, width, height);
|
2023-06-26 20:25:16 +02:00
|
|
|
|
2023-07-25 15:56:30 +02:00
|
|
|
xassert(scale == floorf(scale));
|
|
|
|
|
const int iscale = (int)floorf(scale);
|
2024-01-09 16:47:41 +01:00
|
|
|
|
|
|
|
|
if (verify) {
|
|
|
|
|
if (width % iscale != 0) {
|
|
|
|
|
BUG("width=%d is not valid with scaling factor %.2f (%d %% %d != 0)",
|
|
|
|
|
width, scale, width, iscale);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (height % iscale != 0) {
|
|
|
|
|
BUG("height=%d is not valid with scaling factor %.2f (%d %% %d != 0)",
|
|
|
|
|
height, scale, height, iscale);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-26 20:25:16 +02:00
|
|
|
|
2023-06-26 21:06:47 +02:00
|
|
|
wl_surface_set_buffer_scale(surf->surf, iscale);
|
2023-06-26 15:51:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-29 08:18:00 +02:00
|
|
|
void
|
|
|
|
|
wayl_surface_scale_explicit_width_height(
|
|
|
|
|
const struct wl_window *win, const struct wayl_surface *surf,
|
|
|
|
|
int width, int height, float scale)
|
|
|
|
|
{
|
|
|
|
|
surface_scale_explicit_width_height(win, surf, width, height, scale, false);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 15:51:04 +02:00
|
|
|
void
|
2023-06-26 21:06:47 +02:00
|
|
|
wayl_surface_scale(const struct wl_window *win, const struct wayl_surface *surf,
|
2023-06-26 17:05:16 +02:00
|
|
|
const struct buffer *buf, float scale)
|
|
|
|
|
{
|
2023-07-29 08:18:00 +02:00
|
|
|
surface_scale_explicit_width_height(
|
|
|
|
|
win, surf, buf->width, buf->height, scale, true);
|
2023-06-26 17:05:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
wayl_win_scale(struct wl_window *win, const struct buffer *buf)
|
2023-06-26 15:51:04 +02:00
|
|
|
{
|
|
|
|
|
const struct terminal *term = win->term;
|
|
|
|
|
const float scale = term->scale;
|
|
|
|
|
|
2023-06-26 21:06:47 +02:00
|
|
|
wayl_surface_scale(win, &win->surface, buf, scale);
|
2023-06-26 15:51:04 +02:00
|
|
|
}
|
|
|
|
|
|
osc: update font subpixel mode, and window opaque compositor hint, on alpha changes
When background alpha is changed at runtime (using OSC-11), we (may)
have to update the opaque hint we send to the compositor.
We must also update the subpixel mode used when rendering font
glyphs.
Why?
When the window is fully opaque, we use wl_surface_set_opaque_region()
on the entire surface, to hint to the compositor that it doesn’t have
to blend the window content with whatever is behind the
window. Obviously, if alpha is changed from opaque, to transparent (or
semi-transparent), that hint must be removed.
Sub-pixel mode is harder to explain, but in short, we can’t do
subpixel hinting with a (semi-)transparent background. Thus, similar
to the opaque hint, subpixel antialiasing must be enabled/disabled
when background alpha is changed.
2023-05-25 18:39:32 +02:00
|
|
|
void
|
|
|
|
|
wayl_win_alpha_changed(struct wl_window *win)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = win->term;
|
|
|
|
|
|
|
|
|
|
if (term->colors.alpha == 0xffff) {
|
|
|
|
|
struct wl_region *region = wl_compositor_create_region(
|
|
|
|
|
term->wl->compositor);
|
|
|
|
|
|
|
|
|
|
if (region != NULL) {
|
|
|
|
|
wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_set_opaque_region(win->surface.surf, region);
|
osc: update font subpixel mode, and window opaque compositor hint, on alpha changes
When background alpha is changed at runtime (using OSC-11), we (may)
have to update the opaque hint we send to the compositor.
We must also update the subpixel mode used when rendering font
glyphs.
Why?
When the window is fully opaque, we use wl_surface_set_opaque_region()
on the entire surface, to hint to the compositor that it doesn’t have
to blend the window content with whatever is behind the
window. Obviously, if alpha is changed from opaque, to transparent (or
semi-transparent), that hint must be removed.
Sub-pixel mode is harder to explain, but in short, we can’t do
subpixel hinting with a (semi-)transparent background. Thus, similar
to the opaque hint, subpixel antialiasing must be enabled/disabled
when background alpha is changed.
2023-05-25 18:39:32 +02:00
|
|
|
wl_region_destroy(region);
|
|
|
|
|
}
|
|
|
|
|
} else
|
2023-06-26 16:10:40 +02:00
|
|
|
wl_surface_set_opaque_region(win->surface.surf, NULL);
|
osc: update font subpixel mode, and window opaque compositor hint, on alpha changes
When background alpha is changed at runtime (using OSC-11), we (may)
have to update the opaque hint we send to the compositor.
We must also update the subpixel mode used when rendering font
glyphs.
Why?
When the window is fully opaque, we use wl_surface_set_opaque_region()
on the entire surface, to hint to the compositor that it doesn’t have
to blend the window content with whatever is behind the
window. Obviously, if alpha is changed from opaque, to transparent (or
semi-transparent), that hint must be removed.
Sub-pixel mode is harder to explain, but in short, we can’t do
subpixel hinting with a (semi-)transparent background. Thus, similar
to the opaque hint, subpixel antialiasing must be enabled/disabled
when background alpha is changed.
2023-05-25 18:39:32 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-09 12:13:14 +02:00
|
|
|
static void
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
activation_token_for_urgency_done(const char *token, void *data)
|
2021-05-09 12:13:14 +02:00
|
|
|
{
|
|
|
|
|
struct wl_window *win = data;
|
|
|
|
|
struct wayland *wayl = win->term->wl;
|
|
|
|
|
|
2022-05-11 21:17:52 +02:00
|
|
|
win->urgency_token_is_pending = false;
|
2023-06-26 16:10:40 +02:00
|
|
|
xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface.surf);
|
2021-05-09 12:13:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
wayl_win_set_urgent(struct wl_window *win)
|
|
|
|
|
{
|
2022-05-11 21:17:52 +02:00
|
|
|
if (win->urgency_token_is_pending) {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* We already have a pending token. Don't request another one,
|
2022-05-11 21:17:52 +02:00
|
|
|
* to avoid flooding the Wayland socket */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool success = wayl_get_activation_token(
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
win->term->wl, NULL, 0, win, &activation_token_for_urgency_done, win);
|
2022-05-11 21:17:52 +02:00
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
win->urgency_token_is_pending = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2021-05-09 12:13:14 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-17 10:10:10 +01:00
|
|
|
bool
|
|
|
|
|
wayl_win_ring_bell(const struct wl_window *win)
|
|
|
|
|
{
|
|
|
|
|
if (win->term->wl->system_bell == NULL) {
|
|
|
|
|
static bool have_warned = false;
|
|
|
|
|
|
|
|
|
|
if (!have_warned) {
|
|
|
|
|
LOG_WARN("compositor does not implement the XDG system bell protocol");
|
|
|
|
|
have_warned = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xdg_system_bell_v1_ring(win->term->wl->system_bell, win->surface.surf);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-16 11:26:28 +02:00
|
|
|
bool
|
|
|
|
|
wayl_win_csd_titlebar_visible(const struct wl_window *win)
|
|
|
|
|
{
|
|
|
|
|
return win->csd_mode == CSD_YES &&
|
2022-04-15 20:12:34 +02:00
|
|
|
!win->is_fullscreen &&
|
|
|
|
|
!(win->is_maximized && win->term->conf->csd.hide_when_maximized);
|
2022-04-16 11:26:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
wayl_win_csd_borders_visible(const struct wl_window *win)
|
|
|
|
|
{
|
|
|
|
|
return win->csd_mode == CSD_YES &&
|
|
|
|
|
!win->is_fullscreen &&
|
|
|
|
|
!win->is_maximized;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 11:29:36 +01:00
|
|
|
bool
|
|
|
|
|
wayl_win_subsurface_new_with_custom_parent(
|
|
|
|
|
struct wl_window *win, struct wl_surface *parent,
|
2023-06-26 16:10:40 +02:00
|
|
|
struct wayl_sub_surface *surf, bool allow_pointer_input)
|
2021-02-12 11:29:36 +01:00
|
|
|
{
|
|
|
|
|
struct wayland *wayl = win->term->wl;
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
surf->surface.surf = NULL;
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
surf->surface.viewport = NULL;
|
2021-02-12 11:29:36 +01:00
|
|
|
surf->sub = NULL;
|
|
|
|
|
|
2021-02-25 07:46:21 +01:00
|
|
|
struct wl_surface *main_surface
|
|
|
|
|
= wl_compositor_create_surface(wayl->compositor);
|
|
|
|
|
|
2023-06-26 15:55:40 +02:00
|
|
|
if (main_surface == NULL) {
|
|
|
|
|
LOG_ERR("failed to instantiate surface for sub-surface");
|
2021-02-12 11:29:36 +01:00
|
|
|
return false;
|
2023-06-26 15:55:40 +02:00
|
|
|
}
|
2021-02-12 11:29:36 +01:00
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
surf->surface.color_management = NULL;
|
|
|
|
|
if (win->term->conf->gamma_correct &&
|
|
|
|
|
wayl->color_management.img_description != NULL)
|
|
|
|
|
{
|
|
|
|
|
xassert(wayl->color_management.manager != NULL);
|
|
|
|
|
|
|
|
|
|
surf->surface.color_management = wp_color_manager_v1_get_surface(
|
|
|
|
|
wayl->color_management.manager, main_surface);
|
|
|
|
|
|
|
|
|
|
wp_color_management_surface_v1_set_image_description(
|
|
|
|
|
surf->surface.color_management, wayl->color_management.img_description,
|
|
|
|
|
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 11:29:36 +01:00
|
|
|
struct wl_subsurface *sub = wl_subcompositor_get_subsurface(
|
2021-02-25 07:46:21 +01:00
|
|
|
wayl->sub_compositor, main_surface, parent);
|
2021-02-12 11:29:36 +01:00
|
|
|
|
|
|
|
|
if (sub == NULL) {
|
2023-06-26 15:55:40 +02:00
|
|
|
LOG_ERR("failed to instantiate sub-surface");
|
2021-02-25 07:46:21 +01:00
|
|
|
wl_surface_destroy(main_surface);
|
2021-02-12 11:29:36 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 15:55:40 +02:00
|
|
|
struct wp_viewport *viewport = NULL;
|
2024-05-21 16:09:34 +02:00
|
|
|
if (wayl->viewporter != NULL) {
|
2023-06-26 15:55:40 +02:00
|
|
|
viewport = wp_viewporter_get_viewport(wayl->viewporter, main_surface);
|
|
|
|
|
if (viewport == NULL) {
|
|
|
|
|
LOG_ERR("failed to instantiate viewport for sub-surface");
|
|
|
|
|
wl_subsurface_destroy(sub);
|
|
|
|
|
wl_surface_destroy(main_surface);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 07:46:21 +01:00
|
|
|
wl_surface_set_user_data(main_surface, win);
|
2021-02-12 11:29:36 +01:00
|
|
|
wl_subsurface_set_sync(sub);
|
|
|
|
|
|
2022-04-16 17:41:14 +02:00
|
|
|
/* Disable pointer and touch events */
|
|
|
|
|
if (!allow_pointer_input) {
|
|
|
|
|
struct wl_region *empty =
|
|
|
|
|
wl_compositor_create_region(wayl->compositor);
|
|
|
|
|
wl_surface_set_input_region(main_surface, empty);
|
|
|
|
|
wl_region_destroy(empty);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
surf->surface.surf = main_surface;
|
2021-02-12 11:29:36 +01:00
|
|
|
surf->sub = sub;
|
2023-06-26 16:10:40 +02:00
|
|
|
surf->surface.viewport = viewport;
|
2021-02-12 11:29:36 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2023-06-26 16:10:40 +02:00
|
|
|
wayl_win_subsurface_new(struct wl_window *win, struct wayl_sub_surface *surf,
|
2022-04-16 17:41:14 +02:00
|
|
|
bool allow_pointer_input)
|
2021-02-12 11:29:36 +01:00
|
|
|
{
|
2022-04-16 17:41:14 +02:00
|
|
|
return wayl_win_subsurface_new_with_custom_parent(
|
2023-06-26 16:10:40 +02:00
|
|
|
win, win->surface.surf, surf, allow_pointer_input);
|
2021-02-12 11:29:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2023-06-26 16:10:40 +02:00
|
|
|
wayl_win_subsurface_destroy(struct wayl_sub_surface *surf)
|
2021-02-12 11:29:36 +01:00
|
|
|
{
|
|
|
|
|
if (surf == NULL)
|
|
|
|
|
return;
|
2023-06-26 15:55:40 +02:00
|
|
|
|
render: gamma-correct blending
This implements gamma-correct blending, which mainly affects font
rendering.
The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).
How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.
Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.
Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.
Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.
There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.
Perhaps, in the future, we can enable it by default if:
* gamma-correct blending is enabled
* the user has not enabled a transparent background
2025-02-21 11:01:29 +01:00
|
|
|
if (surf->surface.color_management != NULL) {
|
|
|
|
|
wp_color_management_surface_v1_destroy(surf->surface.color_management);
|
|
|
|
|
surf->surface.color_management = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
if (surf->surface.viewport != NULL) {
|
|
|
|
|
wp_viewport_destroy(surf->surface.viewport);
|
|
|
|
|
surf->surface.viewport = NULL;
|
|
|
|
|
}
|
2023-07-31 16:32:53 +02:00
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
if (surf->sub != NULL) {
|
2021-02-12 11:29:36 +01:00
|
|
|
wl_subsurface_destroy(surf->sub);
|
2023-06-26 16:10:40 +02:00
|
|
|
surf->sub = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (surf->surface.surf != NULL) {
|
|
|
|
|
wl_surface_destroy(surf->surface.surf);
|
|
|
|
|
surf->surface.surf = NULL;
|
|
|
|
|
}
|
2021-02-12 11:29:36 +01:00
|
|
|
}
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
activation_token_done(void *data, struct xdg_activation_token_v1 *xdg_token,
|
|
|
|
|
const char *token)
|
|
|
|
|
{
|
|
|
|
|
LOG_DBG("XDG activation token done: %s", token);
|
|
|
|
|
|
|
|
|
|
struct xdg_activation_token_context *ctx = data;
|
|
|
|
|
struct wl_window *win = ctx->win;
|
|
|
|
|
|
|
|
|
|
ctx->cb(token, ctx->cb_data);
|
|
|
|
|
|
|
|
|
|
tll_foreach(win->xdg_tokens, it) {
|
|
|
|
|
if (it->item->xdg_token != xdg_token)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
xassert(win == it->item->win);
|
|
|
|
|
|
|
|
|
|
free(ctx);
|
|
|
|
|
xdg_activation_token_v1_destroy(xdg_token);
|
|
|
|
|
tll_remove(win->xdg_tokens, it);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 21:04:22 +00:00
|
|
|
BUG("activation token not found in list");
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct
|
|
|
|
|
xdg_activation_token_v1_listener activation_token_listener = {
|
|
|
|
|
.done = &activation_token_done,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
wayl_get_activation_token(
|
|
|
|
|
struct wayland *wayl, struct seat *seat, uint32_t serial,
|
|
|
|
|
struct wl_window *win,
|
|
|
|
|
void (*cb)(const char *token, void *data), void *cb_data)
|
|
|
|
|
{
|
|
|
|
|
if (wayl->xdg_activation == NULL)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct xdg_activation_token_v1 *token =
|
|
|
|
|
xdg_activation_v1_get_activation_token(wayl->xdg_activation);
|
|
|
|
|
|
|
|
|
|
if (token == NULL) {
|
|
|
|
|
LOG_ERR("failed to retrieve XDG activation token");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct xdg_activation_token_context *ctx = xmalloc(sizeof(*ctx));
|
|
|
|
|
*ctx = (struct xdg_activation_token_context){
|
|
|
|
|
.win = win,
|
|
|
|
|
.xdg_token = token,
|
|
|
|
|
.cb = cb,
|
|
|
|
|
.cb_data = cb_data,
|
|
|
|
|
};
|
|
|
|
|
tll_push_back(win->xdg_tokens, ctx);
|
|
|
|
|
|
|
|
|
|
if (seat != NULL && serial != 0)
|
|
|
|
|
xdg_activation_token_v1_set_serial(token, serial, seat->wl_seat);
|
|
|
|
|
|
2023-06-26 16:10:40 +02:00
|
|
|
xdg_activation_token_v1_set_surface(token, win->surface.surf);
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
xdg_activation_token_v1_add_listener(token, &activation_token_listener, ctx);
|
|
|
|
|
xdg_activation_token_v1_commit(token);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-07-23 06:58:37 +02:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
wayl_activate(struct wayland *wayl, struct wl_window *win, const char *token)
|
|
|
|
|
{
|
|
|
|
|
if (wayl->xdg_activation == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (token == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface.surf);
|
|
|
|
|
}
|
2025-05-01 08:34:49 +02:00
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf)
|
|
|
|
|
{
|
|
|
|
|
return conf->gamma_correct &&
|
|
|
|
|
wayl->color_management.img_description != NULL;
|
|
|
|
|
}
|