2019-10-27 15:57:23 +01:00
|
|
|
#include "wayland.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>
|
|
|
|
|
#include <errno.h>
|
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
|
|
|
#include <poll.h>
|
2020-01-10 19:23:32 +01:00
|
|
|
#include <fcntl.h>
|
2019-10-27 19:16:12 +01:00
|
|
|
|
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
|
|
|
|
2019-10-27 15:57:23 +01:00
|
|
|
#include <wayland-client.h>
|
|
|
|
|
#include <wayland-cursor.h>
|
|
|
|
|
#include <xdg-shell.h>
|
|
|
|
|
#include <xkbcommon/xkbcommon-compose.h>
|
|
|
|
|
|
2019-11-17 19:19:55 +01:00
|
|
|
#include <tllist.h>
|
2019-10-27 15:57:23 +01:00
|
|
|
#include <xdg-output-unstable-v1.h>
|
|
|
|
|
#include <xdg-decoration-unstable-v1.h>
|
|
|
|
|
|
|
|
|
|
#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"
|
2019-10-27 19:08:48 +01:00
|
|
|
#include "input.h"
|
|
|
|
|
#include "render.h"
|
|
|
|
|
#include "selection.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
#include "util.h"
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
static bool wayl_reload_cursor_theme(struct seat *seat, struct terminal *term);
|
2019-11-28 19:35:47 +01:00
|
|
|
|
2020-02-26 12:39:17 +01:00
|
|
|
static void
|
|
|
|
|
csd_instantiate(struct wl_window *win)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = win->term->wl;
|
|
|
|
|
assert(wayl != NULL);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ALEN(win->csd.surface); i++) {
|
|
|
|
|
assert(win->csd.surface[i] == NULL);
|
|
|
|
|
assert(win->csd.sub_surface[i] == NULL);
|
|
|
|
|
|
|
|
|
|
win->csd.surface[i] = wl_compositor_create_surface(wayl->compositor);
|
2020-03-02 20:29:28 +01:00
|
|
|
|
|
|
|
|
struct wl_surface *parent = i < CSD_SURF_MINIMIZE
|
|
|
|
|
? win->surface : win->csd.surface[CSD_SURF_TITLE];
|
|
|
|
|
|
2020-02-26 12:39:17 +01:00
|
|
|
win->csd.sub_surface[i] = wl_subcompositor_get_subsurface(
|
2020-03-02 20:29:28 +01:00
|
|
|
wayl->sub_compositor, win->csd.surface[i], parent);
|
2020-02-26 12:39:17 +01:00
|
|
|
|
|
|
|
|
wl_subsurface_set_sync(win->csd.sub_surface[i]);
|
2020-02-29 12:09:28 +01:00
|
|
|
wl_surface_set_user_data(win->csd.surface[i], win);
|
2020-02-26 12:39:17 +01:00
|
|
|
wl_surface_commit(win->csd.surface[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
csd_destroy(struct wl_window *win)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < ALEN(win->csd.surface); i++) {
|
|
|
|
|
if (win->csd.sub_surface[i] != NULL)
|
|
|
|
|
wl_subsurface_destroy(win->csd.sub_surface[i]);
|
|
|
|
|
if (win->csd.surface[i] != NULL)
|
|
|
|
|
wl_surface_destroy(win->csd.surface[i]);
|
|
|
|
|
|
|
|
|
|
win->csd.surface[i] = NULL;
|
|
|
|
|
win->csd.sub_surface[i] = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
static void
|
|
|
|
|
seat_destroy(struct seat *seat)
|
|
|
|
|
{
|
|
|
|
|
if (seat == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
tll_foreach(seat->kbd.bindings.key, it)
|
|
|
|
|
tll_free(it->item.bind.key_codes);
|
|
|
|
|
tll_free(seat->kbd.bindings.key);
|
|
|
|
|
|
|
|
|
|
tll_foreach(seat->kbd.bindings.search, it)
|
|
|
|
|
tll_free(it->item.bind.key_codes);
|
|
|
|
|
tll_free(seat->kbd.bindings.search);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
free(seat->pointer.theme_name);
|
|
|
|
|
if (seat->pointer.theme != NULL)
|
|
|
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
|
|
|
|
if (seat->pointer.surface != NULL)
|
|
|
|
|
wl_surface_destroy(seat->pointer.surface);
|
|
|
|
|
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)
|
|
|
|
|
wl_data_device_destroy(seat->data_device);
|
|
|
|
|
|
|
|
|
|
if (seat->wl_keyboard != NULL)
|
|
|
|
|
wl_keyboard_destroy(seat->wl_keyboard);
|
|
|
|
|
if (seat->wl_pointer != NULL)
|
|
|
|
|
wl_pointer_destroy(seat->wl_pointer);
|
|
|
|
|
if (seat->wl_seat != NULL)
|
|
|
|
|
wl_seat_destroy(seat->wl_seat);
|
|
|
|
|
|
|
|
|
|
free(seat->clipboard.text);
|
|
|
|
|
free(seat->primary.text);
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
struct wayland *wayl = data;
|
|
|
|
|
if (format == WL_SHM_FORMAT_ARGB8888)
|
|
|
|
|
wayl->have_argb8888 = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
assert(seat->wl_seat == wl_seat);
|
2020-07-08 19:30:34 +02:00
|
|
|
|
|
|
|
|
LOG_DBG("%s: keyboard=%s, pointer=%s", seat->name,
|
|
|
|
|
(caps & WL_SEAT_CAPABILITY_KEYBOARD) ? "yes" : "no",
|
|
|
|
|
(caps & WL_SEAT_CAPABILITY_POINTER) ? "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) {
|
|
|
|
|
seat->wl_pointer = wl_seat_get_pointer(wl_seat);
|
|
|
|
|
wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat);
|
2020-03-03 18:20:53 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
2020-07-08 16:45:26 +02:00
|
|
|
if (seat->wl_pointer != NULL) {
|
|
|
|
|
wl_pointer_release(seat->wl_pointer);
|
|
|
|
|
seat->wl_pointer = NULL;
|
2020-03-03 18:20:53 +01:00
|
|
|
}
|
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);
|
|
|
|
|
seat->name = strdup(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)
|
|
|
|
|
{
|
2020-02-29 15:46:40 +01:00
|
|
|
if (tll_length(term->window->on_outputs) == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-02-15 19:05:33 +01:00
|
|
|
render_resize(term, term->width / term->scale, term->height / term->scale);
|
|
|
|
|
term_font_dpi_changed(term);
|
2020-04-20 18:37:59 +02:00
|
|
|
term_font_subpixel_changed(term);
|
2020-07-08 18:08:39 +02:00
|
|
|
|
|
|
|
|
tll_foreach(term->wl->seats, it)
|
|
|
|
|
wayl_reload_cursor_theme(&it->item, term);
|
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)
|
|
|
|
|
{
|
2020-03-11 16:10:55 +01:00
|
|
|
if (mon->dim.mm.width == 0 || mon->dim.mm.height == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
int x_inches = mon->dim.mm.width * 0.03937008;
|
|
|
|
|
int y_inches = mon->dim.mm.height * 0.03937008;
|
|
|
|
|
mon->ppi.real.x = mon->dim.px_real.width / x_inches;
|
|
|
|
|
mon->ppi.real.y = mon->dim.px_real.height / y_inches;
|
|
|
|
|
|
|
|
|
|
mon->ppi.scaled.x = mon->dim.px_scaled.width / x_inches;
|
|
|
|
|
mon->ppi.scaled.y = mon->dim.px_scaled.height / y_inches;
|
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;
|
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;
|
2019-12-05 19:35:34 +01:00
|
|
|
mon->make = make != NULL ? strdup(make) : NULL;
|
|
|
|
|
mon->model = model != NULL ? strdup(model) : NULL;
|
2020-04-20 18:37:59 +02:00
|
|
|
mon->subpixel = subpixel;
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_output_listener output_listener = {
|
|
|
|
|
.geometry = &output_geometry,
|
|
|
|
|
.mode = &output_mode,
|
|
|
|
|
.done = &output_done,
|
|
|
|
|
.scale = &output_scale,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
|
|
|
|
|
const char *name)
|
|
|
|
|
{
|
|
|
|
|
struct monitor *mon = data;
|
2020-06-25 17:30:51 +02:00
|
|
|
mon->name = name != NULL ? strdup(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;
|
|
|
|
|
mon->description = description != NULL ? strdup(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,
|
|
|
|
|
};
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR("unmapped from unknown output");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
|
|
|
.enter = &surface_enter,
|
|
|
|
|
.leave = &surface_leave,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
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",
|
|
|
|
|
};
|
|
|
|
|
#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-02-26 13:23:00 +01:00
|
|
|
case XDG_TOPLEVEL_STATE_ACTIVATED: is_activated = true; break;
|
2020-02-26 12:39:17 +01:00
|
|
|
case XDG_TOPLEVEL_STATE_FULLSCREEN: is_fullscreen = true; break;
|
2020-02-26 13:23:00 +01:00
|
|
|
case XDG_TOPLEVEL_STATE_MAXIMIZED: is_maximized = true; break;
|
2020-01-03 12:49:04 +01:00
|
|
|
|
2020-02-26 12:26:03 +01:00
|
|
|
case XDG_TOPLEVEL_STATE_RESIZING:
|
2020-01-03 12:49:04 +01:00
|
|
|
case XDG_TOPLEVEL_STATE_TILED_LEFT:
|
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
|
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_TOP:
|
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
|
|
|
|
|
/* Ignored */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
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 >= XDG_TOPLEVEL_STATE_MAXIMIZED &&
|
|
|
|
|
*state <= XDG_TOPLEVEL_STATE_TILED_BOTTOM)
|
|
|
|
|
{
|
|
|
|
|
state_chars += snprintf(
|
|
|
|
|
&state_str[state_chars], sizeof(state_str) - state_chars,
|
|
|
|
|
"%s, ", strings[*state]);
|
|
|
|
|
}
|
|
|
|
|
#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
|
|
|
|
|
|
|
|
LOG_DBG("xdg-toplevel: configure: size=%dx%d, states=%s",
|
|
|
|
|
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-03-03 18:26:15 +01:00
|
|
|
if (!is_fullscreen && win->use_csd == CSD_YES && width > 0 && height > 0) {
|
2020-02-26 12:26:03 +01:00
|
|
|
/*
|
2020-03-03 18:26:15 +01:00
|
|
|
* We include the CSD title bar in our window geometry. Thus,
|
|
|
|
|
* the height we call render_resize() with must be adjusted,
|
|
|
|
|
* since it expects the size to refer to the main grid only.
|
2020-02-26 12:26:03 +01:00
|
|
|
*/
|
2020-03-03 18:26:15 +01:00
|
|
|
height -= win->term->conf->csd.title_height;
|
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;
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
|
|
|
.configure = &xdg_toplevel_configure,
|
|
|
|
|
.close = &xdg_toplevel_close,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2020-04-30 17:22:57 +02:00
|
|
|
bool wasnt_configured = !win->is_configured;
|
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;
|
2020-01-03 18:55:13 +01:00
|
|
|
|
2020-02-26 12:39:17 +01:00
|
|
|
if (win->is_fullscreen != win->configure.is_fullscreen && win->use_csd == CSD_YES) {
|
|
|
|
|
if (win->configure.is_fullscreen)
|
|
|
|
|
csd_destroy(win);
|
|
|
|
|
else
|
|
|
|
|
csd_instantiate(win);
|
|
|
|
|
}
|
2020-02-29 17:24:45 +01:00
|
|
|
win->is_fullscreen = win->configure.is_fullscreen;
|
2020-02-26 12:39:17 +01:00
|
|
|
|
2020-02-25 19:53:06 +01:00
|
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
2020-05-12 18:35:28 +02:00
|
|
|
bool resized = render_resize_force(term, win->configure.width, win->configure.height);
|
2020-01-03 18:58:26 +01:00
|
|
|
|
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
|
|
|
|
2020-05-12 18:35:28 +02:00
|
|
|
/* TODO: remove - shouldn't be necessary with render_resize_force() */
|
2020-03-03 18:26:15 +01:00
|
|
|
if (!resized) {
|
2020-02-25 19:53:06 +01:00
|
|
|
/*
|
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
|
|
|
* If we didn't resize, we won't be commit a new surface
|
|
|
|
|
* anytime soon. Some compositors require a commit in
|
|
|
|
|
* combination with an ack - make them happy.
|
2020-02-25 19:53:06 +01:00
|
|
|
*/
|
|
|
|
|
wl_surface_commit(win->surface);
|
|
|
|
|
}
|
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;
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
switch (mode) {
|
|
|
|
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
|
2020-03-02 18:43:07 +01:00
|
|
|
LOG_INFO("using CSD decorations");
|
2020-02-26 12:17:58 +01:00
|
|
|
win->use_csd = CSD_YES;
|
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_instantiate(win);
|
2019-10-27 19:08:48 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
|
2020-03-02 18:43:07 +01:00
|
|
|
LOG_INFO("using SSD decorations");
|
2020-02-26 12:17:58 +01:00
|
|
|
win->use_csd = CSD_NO;
|
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);
|
2019-10-27 19:08:48 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-02-25 19:16:23 +01:00
|
|
|
|
2020-02-28 18:42:10 +01:00
|
|
|
if (win->is_configured && win->use_csd == CSD_YES) {
|
2020-02-26 13:44:05 +01:00
|
|
|
struct terminal *term = win->term;
|
2020-02-28 18:42:10 +01:00
|
|
|
|
2020-03-02 18:42:49 +01:00
|
|
|
int scale = term->scale;
|
2020-02-28 18:42:10 +01:00
|
|
|
int width = term->width / scale;
|
|
|
|
|
int height = term->height / scale;
|
|
|
|
|
|
2020-03-03 18:26:15 +01:00
|
|
|
/* Take CSD title bar into account */
|
|
|
|
|
height -= term->conf->csd.title_height;
|
2020-02-28 18:42:10 +01:00
|
|
|
render_resize_force(term, width, height);
|
2020-02-26 13:44:05 +01:00
|
|
|
}
|
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;
|
|
|
|
|
|
|
|
|
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
|
|
|
|
const uint32_t required = 4;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->compositor = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wl_compositor_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
|
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->shm = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wl_shm_interface, required);
|
|
|
|
|
wl_shm_add_listener(wayl->shm, &shm_listener, wayl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
|
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->shell = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &xdg_wm_base_interface, required);
|
|
|
|
|
xdg_wm_base_add_listener(wayl->shell, &xdg_wm_base_listener, wayl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
|
|
|
|
const uint32_t required = 5;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
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(
|
2020-04-27 20:18:03 +02:00
|
|
|
wayl->registry, name, &wl_seat_interface, required);
|
2020-07-08 16:45:26 +02:00
|
|
|
|
|
|
|
|
/* Clipboard */
|
|
|
|
|
struct wl_data_device *data_device = wl_data_device_manager_get_data_device(
|
|
|
|
|
wayl->data_device_manager, wl_seat);
|
|
|
|
|
|
|
|
|
|
/* Primary selection */
|
|
|
|
|
struct zwp_primary_selection_device_v1 *primary_selection_device;
|
|
|
|
|
if (wayl->primary_selection_device_manager != NULL) {
|
|
|
|
|
primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
|
|
|
|
|
wayl->primary_selection_device_manager, wl_seat);
|
|
|
|
|
} else
|
|
|
|
|
primary_selection_device = NULL;
|
|
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
struct wl_surface *pointer_surf
|
|
|
|
|
= wl_compositor_create_surface(wayl->compositor);
|
|
|
|
|
|
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-08 18:08:39 +02:00
|
|
|
.pointer = {
|
|
|
|
|
.surface = pointer_surf,
|
|
|
|
|
.size = wayl->xcursor_size,
|
|
|
|
|
.theme_name = wayl->xcursor_theme != NULL ? strdup(wayl->xcursor_theme) : NULL,
|
|
|
|
|
},
|
2020-07-08 16:45:26 +02:00
|
|
|
.data_device = data_device,
|
|
|
|
|
.primary_selection_device = primary_selection_device,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wl_seat_add_listener(wl_seat, &seat_listener, seat);
|
|
|
|
|
wl_data_device_add_listener(data_device, &data_device_listener, seat);
|
|
|
|
|
if (primary_selection_device != NULL) {
|
|
|
|
|
zwp_primary_selection_device_v1_add_listener(
|
|
|
|
|
primary_selection_device, &primary_selection_device_listener, seat);
|
|
|
|
|
}
|
2020-04-27 20:18:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, wl_output_interface.name) == 0) {
|
|
|
|
|
const uint32_t required = 2;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct wl_output *output = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wl_output_interface, required);
|
|
|
|
|
|
|
|
|
|
tll_push_back(
|
2020-04-27 20:46:40 +02:00
|
|
|
wayl->monitors,
|
|
|
|
|
((struct monitor){.wayl = wayl, .output = output, .wl_name = name}));
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
|
|
|
|
|
const uint32_t required = 1;
|
|
|
|
|
if (!verify_iface_version(interface, version, required))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wayl->data_device_manager = wl_registry_bind(
|
|
|
|
|
wayl->registry, name, &wl_data_device_manager_interface, required);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else if (strcmp(interface, wp_presentation_interface.name) == 0) {
|
|
|
|
|
if (wayl->conf->presentation_timings) {
|
|
|
|
|
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-04-27 20:46:40 +02:00
|
|
|
static void
|
|
|
|
|
monitor_destroy(struct monitor *mon)
|
|
|
|
|
{
|
|
|
|
|
if (mon->xdg != NULL)
|
|
|
|
|
zxdg_output_v1_destroy(mon->xdg);
|
|
|
|
|
if (mon->output != NULL)
|
|
|
|
|
wl_output_destroy(mon->output);
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
/* For now, we only support removal of outputs */
|
|
|
|
|
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-04-28 19:05:10 +02:00
|
|
|
LOG_INFO("monitor unplugged: %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-04-28 19:05:10 +02:00
|
|
|
tll_foreach(wayl->terms, t)
|
|
|
|
|
surface_leave(t->item->window, NULL /* wl_surface - unused */, mon->output);
|
2020-04-27 20:46:40 +02:00
|
|
|
|
|
|
|
|
monitor_destroy(&it->item);
|
|
|
|
|
tll_remove(wayl->monitors, it);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
2020-01-04 23:32:00 +01:00
|
|
|
while (wl_display_prepare_read(wayl->display) != 0)
|
|
|
|
|
wl_display_dispatch_pending(wayl->display);
|
|
|
|
|
}
|
2019-10-27 19:11:35 +01:00
|
|
|
|
|
|
|
|
if (events & EPOLLHUP) {
|
2019-11-23 13:56:11 +01:00
|
|
|
LOG_WARN("disconnected from Wayland");
|
2020-01-03 21:02:12 +01:00
|
|
|
wl_display_cancel_read(wayl->display);
|
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 *
|
2019-12-31 16:12:48 +01:00
|
|
|
wayl_init(const struct config *conf, struct fdm *fdm)
|
2019-10-27 19:08:48 +01:00
|
|
|
{
|
|
|
|
|
struct wayland *wayl = calloc(1, sizeof(*wayl));
|
2019-12-31 16:12:48 +01:00
|
|
|
wayl->conf = conf;
|
2019-10-27 19:08:48 +01:00
|
|
|
wayl->fdm = fdm;
|
2020-03-15 13:36:35 +01:00
|
|
|
wayl->fd = -1;
|
2019-10-27 19:08:48 +01:00
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
/* XCursor */
|
|
|
|
|
const char *xcursor_theme = getenv("XCURSOR_THEME");
|
|
|
|
|
if (xcursor_theme != NULL)
|
|
|
|
|
wayl->xcursor_theme = strdup(xcursor_theme);
|
|
|
|
|
wayl->xcursor_size = 24;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const char *env_cursor_size = getenv("XCURSOR_SIZE");
|
|
|
|
|
if (env_cursor_size != NULL) {
|
|
|
|
|
unsigned size;
|
|
|
|
|
if (sscanf(env_cursor_size, "%u", &size) == 1)
|
|
|
|
|
wayl->xcursor_size = size;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_INFO("cursor theme: %s, size: %u",
|
|
|
|
|
wayl->xcursor_theme, wayl->xcursor_size);
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (wayl->primary_selection_device_manager == NULL)
|
|
|
|
|
LOG_WARN("no primary selection available");
|
|
|
|
|
|
2020-03-09 18:46:09 +01:00
|
|
|
if (conf->presentation_timings && wayl->presentation == NULL) {
|
|
|
|
|
LOG_ERR("presentation time interface not implemented by compositor");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2020-01-01 16:09:16 +01:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (!wayl->have_argb8888) {
|
|
|
|
|
LOG_ERR("compositor does not support ARGB surfaces");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
tll_foreach(wayl->monitors, it) {
|
2019-12-05 19:35:34 +01:00
|
|
|
LOG_INFO(
|
2020-03-11 16:10:55 +01:00
|
|
|
"%s: %dx%d+%dx%d@%dHz %s %.2f\" scale=%d PPI=%dx%d (physical) PPI=%dx%d (logical)",
|
|
|
|
|
it->item.name, it->item.dim.px_real.width, it->item.dim.px_real.height,
|
|
|
|
|
it->item.x, it->item.y, (int)round(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,
|
2020-03-11 16:10:55 +01:00
|
|
|
it->item.ppi.real.x, it->item.ppi.real.y,
|
|
|
|
|
it->item.ppi.scaled.x, it->item.ppi.scaled.y);
|
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);
|
|
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
tll_foreach(wayl->monitors, it)
|
2020-04-27 20:46:40 +02:00
|
|
|
monitor_destroy(&it->item);
|
2020-07-08 16:45:26 +02:00
|
|
|
tll_free(wayl->monitors);
|
2019-10-27 15:57:23 +01:00
|
|
|
|
2020-07-08 16:45:26 +02:00
|
|
|
tll_foreach(wayl->seats, it)
|
|
|
|
|
seat_destroy(&it->item);
|
|
|
|
|
tll_free(wayl->seats);
|
2020-01-04 22:01:19 +01:00
|
|
|
|
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);
|
|
|
|
|
if (wayl->shm != NULL)
|
|
|
|
|
wl_shm_destroy(wayl->shm);
|
|
|
|
|
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-07-08 18:08:39 +02:00
|
|
|
if (wayl->display != NULL)
|
2019-10-27 15:57:23 +01:00
|
|
|
wl_display_disconnect(wayl->display);
|
2019-10-27 19:16:42 +01:00
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
free(wayl->xcursor_theme);
|
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
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
struct wl_window *
|
2020-01-03 13:37:03 +01:00
|
|
|
wayl_win_init(struct terminal *term)
|
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
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
struct wl_window *win = calloc(1, sizeof(*win));
|
2020-01-03 13:37:03 +01:00
|
|
|
win->term = term;
|
2020-02-26 12:17:58 +01:00
|
|
|
win->use_csd = CSD_UNKNOWN;
|
2020-02-29 12:52:55 +01:00
|
|
|
win->csd.move_timeout_fd = -1;
|
2019-10-27 19:08:48 +01:00
|
|
|
|
|
|
|
|
win->surface = wl_compositor_create_surface(wayl->compositor);
|
|
|
|
|
if (win->surface == NULL) {
|
|
|
|
|
LOG_ERR("failed to create wayland surface");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-03 21:53:38 +01:00
|
|
|
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);
|
|
|
|
|
wl_surface_set_opaque_region(win->surface, region);
|
|
|
|
|
wl_region_destroy(region);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-03 13:40:37 +01:00
|
|
|
wl_surface_add_listener(win->surface, &surface_listener, win);
|
2019-10-27 19:08:48 +01:00
|
|
|
|
|
|
|
|
win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface);
|
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
|
|
|
|
|
|
|
|
/* Request server-side decorations */
|
2020-02-25 19:55:50 +01:00
|
|
|
if (wayl->xdg_decoration_manager != NULL) {
|
|
|
|
|
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
|
|
|
|
|
? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
|
|
|
|
|
: ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE));
|
|
|
|
|
|
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 */
|
|
|
|
|
win->use_csd = CSD_YES;
|
|
|
|
|
csd_instantiate(win);
|
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
|
|
|
|
|
|
|
|
wl_surface_commit(win->surface);
|
|
|
|
|
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;
|
|
|
|
|
|
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).
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Scrollback search */
|
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
|
|
|
if (win->search_surface != NULL) {
|
|
|
|
|
wl_surface_attach(win->search_surface, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->search_surface);
|
|
|
|
|
}
|
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++) {
|
|
|
|
|
if (win->csd.surface[i] != NULL) {
|
|
|
|
|
wl_surface_attach(win->csd.surface[i], NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->csd.surface[i]);
|
|
|
|
|
}
|
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 */
|
2019-11-01 20:19:53 +01:00
|
|
|
wl_surface_attach(win->surface, NULL, 0, 0);
|
|
|
|
|
wl_surface_commit(win->surface);
|
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
|
|
|
|
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);
|
2019-10-27 16:01:03 +01:00
|
|
|
if (win->search_sub_surface != NULL)
|
|
|
|
|
wl_subsurface_destroy(win->search_sub_surface);
|
|
|
|
|
if (win->search_surface != NULL)
|
|
|
|
|
wl_surface_destroy(win->search_surface);
|
|
|
|
|
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);
|
|
|
|
|
if (win->surface != NULL)
|
|
|
|
|
wl_surface_destroy(win->surface);
|
2019-11-01 20:19:53 +01:00
|
|
|
|
2020-01-03 21:02:12 +01:00
|
|
|
wayl_roundtrip(win->term->wl);
|
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
|
|
|
|
2019-11-28 19:35:47 +01:00
|
|
|
static bool
|
2020-07-08 18:08:39 +02:00
|
|
|
wayl_reload_cursor_theme(struct seat *seat, struct terminal *term)
|
2019-11-28 19:35:47 +01:00
|
|
|
{
|
2020-07-08 18:08:39 +02:00
|
|
|
if (seat->pointer.size == 0)
|
2019-11-28 19:35:47 +01:00
|
|
|
return true;
|
|
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
if (seat->pointer.theme != NULL) {
|
|
|
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
|
|
|
|
seat->pointer.theme = NULL;
|
|
|
|
|
seat->pointer.cursor = NULL;
|
2019-11-28 19:35:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_DBG("reloading cursor theme: %s@%d",
|
2020-07-08 18:08:39 +02:00
|
|
|
seat->pointer.theme_name, seat->pointer.size);
|
2019-11-28 19:35:47 +01:00
|
|
|
|
2020-07-08 18:08:39 +02:00
|
|
|
seat->pointer.theme = wl_cursor_theme_load(
|
|
|
|
|
seat->pointer.theme_name, seat->pointer.size * term->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 18:08:39 +02:00
|
|
|
return render_xcursor_set(seat, term);
|
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) {
|
|
|
|
|
LOG_ERRNO("failed to flush wayland socket");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 23:36:32 +01:00
|
|
|
/* Socket buffer is full - need to wait for it to become
|
|
|
|
|
writeable again */
|
|
|
|
|
assert(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;
|
|
|
|
|
|
|
|
|
|
assert(fds[0].revents & POLLOUT);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-03 21:02:12 +01:00
|
|
|
void
|
|
|
|
|
wayl_roundtrip(struct wayland *wayl)
|
|
|
|
|
{
|
|
|
|
|
wl_display_cancel_read(wayl->display);
|
|
|
|
|
wl_display_roundtrip(wayl->display);
|
|
|
|
|
|
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... */
|
|
|
|
|
|
2020-01-03 21:02:12 +01:00
|
|
|
while (wl_display_prepare_read(wayl->display) != 0)
|
|
|
|
|
wl_display_dispatch_pending(wayl->display);
|
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
|
|
|
}
|