bar/wayland: handle layer surface being closed

If the output we’re mapped on is disabled (or disconnected), the
compositor will unmap us.

Up until now, our response was to simply shutdown.

Now, we destroy the surface, remove all pending rendering buffers, and
all further calls to commit() will return immediately, without doing
anything.

If the user has configured a specific monitor to use, we wait for that
output to come back. When it does, we re-create the layer surface and
then we’re up and running again.

Bars running on the “default” monitor are handled in a similar
way. Since we don’t have an output name from the configuration, we
instead store the name of the output we were mapped on, when we’re
either unmapped from that output, or that output global is destroyed.

As soon as we see that output come back, we re-create the layer
surface.
This commit is contained in:
Daniel Eklöf 2021-12-19 21:22:51 +01:00
parent 37e7c2b2c1
commit 2a0a722c13
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F

View file

@ -46,6 +46,7 @@ struct monitor {
struct wl_output *output;
struct zxdg_output_v1 *xdg;
char *name;
uint32_t wl_name;
int x;
int y;
@ -96,6 +97,7 @@ struct wayland_backend {
tll(struct monitor) monitors;
const struct monitor *monitor;
char *last_mapped_monitor;
int scale;
@ -113,6 +115,7 @@ struct wayland_backend {
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;
@ -519,6 +522,9 @@ xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output,
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)
{
@ -531,13 +537,40 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
struct wayland_backend *backend = mon->backend;
struct private *bar = backend->bar->private;
if (bar->monitor != NULL && mon->name != NULL &&
strcmp(bar->monitor, mon->name) == 0)
{
/* User specified a monitor, and this is one */
backend->monitor = mon;
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
@ -610,6 +643,7 @@ handle_global(void *data, struct wl_registry *registry,
tll_push_back(backend->monitors, ((struct monitor){
.backend = backend,
.wl_name = name,
.output = output}));
struct monitor *mon = &tll_back(backend->monitors);
@ -679,9 +713,23 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
}
}
LOG_WARN("unknown global removed: 0x%08x", name);
tll_foreach(backend->monitors, it) {
struct monitor *mon = &it->item;
if (mon->wl_name == name) {
LOG_INFO("%s disconnected/disabled", mon->name);
/* TODO: need to handle displays and seats */
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 = {
@ -703,22 +751,10 @@ layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface,
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;
/*
* Called e.g. when an output is disabled. We don't get a
* corresponding event if/when that same output re-appears. So,
* for now, we simply shut down. In the future, we _could_ maybe
* destroy the surface, listen for output events and re-create the
* surface if the same output re-appears.
*/
LOG_WARN("compositor requested surface be closed - shutting down");
if (write(backend->bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
!= sizeof(uint64_t))
{
LOG_ERRNO("failed to signal abort to modules");
}
destroy_surface(backend);
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
@ -726,6 +762,82 @@ static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.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)
{
@ -892,6 +1004,8 @@ update_size(struct wayland_backend *backend)
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;
@ -942,8 +1056,6 @@ update_size(struct wayland_backend *backend)
return true;
}
static const struct wl_surface_listener surface_listener;
static bool
setup(struct bar *_bar)
{
@ -989,45 +1101,14 @@ setup(struct bar *_bar)
/* Trigger listeners registered in previous roundtrip */
wl_display_roundtrip(backend->display);
backend->surface = wl_compositor_create_surface(backend->compositor);
if (backend->surface == NULL) {
LOG_ERR("failed to create panel surface");
return false;
if (backend->surface == NULL && backend->layer_surface == NULL) {
if (!create_surface(backend))
return false;
if (!update_size(backend))
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);
if (!update_size(backend))
return false;
assert(backend->monitor == NULL ||
backend->width / backend->monitor->scale <= backend->monitor->width_px);
@ -1051,16 +1132,6 @@ cleanup(struct bar *_bar)
if (backend->pipe_fds[1] >= 0)
close(backend->pipe_fds[1]);
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);
}
tll_foreach(backend->monitors, it) {
struct monitor *mon = &it->item;
free(mon->name);
@ -1071,6 +1142,7 @@ cleanup(struct bar *_bar)
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);
@ -1079,12 +1151,20 @@ cleanup(struct bar *_bar)
seat_destroy(&it->item);
tll_free(backend->seats);
if (backend->layer_surface != NULL)
zwlr_layer_surface_v1_destroy(backend->layer_surface);
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->surface != NULL)
wl_surface_destroy(backend->surface);
if (backend->compositor != NULL)
wl_compositor_destroy(backend->compositor);
if (backend->shm != NULL)
@ -1209,7 +1289,15 @@ 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 = {
@ -1233,7 +1321,9 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
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;
@ -1248,6 +1338,7 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
wl_surface_commit(backend->surface);
wl_display_flush(backend->display);
backend->frame_callback = cb;
backend->pending_buffer = NULL;
backend->render_scheduled = true;
} else
@ -1262,6 +1353,9 @@ commit(const struct bar *_bar)
//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);
@ -1289,6 +1383,7 @@ commit(const struct bar *_bar)
wl_display_flush(backend->display);
backend->render_scheduled = true;
backend->frame_callback = cb;
}
backend->next_buffer = get_buffer(backend);