mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-22 04:15:39 +02:00
If the surface enters an output, there's no need for last_mapped_monitor, and it must be reset to fulfill the asserts in other parts of the code. This makes yambar no longer crash when it is hidden by an opaque window multiple times on a compositor using wlroots' scene tree.
1527 lines
42 KiB
C
1527 lines
42 KiB
C
#include "wayland.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
#include <errno.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <fcntl.h>
|
|
#include <linux/input-event-codes.h>
|
|
|
|
#include <pixman.h>
|
|
#include <wayland-client.h>
|
|
#include <wayland-cursor.h>
|
|
|
|
#include <tllist.h>
|
|
#include <xdg-output-unstable-v1.h>
|
|
#include <wlr-layer-shell-unstable-v1.h>
|
|
|
|
#define LOG_MODULE "bar:wayland"
|
|
#define LOG_ENABLE_DBG 0
|
|
#include "../log.h"
|
|
#include "../stride.h"
|
|
|
|
#include "private.h"
|
|
|
|
#if !defined(MFD_NOEXEC_SEAL)
|
|
#define MFD_NOEXEC_SEAL 0
|
|
#endif
|
|
|
|
struct buffer {
|
|
bool busy;
|
|
size_t width;
|
|
size_t height;
|
|
size_t size;
|
|
void *mmapped;
|
|
|
|
struct wl_buffer *wl_buf;
|
|
|
|
pixman_image_t *pix;
|
|
};
|
|
|
|
struct monitor {
|
|
struct wayland_backend *backend;
|
|
|
|
struct wl_output *output;
|
|
struct zxdg_output_v1 *xdg;
|
|
char *name;
|
|
uint32_t wl_name;
|
|
|
|
int x;
|
|
int y;
|
|
|
|
int width_mm;
|
|
int height_mm;
|
|
|
|
int width_px;
|
|
int height_px;
|
|
|
|
int scale;
|
|
};
|
|
|
|
struct seat {
|
|
struct wayland_backend *backend;
|
|
struct wl_seat *seat;
|
|
char *name;
|
|
uint32_t id;
|
|
|
|
struct wl_pointer *wl_pointer;
|
|
struct {
|
|
uint32_t serial;
|
|
|
|
int x;
|
|
int y;
|
|
|
|
struct wl_surface *surface;
|
|
struct wl_cursor_theme *theme;
|
|
struct wl_cursor *cursor;
|
|
const char *xcursor;
|
|
int scale;
|
|
} pointer;
|
|
};
|
|
|
|
struct wayland_backend {
|
|
struct bar *bar;
|
|
|
|
struct wl_display *display;
|
|
struct wl_registry *registry;
|
|
struct wl_compositor *compositor;
|
|
struct wl_surface *surface;
|
|
struct zwlr_layer_shell_v1 *layer_shell;
|
|
struct zwlr_layer_surface_v1 *layer_surface;
|
|
struct wl_shm *shm;
|
|
|
|
tll(struct seat) seats;
|
|
struct seat *active_seat;
|
|
|
|
tll(struct monitor) monitors;
|
|
const struct monitor *monitor;
|
|
char *last_mapped_monitor;
|
|
|
|
int scale;
|
|
|
|
struct zxdg_output_manager_v1 *xdg_output_manager;
|
|
|
|
/* TODO: set directly in bar instead */
|
|
int width, height;
|
|
|
|
/* Used to signal e.g. refresh */
|
|
int pipe_fds[2];
|
|
|
|
/* We're already waiting for a frame done callback */
|
|
bool render_scheduled;
|
|
|
|
tll(struct buffer) buffers; /* List of SHM buffers */
|
|
struct buffer *next_buffer; /* Bar is rendering to this one */
|
|
struct buffer *pending_buffer; /* Finished, but not yet rendered */
|
|
struct wl_callback *frame_callback;
|
|
|
|
double aggregated_scroll;
|
|
bool have_discrete;
|
|
|
|
void (*bar_on_mouse)(struct bar *bar, enum mouse_event event,
|
|
enum mouse_button btn, int x, int y);
|
|
};
|
|
|
|
static void
|
|
seat_destroy(struct seat *seat)
|
|
{
|
|
if (seat == NULL)
|
|
return;
|
|
|
|
free(seat->name);
|
|
|
|
if (seat->pointer.theme != NULL)
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
|
if (seat->wl_pointer != NULL)
|
|
wl_pointer_release(seat->wl_pointer);
|
|
if (seat->pointer.surface != NULL)
|
|
wl_surface_destroy(seat->pointer.surface);
|
|
if (seat->seat != NULL)
|
|
wl_seat_release(seat->seat);
|
|
}
|
|
|
|
void *
|
|
bar_backend_wayland_new(void)
|
|
{
|
|
struct wayland_backend *backend = calloc(1, sizeof(struct wayland_backend));
|
|
backend->pipe_fds[0] = backend->pipe_fds[1] = -1;
|
|
return backend;
|
|
}
|
|
|
|
static void
|
|
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
|
|
{
|
|
//printf("SHM format: 0x%08x\n", format);
|
|
}
|
|
|
|
static const struct wl_shm_listener shm_listener = {
|
|
.format = &shm_format,
|
|
};
|
|
|
|
static void
|
|
update_cursor_surface(struct wayland_backend *backend, struct seat *seat)
|
|
{
|
|
if (seat->pointer.serial == 0 ||
|
|
seat->pointer.cursor == NULL ||
|
|
seat->pointer.surface == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
struct wl_cursor_image *image = seat->pointer.cursor->images[0];
|
|
|
|
const int scale = seat->pointer.scale;
|
|
wl_surface_set_buffer_scale(seat->pointer.surface, scale);
|
|
|
|
wl_surface_attach(
|
|
seat->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0);
|
|
|
|
wl_pointer_set_cursor(
|
|
seat->wl_pointer, seat->pointer.serial,
|
|
seat->pointer.surface,
|
|
image->hotspot_x / scale, image->hotspot_y / scale);
|
|
|
|
|
|
wl_surface_damage_buffer(
|
|
seat->pointer.surface, 0, 0, INT32_MAX, INT32_MAX);
|
|
|
|
wl_surface_commit(seat->pointer.surface);
|
|
wl_display_flush(backend->display);
|
|
}
|
|
|
|
static void
|
|
reload_cursor_theme(struct seat *seat, int new_scale)
|
|
{
|
|
if (seat->pointer.theme != NULL && seat->pointer.scale == new_scale)
|
|
return;
|
|
|
|
if (seat->pointer.theme != NULL) {
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
|
seat->pointer.theme = NULL;
|
|
seat->pointer.cursor = NULL;
|
|
}
|
|
|
|
unsigned cursor_size = 24;
|
|
const char *cursor_theme = getenv("XCURSOR_THEME");
|
|
|
|
{
|
|
const char *env_cursor_size = getenv("XCURSOR_SIZE");
|
|
if (env_cursor_size != NULL) {
|
|
unsigned size;
|
|
if (sscanf(env_cursor_size, "%u", &size) == 1)
|
|
cursor_size = size;
|
|
}
|
|
}
|
|
|
|
LOG_INFO("%s: cursor theme: %s, size: %u, scale: %d",
|
|
seat->name, cursor_theme, cursor_size, new_scale);
|
|
|
|
struct wl_cursor_theme *theme = wl_cursor_theme_load(
|
|
cursor_theme, cursor_size * new_scale, seat->backend->shm);
|
|
|
|
if (theme == NULL) {
|
|
LOG_ERR("%s: failed to load cursor theme", seat->name);
|
|
return;
|
|
}
|
|
|
|
seat->pointer.scale = new_scale;
|
|
seat->pointer.theme = theme;
|
|
}
|
|
|
|
static void
|
|
wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t serial, struct wl_surface *surface,
|
|
wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
{
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
|
|
seat->pointer.serial = serial;
|
|
seat->pointer.x = wl_fixed_to_int(surface_x) * backend->scale;
|
|
seat->pointer.y = wl_fixed_to_int(surface_y) * backend->scale;
|
|
|
|
backend->active_seat = seat;
|
|
reload_cursor_theme(seat, backend->monitor->scale);
|
|
update_cursor_surface(backend, seat);
|
|
}
|
|
|
|
static void
|
|
wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t serial, struct wl_surface *surface)
|
|
{
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
|
|
backend->have_discrete = false;
|
|
|
|
if (backend->active_seat == seat)
|
|
backend->active_seat = NULL;
|
|
}
|
|
|
|
static void
|
|
wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
{
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
|
|
seat->pointer.x = wl_fixed_to_int(surface_x) * backend->scale;
|
|
seat->pointer.y = wl_fixed_to_int(surface_y) * backend->scale;
|
|
|
|
backend->active_seat = seat;
|
|
backend->bar_on_mouse(
|
|
backend->bar, ON_MOUSE_MOTION, MOUSE_BTN_NONE,
|
|
seat->pointer.x, seat->pointer.y);
|
|
}
|
|
|
|
static void
|
|
wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
|
|
{
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
|
|
backend->active_seat = seat;
|
|
else {
|
|
enum mouse_button btn;
|
|
|
|
switch (button) {
|
|
case BTN_LEFT: btn = MOUSE_BTN_LEFT; break;
|
|
case BTN_MIDDLE: btn = MOUSE_BTN_MIDDLE; break;
|
|
case BTN_RIGHT: btn = MOUSE_BTN_RIGHT; break;
|
|
case BTN_SIDE: btn = MOUSE_BTN_PREVIOUS; break;
|
|
case BTN_EXTRA: btn = MOUSE_BTN_NEXT; break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
backend->bar_on_mouse(
|
|
backend->bar, ON_MOUSE_CLICK, btn, seat->pointer.x, seat->pointer.y);
|
|
}
|
|
}
|
|
|
|
static void
|
|
wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t time, uint32_t axis, wl_fixed_t value)
|
|
{
|
|
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
|
|
return;
|
|
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
struct private *bar = backend->bar->private;
|
|
|
|
backend->active_seat = seat;
|
|
|
|
if (backend->have_discrete)
|
|
return;
|
|
|
|
const double amount = wl_fixed_to_double(value);
|
|
|
|
if ((backend->aggregated_scroll > 0 && amount < 0) ||
|
|
(backend->aggregated_scroll < 0 && amount > 0))
|
|
{
|
|
backend->aggregated_scroll = amount;
|
|
} else
|
|
backend->aggregated_scroll += amount;
|
|
|
|
enum mouse_button btn = backend->aggregated_scroll > 0
|
|
? MOUSE_BTN_WHEEL_DOWN
|
|
: MOUSE_BTN_WHEEL_UP;
|
|
|
|
const double step = bar->trackpad_sensitivity;
|
|
const double adjust = backend->aggregated_scroll > 0 ? -step : step;
|
|
|
|
while (fabs(backend->aggregated_scroll) >= step) {
|
|
backend->bar_on_mouse(
|
|
backend->bar, ON_MOUSE_CLICK, btn,
|
|
seat->pointer.x, seat->pointer.y);
|
|
backend->aggregated_scroll += adjust;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wl_pointer_frame(void *data, struct wl_pointer *wl_pointer)
|
|
{
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
backend->have_discrete = false;
|
|
}
|
|
|
|
static void
|
|
wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t axis_source)
|
|
{
|
|
}
|
|
|
|
static void
|
|
wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t time, uint32_t axis)
|
|
{
|
|
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
|
|
return;
|
|
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
backend->aggregated_scroll = 0.;
|
|
}
|
|
|
|
static void
|
|
wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t axis, int32_t discrete)
|
|
{
|
|
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
|
|
return;
|
|
|
|
struct seat *seat = data;
|
|
struct wayland_backend *backend = seat->backend;
|
|
backend->have_discrete = true;
|
|
|
|
enum mouse_button btn = discrete > 0
|
|
? MOUSE_BTN_WHEEL_DOWN
|
|
: MOUSE_BTN_WHEEL_UP;
|
|
|
|
int count = abs(discrete);
|
|
|
|
for (int32_t i = 0; i < count; i++) {
|
|
backend->bar_on_mouse(
|
|
backend->bar, ON_MOUSE_CLICK, btn,
|
|
seat->pointer.x, seat->pointer.y);
|
|
}
|
|
}
|
|
|
|
static const struct wl_pointer_listener pointer_listener = {
|
|
.enter = wl_pointer_enter,
|
|
.leave = wl_pointer_leave,
|
|
.motion = wl_pointer_motion,
|
|
.button = wl_pointer_button,
|
|
.axis = wl_pointer_axis,
|
|
.frame = wl_pointer_frame,
|
|
.axis_source = wl_pointer_axis_source,
|
|
.axis_stop = wl_pointer_axis_stop,
|
|
.axis_discrete = wl_pointer_axis_discrete,
|
|
};
|
|
|
|
static void
|
|
seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
|
|
enum wl_seat_capability caps)
|
|
{
|
|
struct seat *seat = data;
|
|
|
|
if (caps & WL_SEAT_CAPABILITY_POINTER) {
|
|
if (seat->wl_pointer == NULL) {
|
|
assert(seat->pointer.surface == NULL);
|
|
seat->pointer.surface = wl_compositor_create_surface(
|
|
seat->backend->compositor);
|
|
|
|
if (seat->pointer.surface == NULL) {
|
|
LOG_ERR("%s: failed to create pointer surface", seat->name);
|
|
return;
|
|
}
|
|
|
|
seat->wl_pointer = wl_seat_get_pointer(wl_seat);
|
|
wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat);
|
|
}
|
|
} else {
|
|
if (seat->wl_pointer != NULL) {
|
|
wl_pointer_release(seat->wl_pointer);
|
|
wl_surface_destroy(seat->pointer.surface);
|
|
|
|
if (seat->pointer.theme != NULL)
|
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
|
|
|
seat->wl_pointer = NULL;
|
|
seat->pointer.surface = NULL;
|
|
seat->pointer.theme = NULL;
|
|
seat->pointer.cursor = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name)
|
|
{
|
|
struct seat *seat = data;
|
|
free(seat->name);
|
|
seat->name = strdup(name);
|
|
}
|
|
|
|
static const struct wl_seat_listener seat_listener = {
|
|
.capabilities = seat_handle_capabilities,
|
|
.name = seat_handle_name,
|
|
};
|
|
|
|
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;
|
|
mon->width_mm = physical_width;
|
|
mon->height_mm = physical_height;
|
|
}
|
|
|
|
static void
|
|
output_mode(void *data, struct wl_output *wl_output, uint32_t flags,
|
|
int32_t width, int32_t height, int32_t refresh)
|
|
{
|
|
}
|
|
|
|
static void
|
|
output_done(void *data, struct wl_output *wl_output)
|
|
{
|
|
}
|
|
|
|
static bool update_size(struct wayland_backend *backend);
|
|
static void refresh(const struct bar *_bar);
|
|
|
|
static void
|
|
output_scale(void *data, struct wl_output *wl_output, int32_t factor)
|
|
{
|
|
struct monitor *mon = data;
|
|
if (mon->scale == factor)
|
|
return;
|
|
|
|
mon->scale = factor;
|
|
|
|
if (mon->backend->monitor == mon) {
|
|
int old_scale = mon->backend->scale;
|
|
update_size(mon->backend);
|
|
|
|
if (mon->backend->scale != old_scale)
|
|
refresh(mon->backend->bar);
|
|
}
|
|
}
|
|
|
|
#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 ? strdup(name) : NULL;
|
|
}
|
|
#endif
|
|
|
|
#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION)
|
|
static void
|
|
output_description(void *data, struct wl_output *wl_output,
|
|
const char *description)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static const struct wl_output_listener output_listener = {
|
|
.geometry = &output_geometry,
|
|
.mode = &output_mode,
|
|
.done = &output_done,
|
|
.scale = &output_scale,
|
|
#if defined(WL_OUTPUT_NAME_SINCE_VERSION)
|
|
.name = &output_name,
|
|
#endif
|
|
#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION)
|
|
.description = &output_description,
|
|
#endif
|
|
};
|
|
|
|
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)
|
|
{
|
|
struct monitor *mon = data;
|
|
mon->width_px = width;
|
|
mon->height_px = height;
|
|
}
|
|
|
|
static bool create_surface(struct wayland_backend *backend);
|
|
static void destroy_surface(struct wayland_backend *backend);
|
|
|
|
static void
|
|
xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
|
|
{
|
|
const struct monitor *mon = data;
|
|
|
|
LOG_INFO("monitor: %s: %dx%d+%d+%d (%dx%dmm)",
|
|
mon->name, mon->width_px, mon->height_px,
|
|
mon->x, mon->y, mon->width_mm, mon->height_mm);
|
|
|
|
struct wayland_backend *backend = mon->backend;
|
|
struct private *bar = backend->bar->private;
|
|
|
|
const bool is_mapped = backend->monitor != NULL;
|
|
if (is_mapped) {
|
|
assert(backend->surface != NULL);
|
|
assert(backend->last_mapped_monitor == NULL);
|
|
return;
|
|
}
|
|
|
|
const bool output_is_our_configured_monitor = (
|
|
bar->monitor != NULL &&
|
|
mon->name != NULL &&
|
|
strcmp(bar->monitor, mon->name) == 0);
|
|
|
|
const bool output_is_last_mapped = (
|
|
backend->last_mapped_monitor != NULL &&
|
|
mon->name != NULL &&
|
|
strcmp(backend->last_mapped_monitor, mon->name) == 0);
|
|
|
|
if (output_is_our_configured_monitor)
|
|
LOG_DBG("%s: using this monitor (user configured)", mon->name);
|
|
else if (output_is_last_mapped)
|
|
LOG_DBG("%s: using this monitor (last mapped)", mon->name);
|
|
|
|
if (output_is_our_configured_monitor || output_is_last_mapped) {
|
|
/* User specified a monitor, and this is one */
|
|
backend->monitor = mon;
|
|
|
|
free(backend->last_mapped_monitor);
|
|
backend->last_mapped_monitor = NULL;
|
|
|
|
if (create_surface(backend) && update_size(backend)) {
|
|
if (backend->pipe_fds[1] >= 0)
|
|
refresh(backend->bar);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
|
|
const char *name)
|
|
{
|
|
struct monitor *mon = data;
|
|
free(mon->name);
|
|
mon->name = strdup(name);
|
|
}
|
|
|
|
static void
|
|
xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output,
|
|
const char *description)
|
|
{
|
|
}
|
|
|
|
static struct zxdg_output_v1_listener xdg_output_listener = {
|
|
.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,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
static void
|
|
handle_global(void *data, struct wl_registry *registry,
|
|
uint32_t name, const char *interface, uint32_t version)
|
|
{
|
|
LOG_DBG("global: 0x%08x, interface=%s, version=%u", name, interface, version);
|
|
struct wayland_backend *backend = data;
|
|
|
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
|
const uint32_t required = 4;
|
|
if (!verify_iface_version(interface, version, required))
|
|
return;
|
|
|
|
backend->compositor = wl_registry_bind(
|
|
registry, name, &wl_compositor_interface, required);
|
|
}
|
|
|
|
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
|
const uint32_t required = 1;
|
|
if (!verify_iface_version(interface, version, required))
|
|
return;
|
|
|
|
backend->shm = wl_registry_bind(
|
|
registry, name, &wl_shm_interface, required);
|
|
wl_shm_add_listener(backend->shm, &shm_listener, backend);
|
|
}
|
|
|
|
else if (strcmp(interface, wl_output_interface.name) == 0) {
|
|
const uint32_t required = 3;
|
|
if (!verify_iface_version(interface, version, required))
|
|
return;
|
|
|
|
struct wl_output *output = wl_registry_bind(
|
|
registry, name, &wl_output_interface, required);
|
|
|
|
tll_push_back(backend->monitors, ((struct monitor){
|
|
.backend = backend,
|
|
.wl_name = name,
|
|
.output = output}));
|
|
|
|
struct monitor *mon = &tll_back(backend->monitors);
|
|
wl_output_add_listener(output, &output_listener, mon);
|
|
|
|
/*
|
|
* The "output" interface doesn't give us the monitors'
|
|
* identifiers (e.g. "LVDS-1"). Use the XDG output interface
|
|
* for that.
|
|
*/
|
|
|
|
assert(backend->xdg_output_manager != NULL);
|
|
if (backend->xdg_output_manager != NULL) {
|
|
mon->xdg = zxdg_output_manager_v1_get_xdg_output(
|
|
backend->xdg_output_manager, mon->output);
|
|
|
|
zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon);
|
|
}
|
|
}
|
|
|
|
else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
|
|
const uint32_t required = 1;
|
|
if (!verify_iface_version(interface, version, required))
|
|
return;
|
|
|
|
backend->layer_shell = wl_registry_bind(
|
|
registry, name, &zwlr_layer_shell_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;
|
|
|
|
struct wl_seat *seat = wl_registry_bind(
|
|
registry, name, &wl_seat_interface, required);
|
|
assert(seat != NULL);
|
|
|
|
tll_push_back(
|
|
backend->seats, ((struct seat){.backend = backend, .seat = seat, .id = name}));
|
|
|
|
wl_seat_add_listener(seat, &seat_listener, &tll_back(backend->seats));
|
|
}
|
|
|
|
else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
|
const uint32_t required = 2;
|
|
if (!verify_iface_version(interface, version, required))
|
|
return;
|
|
|
|
backend->xdg_output_manager = wl_registry_bind(
|
|
registry, name, &zxdg_output_manager_v1_interface, required);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
|
{
|
|
struct wayland_backend *backend = data;
|
|
|
|
tll_foreach(backend->seats, it) {
|
|
if (it->item.id == name) {
|
|
if (backend->active_seat == &it->item)
|
|
backend->active_seat = NULL;
|
|
|
|
seat_destroy(&it->item);
|
|
return;
|
|
}
|
|
}
|
|
|
|
tll_foreach(backend->monitors, it) {
|
|
struct monitor *mon = &it->item;
|
|
if (mon->wl_name == name) {
|
|
LOG_INFO("%s disconnected/disabled", mon->name);
|
|
|
|
if (mon == backend->monitor) {
|
|
assert(backend->last_mapped_monitor == NULL);
|
|
backend->last_mapped_monitor = strdup(mon->name);
|
|
backend->monitor = NULL;
|
|
}
|
|
|
|
tll_remove(backend->monitors, it);
|
|
return;
|
|
}
|
|
}
|
|
|
|
LOG_WARN("unknown global removed: 0x%08x", name);
|
|
}
|
|
|
|
static const struct wl_registry_listener registry_listener = {
|
|
.global = &handle_global,
|
|
.global_remove = &handle_global_remove,
|
|
};
|
|
|
|
static void
|
|
layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface,
|
|
uint32_t serial, uint32_t w, uint32_t h)
|
|
{
|
|
struct wayland_backend *backend = data;
|
|
backend->width = w * backend->scale;
|
|
backend->height = h * backend->scale;
|
|
|
|
zwlr_layer_surface_v1_ack_configure(surface, serial);
|
|
}
|
|
|
|
static void
|
|
layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface)
|
|
{
|
|
LOG_DBG("layer surface closed by compositor");
|
|
|
|
struct wayland_backend *backend = data;
|
|
destroy_surface(backend);
|
|
}
|
|
|
|
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
|
|
.configure = &layer_surface_configure,
|
|
.closed = &layer_surface_closed,
|
|
};
|
|
|
|
static const struct wl_surface_listener surface_listener;
|
|
|
|
static bool
|
|
create_surface(struct wayland_backend *backend)
|
|
{
|
|
assert(tll_length(backend->monitors) > 0);
|
|
assert(backend->surface == NULL);
|
|
assert(backend->layer_surface == NULL);
|
|
|
|
struct bar *_bar = backend->bar;
|
|
struct private *bar = _bar->private;
|
|
|
|
backend->surface = wl_compositor_create_surface(backend->compositor);
|
|
if (backend->surface == NULL) {
|
|
LOG_ERR("failed to create panel surface");
|
|
return false;
|
|
}
|
|
|
|
wl_surface_add_listener(backend->surface, &surface_listener, backend);
|
|
|
|
enum zwlr_layer_shell_v1_layer layer = bar->layer == BAR_LAYER_BOTTOM
|
|
? ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM
|
|
: ZWLR_LAYER_SHELL_V1_LAYER_TOP;
|
|
|
|
backend->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
|
|
backend->layer_shell, backend->surface,
|
|
backend->monitor != NULL ? backend->monitor->output : NULL,
|
|
layer, "panel");
|
|
|
|
if (backend->layer_surface == NULL) {
|
|
LOG_ERR("failed to create layer shell surface");
|
|
return false;
|
|
}
|
|
|
|
zwlr_layer_surface_v1_add_listener(
|
|
backend->layer_surface, &layer_surface_listener, backend);
|
|
|
|
/* Aligned to top, maximum width */
|
|
enum zwlr_layer_surface_v1_anchor top_or_bottom = bar->location == BAR_TOP
|
|
? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
|
|
: ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
|
|
|
|
zwlr_layer_surface_v1_set_anchor(
|
|
backend->layer_surface,
|
|
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
|
|
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
|
|
top_or_bottom);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
destroy_surface(struct wayland_backend *backend)
|
|
{
|
|
if (backend->layer_surface != NULL)
|
|
zwlr_layer_surface_v1_destroy(backend->layer_surface);
|
|
if (backend->surface != NULL)
|
|
wl_surface_destroy(backend->surface);
|
|
if (backend->frame_callback != NULL)
|
|
wl_callback_destroy(backend->frame_callback);
|
|
|
|
if (backend->pending_buffer != NULL)
|
|
backend->pending_buffer->busy = false;
|
|
if (backend->next_buffer != NULL)
|
|
backend->next_buffer->busy = false;
|
|
|
|
backend->layer_surface = NULL;
|
|
backend->surface = NULL;
|
|
backend->frame_callback = NULL;
|
|
backend->pending_buffer = NULL;
|
|
backend->next_buffer = NULL;
|
|
|
|
backend->scale = 0;
|
|
backend->render_scheduled = false;
|
|
}
|
|
|
|
static void
|
|
buffer_release(void *data, struct wl_buffer *wl_buffer)
|
|
{
|
|
//printf("buffer release\n");
|
|
struct buffer *buffer = data;
|
|
assert(buffer->busy);
|
|
buffer->busy = false;
|
|
}
|
|
|
|
static const struct wl_buffer_listener buffer_listener = {
|
|
.release = &buffer_release,
|
|
};
|
|
|
|
static struct buffer *
|
|
get_buffer(struct wayland_backend *backend)
|
|
{
|
|
tll_foreach(backend->buffers, it) {
|
|
if (!it->item.busy && it->item.width == backend->width && it->item.height == backend->height) {
|
|
it->item.busy = true;
|
|
return &it->item;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No existing buffer available. Create a new one by:
|
|
*
|
|
* 1. open a memory backed "file" with memfd_create()
|
|
* 2. mmap() the memory file, to be used by the pixman image
|
|
* 3. create a wayland shm buffer for the same memory file
|
|
*
|
|
* The pixman image and the wayland buffer are now sharing memory.
|
|
*/
|
|
|
|
int pool_fd = -1;
|
|
void *mmapped = NULL;
|
|
size_t size = 0;
|
|
|
|
struct wl_shm_pool *pool = NULL;
|
|
struct wl_buffer *buf = NULL;
|
|
|
|
pixman_image_t *pix = NULL;
|
|
|
|
/* Backing memory for SHM */
|
|
#if defined(MEMFD_CREATE)
|
|
/*
|
|
* Older kernels reject MFD_NOEXEC_SEAL with EINVAL. Try first
|
|
* *with* it, and if that fails, try again *without* it.
|
|
*/
|
|
errno = 0;
|
|
pool_fd = memfd_create(
|
|
"yambar-wayland-shm-buffer-pool",
|
|
MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL);
|
|
|
|
if (pool_fd < 0 && errno == EINVAL) {
|
|
pool_fd = memfd_create(
|
|
"yambar-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
|
}
|
|
#elif defined(__FreeBSD__)
|
|
// memfd_create on FreeBSD 13 is SHM_ANON without sealing support
|
|
pool_fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600);
|
|
#else
|
|
char name[] = "/tmp/yambar-wayland-shm-buffer-pool-XXXXXX";
|
|
pool_fd = mkostemp(name, O_CLOEXEC);
|
|
unlink(name);
|
|
#endif
|
|
if (pool_fd == -1) {
|
|
LOG_ERRNO("failed to create SHM backing memory file");
|
|
goto err;
|
|
}
|
|
|
|
/* Total size */
|
|
const uint32_t stride = stride_for_format_and_width(
|
|
PIXMAN_a8r8g8b8, backend->width);
|
|
|
|
size = stride * backend->height;
|
|
if (ftruncate(pool_fd, size) == -1) {
|
|
LOG_ERR("failed to truncate SHM pool");
|
|
goto err;
|
|
}
|
|
|
|
mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, pool_fd, 0);
|
|
if (mmapped == MAP_FAILED) {
|
|
LOG_ERR("failed to mmap SHM backing memory file");
|
|
goto err;
|
|
}
|
|
|
|
#if defined(MEMFD_CREATE)
|
|
/* Seal file - we no longer allow any kind of resizing */
|
|
/* TODO: wayland mmaps(PROT_WRITE), for some unknown reason, hence we cannot use F_SEAL_FUTURE_WRITE */
|
|
if (fcntl(pool_fd, F_ADD_SEALS,
|
|
F_SEAL_GROW | F_SEAL_SHRINK | /*F_SEAL_FUTURE_WRITE |*/ F_SEAL_SEAL) < 0)
|
|
{
|
|
LOG_ERRNO("failed to seal SHM backing memory file");
|
|
/* This is not a fatal error */
|
|
}
|
|
#endif
|
|
|
|
pool = wl_shm_create_pool(backend->shm, pool_fd, size);
|
|
if (pool == NULL) {
|
|
LOG_ERR("failed to create SHM pool");
|
|
goto err;
|
|
}
|
|
|
|
buf = wl_shm_pool_create_buffer(
|
|
pool, 0, backend->width, backend->height, stride, WL_SHM_FORMAT_ARGB8888);
|
|
if (buf == NULL) {
|
|
LOG_ERR("failed to create SHM buffer");
|
|
goto err;
|
|
}
|
|
|
|
/* We use the entire pool for our single buffer */
|
|
wl_shm_pool_destroy(pool); pool = NULL;
|
|
close(pool_fd); pool_fd = -1;
|
|
|
|
pix = pixman_image_create_bits_no_clear(
|
|
PIXMAN_a8r8g8b8, backend->width, backend->height, (uint32_t *)mmapped, stride);
|
|
if (pix == NULL) {
|
|
LOG_ERR("failed to create pixman image");
|
|
goto err;
|
|
}
|
|
|
|
/* Push to list of available buffers, but marked as 'busy' */
|
|
tll_push_back(
|
|
backend->buffers,
|
|
((struct buffer){
|
|
.busy = true,
|
|
.width = backend->width,
|
|
.height = backend->height,
|
|
.size = size,
|
|
.mmapped = mmapped,
|
|
.wl_buf = buf,
|
|
.pix = pix,
|
|
})
|
|
);
|
|
|
|
struct buffer *ret = &tll_back(backend->buffers);
|
|
wl_buffer_add_listener(ret->wl_buf, &buffer_listener, ret);
|
|
return ret;
|
|
|
|
err:
|
|
if (pix != NULL)
|
|
pixman_image_unref(pix);
|
|
if (buf != NULL)
|
|
wl_buffer_destroy(buf);
|
|
if (pool != NULL)
|
|
wl_shm_pool_destroy(pool);
|
|
if (pool_fd != -1)
|
|
close(pool_fd);
|
|
if (mmapped != NULL)
|
|
munmap(mmapped, size);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
guess_scale(const struct wayland_backend *backend)
|
|
{
|
|
if (tll_length(backend->monitors) == 0)
|
|
return 1;
|
|
|
|
bool all_have_same_scale = true;
|
|
int last_scale = -1;
|
|
|
|
tll_foreach(backend->monitors, it) {
|
|
if (last_scale == -1)
|
|
last_scale = it->item.scale;
|
|
else if (last_scale != it->item.scale) {
|
|
all_have_same_scale = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_have_same_scale) {
|
|
assert(last_scale >= 1);
|
|
return last_scale;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool
|
|
update_size(struct wayland_backend *backend)
|
|
{
|
|
struct bar *_bar = backend->bar;
|
|
struct private *bar = _bar->private;
|
|
|
|
const struct monitor *mon = backend->monitor;
|
|
const int scale = mon != NULL ? mon->scale : guess_scale(backend);
|
|
|
|
assert(backend->surface != NULL);
|
|
|
|
if (backend->scale == scale)
|
|
return true;
|
|
|
|
backend->scale = scale;
|
|
|
|
int height = bar->height_with_border;
|
|
height /= scale;
|
|
height *= scale;
|
|
bar->height = height - bar->border.top_width - bar->border.bottom_width;
|
|
bar->height_with_border = height;
|
|
|
|
zwlr_layer_surface_v1_set_size(
|
|
backend->layer_surface, 0, bar->height_with_border / scale);
|
|
zwlr_layer_surface_v1_set_exclusive_zone(
|
|
backend->layer_surface,
|
|
(bar->height_with_border + (bar->location == BAR_TOP
|
|
? bar->border.bottom_margin
|
|
: bar->border.top_margin))
|
|
/ scale);
|
|
|
|
zwlr_layer_surface_v1_set_margin(
|
|
backend->layer_surface,
|
|
bar->border.top_margin / scale,
|
|
bar->border.right_margin / scale,
|
|
bar->border.bottom_margin / scale,
|
|
bar->border.left_margin / scale
|
|
);
|
|
|
|
/* Trigger a 'configure' event, after which we'll have the width */
|
|
wl_surface_commit(backend->surface);
|
|
wl_display_roundtrip(backend->display);
|
|
|
|
if (backend->width == -1 ||
|
|
backend->height != bar->height_with_border) {
|
|
LOG_ERR("failed to get panel width");
|
|
return false;
|
|
}
|
|
|
|
bar->width = backend->width;
|
|
|
|
/* Reload buffers */
|
|
if (backend->next_buffer != NULL)
|
|
backend->next_buffer->busy = false;
|
|
backend->next_buffer = get_buffer(backend);
|
|
assert(backend->next_buffer != NULL && backend->next_buffer->busy);
|
|
bar->pix = backend->next_buffer->pix;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
setup(struct bar *_bar)
|
|
{
|
|
struct private *bar = _bar->private;
|
|
struct wayland_backend *backend = bar->backend.data;
|
|
|
|
backend->bar = _bar;
|
|
|
|
backend->display = wl_display_connect(NULL);
|
|
if (backend->display == NULL) {
|
|
LOG_ERR("failed to connect to wayland; no compositor running?");
|
|
return false;
|
|
}
|
|
|
|
backend->registry = wl_display_get_registry(backend->display);
|
|
if (backend->registry == NULL) {
|
|
LOG_ERR("failed to get wayland registry");
|
|
return false;
|
|
}
|
|
|
|
/* Globals */
|
|
wl_registry_add_listener(backend->registry, ®istry_listener, backend);
|
|
wl_display_roundtrip(backend->display);
|
|
|
|
if (backend->compositor == NULL) {
|
|
LOG_ERR("no compositor");
|
|
return false;
|
|
}
|
|
if (backend->layer_shell == NULL) {
|
|
LOG_ERR("no layer shell interface");
|
|
return false;
|
|
}
|
|
if (backend->shm == NULL) {
|
|
LOG_ERR("no shared memory buffers interface");
|
|
return false;
|
|
}
|
|
|
|
if (tll_length(backend->monitors) == 0) {
|
|
LOG_ERR("no monitors");
|
|
return false;
|
|
}
|
|
|
|
/* Trigger listeners registered in previous roundtrip */
|
|
wl_display_roundtrip(backend->display);
|
|
|
|
if (backend->surface == NULL && backend->layer_surface == NULL) {
|
|
if (!create_surface(backend))
|
|
return false;
|
|
|
|
if (!update_size(backend))
|
|
return false;
|
|
}
|
|
|
|
assert(backend->monitor == NULL ||
|
|
backend->width / backend->monitor->scale <= backend->monitor->width_px);
|
|
|
|
if (pipe2(backend->pipe_fds, O_CLOEXEC | O_NONBLOCK) == -1) {
|
|
LOG_ERRNO("failed to create pipe");
|
|
return false;
|
|
}
|
|
|
|
backend->render_scheduled = false;
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
cleanup(struct bar *_bar)
|
|
{
|
|
struct private *bar = _bar->private;
|
|
struct wayland_backend *backend = bar->backend.data;
|
|
|
|
if (backend->pipe_fds[0] >= 0)
|
|
close(backend->pipe_fds[0]);
|
|
if (backend->pipe_fds[1] >= 0)
|
|
close(backend->pipe_fds[1]);
|
|
|
|
tll_foreach(backend->monitors, it) {
|
|
struct monitor *mon = &it->item;
|
|
free(mon->name);
|
|
|
|
if (mon->xdg != NULL)
|
|
zxdg_output_v1_destroy(mon->xdg);
|
|
if (mon->output != NULL)
|
|
wl_output_release(mon->output);
|
|
tll_remove(backend->monitors, it);
|
|
}
|
|
free(backend->last_mapped_monitor);
|
|
|
|
if (backend->xdg_output_manager != NULL)
|
|
zxdg_output_manager_v1_destroy(backend->xdg_output_manager);
|
|
|
|
tll_foreach(backend->seats, it)
|
|
seat_destroy(&it->item);
|
|
tll_free(backend->seats);
|
|
|
|
destroy_surface(backend);
|
|
|
|
tll_foreach(backend->buffers, it) {
|
|
if (it->item.wl_buf != NULL)
|
|
wl_buffer_destroy(it->item.wl_buf);
|
|
if (it->item.pix != NULL)
|
|
pixman_image_unref(it->item.pix);
|
|
|
|
munmap(it->item.mmapped, it->item.size);
|
|
tll_remove(backend->buffers, it);
|
|
}
|
|
|
|
if (backend->layer_shell != NULL)
|
|
zwlr_layer_shell_v1_destroy(backend->layer_shell);
|
|
if (backend->compositor != NULL)
|
|
wl_compositor_destroy(backend->compositor);
|
|
if (backend->shm != NULL)
|
|
wl_shm_destroy(backend->shm);
|
|
if (backend->registry != NULL)
|
|
wl_registry_destroy(backend->registry);
|
|
if (backend->display != NULL) {
|
|
wl_display_flush(backend->display);
|
|
wl_display_disconnect(backend->display);
|
|
}
|
|
|
|
/* Destroyed when freeing buffer list */
|
|
bar->pix = NULL;
|
|
|
|
}
|
|
|
|
static void
|
|
loop(struct bar *_bar,
|
|
void (*expose)(const struct bar *bar),
|
|
void (*on_mouse)(struct bar *bar, enum mouse_event event,
|
|
enum mouse_button btn, int x, int y))
|
|
{
|
|
struct private *bar = _bar->private;
|
|
struct wayland_backend *backend = bar->backend.data;
|
|
bool send_abort_to_modules = true;
|
|
|
|
pthread_setname_np(pthread_self(), "bar(wayland)");
|
|
|
|
backend->bar_on_mouse = on_mouse;
|
|
|
|
while (wl_display_prepare_read(backend->display) != 0) {
|
|
if (wl_display_dispatch_pending(backend->display) < 0) {
|
|
LOG_ERRNO("failed to dispatch pending Wayland events");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
wl_display_flush(backend->display);
|
|
|
|
while (true) {
|
|
struct pollfd fds[] = {
|
|
{.fd = _bar->abort_fd, .events = POLLIN},
|
|
{.fd = wl_display_get_fd(backend->display), .events = POLLIN},
|
|
{.fd = backend->pipe_fds[0], .events = POLLIN},
|
|
};
|
|
|
|
poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
|
if (fds[0].revents & POLLIN) {
|
|
/* Already done by the bar */
|
|
send_abort_to_modules = false;
|
|
break;
|
|
}
|
|
|
|
if (fds[1].revents & POLLHUP) {
|
|
LOG_INFO("disconnected from wayland");
|
|
break;
|
|
}
|
|
|
|
if (fds[2].revents & POLLIN) {
|
|
bool do_expose = false;
|
|
|
|
/* Coalesce “refresh” commands */
|
|
size_t count = 0;
|
|
while (true) {
|
|
uint8_t command;
|
|
ssize_t r = read(backend->pipe_fds[0], &command, sizeof(command));
|
|
if (r < 0 && errno == EAGAIN)
|
|
break;
|
|
|
|
if (r != sizeof(command)) {
|
|
LOG_ERRNO("failed to read from command pipe");
|
|
goto out;
|
|
}
|
|
|
|
assert(command == 1);
|
|
if (command == 1) {
|
|
count++;
|
|
do_expose = true;
|
|
}
|
|
}
|
|
|
|
LOG_DBG("coalesced %zu expose commands", count);
|
|
if (do_expose)
|
|
expose(_bar);
|
|
}
|
|
|
|
if (fds[1].revents & POLLIN) {
|
|
if (wl_display_read_events(backend->display) < 0) {
|
|
LOG_ERRNO("failed to read events from the Wayland socket");
|
|
goto out;
|
|
}
|
|
|
|
while (wl_display_prepare_read(backend->display) != 0) {
|
|
if (wl_display_dispatch_pending(backend->display) < 0) {
|
|
LOG_ERRNO("failed to dispatch pending Wayland events");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
wl_display_flush(backend->display);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (!send_abort_to_modules)
|
|
return;
|
|
|
|
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
|
!= sizeof(uint64_t))
|
|
{
|
|
LOG_ERRNO("failed to signal abort to modules");
|
|
}
|
|
|
|
//wl_display_cancel_read(backend->display);
|
|
}
|
|
|
|
static void
|
|
surface_enter(void *data, struct wl_surface *wl_surface,
|
|
struct wl_output *wl_output)
|
|
{
|
|
struct wayland_backend *backend = data;
|
|
|
|
free(backend->last_mapped_monitor);
|
|
backend->last_mapped_monitor = NULL;
|
|
|
|
tll_foreach(backend->monitors, it) {
|
|
struct monitor *mon = &it->item;
|
|
|
|
if (mon->output != wl_output)
|
|
continue;
|
|
|
|
if (backend->monitor != mon) {
|
|
backend->monitor = mon;
|
|
|
|
int old_scale = backend->scale;
|
|
update_size(backend);
|
|
|
|
if (backend->scale != old_scale)
|
|
refresh(backend->bar);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
surface_leave(void *data, struct wl_surface *wl_surface,
|
|
struct wl_output *wl_output)
|
|
{
|
|
struct wayland_backend *backend = data;
|
|
const struct monitor *mon = backend->monitor;
|
|
|
|
assert(mon != NULL);
|
|
assert(mon->output == wl_output);
|
|
|
|
backend->monitor = NULL;
|
|
|
|
assert(backend->last_mapped_monitor == NULL);
|
|
backend->last_mapped_monitor = mon->name != NULL ? strdup(mon->name) : NULL;
|
|
}
|
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
.enter = &surface_enter,
|
|
.leave = &surface_leave,
|
|
};
|
|
|
|
static void frame_callback(
|
|
void *data, struct wl_callback *wl_callback, uint32_t callback_data);
|
|
|
|
static const struct wl_callback_listener frame_listener = {
|
|
.done = &frame_callback,
|
|
};
|
|
|
|
static void
|
|
frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
|
|
{
|
|
//printf("frame callback\n");
|
|
struct private *bar = data;
|
|
struct wayland_backend *backend = bar->backend.data;
|
|
|
|
backend->render_scheduled = false;
|
|
|
|
assert(wl_callback == backend->frame_callback);
|
|
wl_callback_destroy(wl_callback);
|
|
backend->frame_callback = NULL;
|
|
|
|
if (backend->pending_buffer != NULL) {
|
|
struct buffer *buffer = backend->pending_buffer;
|
|
assert(buffer->busy);
|
|
|
|
wl_surface_set_buffer_scale(backend->surface, backend->scale);
|
|
wl_surface_attach(backend->surface, buffer->wl_buf, 0, 0);
|
|
wl_surface_damage(backend->surface, 0, 0, backend->width, backend->height);
|
|
|
|
struct wl_callback *cb = wl_surface_frame(backend->surface);
|
|
wl_callback_add_listener(cb, &frame_listener, bar);
|
|
wl_surface_commit(backend->surface);
|
|
wl_display_flush(backend->display);
|
|
|
|
backend->frame_callback = cb;
|
|
backend->pending_buffer = NULL;
|
|
backend->render_scheduled = true;
|
|
} else
|
|
;//printf("nothing more to do\n");
|
|
}
|
|
|
|
static void
|
|
commit(const struct bar *_bar)
|
|
{
|
|
struct private *bar = _bar->private;
|
|
struct wayland_backend *backend = bar->backend.data;
|
|
|
|
//printf("commit: %dxl%d\n", backend->width, backend->height);
|
|
|
|
if (backend->next_buffer == NULL)
|
|
return;
|
|
|
|
assert(backend->next_buffer != NULL);
|
|
assert(backend->next_buffer->busy);
|
|
|
|
if (backend->render_scheduled) {
|
|
//printf("already scheduled\n");
|
|
|
|
if (backend->pending_buffer != NULL)
|
|
backend->pending_buffer->busy = false;
|
|
|
|
backend->pending_buffer = backend->next_buffer;
|
|
backend->next_buffer = NULL;
|
|
} else {
|
|
|
|
//printf("scheduling new frame callback\n");
|
|
struct buffer *buffer = backend->next_buffer;
|
|
assert(buffer->busy);
|
|
|
|
wl_surface_set_buffer_scale(backend->surface, backend->scale);
|
|
wl_surface_attach(backend->surface, buffer->wl_buf, 0, 0);
|
|
wl_surface_damage(backend->surface, 0, 0, backend->width, backend->height);
|
|
|
|
struct wl_callback *cb = wl_surface_frame(backend->surface);
|
|
wl_callback_add_listener(cb, &frame_listener, bar);
|
|
wl_surface_commit(backend->surface);
|
|
wl_display_flush(backend->display);
|
|
|
|
backend->render_scheduled = true;
|
|
backend->frame_callback = cb;
|
|
}
|
|
|
|
backend->next_buffer = get_buffer(backend);
|
|
assert(backend->next_buffer != NULL && backend->next_buffer->busy);
|
|
bar->pix = backend->next_buffer->pix;
|
|
}
|
|
|
|
static void
|
|
refresh(const struct bar *_bar)
|
|
{
|
|
const struct private *bar = _bar->private;
|
|
const struct wayland_backend *backend = bar->backend.data;
|
|
|
|
if (write(backend->pipe_fds[1], &(uint8_t){1}, sizeof(uint8_t))
|
|
!= sizeof(uint8_t))
|
|
{
|
|
LOG_ERRNO("failed to signal 'refresh' to main thread");
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_cursor(struct bar *_bar, const char *cursor)
|
|
{
|
|
struct private *bar = _bar->private;
|
|
struct wayland_backend *backend = bar->backend.data;
|
|
|
|
struct seat *seat = backend->active_seat;
|
|
if (seat == NULL || seat->pointer.theme == NULL)
|
|
return;
|
|
|
|
if (seat->pointer.xcursor != NULL && strcmp(seat->pointer.xcursor, cursor) == 0)
|
|
return;
|
|
|
|
seat->pointer.xcursor = cursor;
|
|
|
|
seat->pointer.cursor = wl_cursor_theme_get_cursor(
|
|
seat->pointer.theme, cursor);
|
|
|
|
if (seat->pointer.cursor == NULL) {
|
|
LOG_ERR("%s: failed to load cursor '%s'", seat->name, cursor);
|
|
return;
|
|
}
|
|
|
|
update_cursor_surface(backend, seat);
|
|
}
|
|
|
|
static const char *
|
|
bar_output_name(const struct bar *_bar)
|
|
{
|
|
const struct private *bar = _bar->private;
|
|
const struct wayland_backend *backend = bar->backend.data;
|
|
|
|
return backend->monitor != NULL ? backend->monitor->name : NULL;
|
|
}
|
|
|
|
const struct backend wayland_backend_iface = {
|
|
.setup = &setup,
|
|
.cleanup = &cleanup,
|
|
.loop = &loop,
|
|
.commit = &commit,
|
|
.refresh = &refresh,
|
|
.set_cursor = &set_cursor,
|
|
.output_name = &bar_output_name,
|
|
};
|