Skip to content

Commit

Permalink
wayland: support xdg-shell as a fallback
Browse files Browse the repository at this point in the history
Support the xdg-shell wayland protocol as a fallback in case the
wlr-layer-shell-unstable-v1 protocol is not present. This allows running
dunst on wayland compositors not supporting the layer shell protocol.

Note that the xdg-shell protocol doesn't allow dunst to specify where
it should be displayed on the screen. Therefore it is only chosen, when
the layer-shell protocol is not available.
  • Loading branch information
pslldq committed Jan 10, 2025
1 parent fac745b commit a1bae20
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 46 deletions.
213 changes: 167 additions & 46 deletions src/wayland/wl.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,47 @@ static void layer_surface_handle_configure(void *data,
send_frame();
}

static void layer_surface_handle_closed(void *data,
struct zwlr_layer_surface_v1 *surface) {
LOG_I("Destroying layer");
if (ctx.layer_surface)
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
ctx.layer_surface = NULL;
static void xdg_surface_handle_configure(void *data,
struct xdg_surface *surface,
uint32_t serial) {
xdg_surface_ack_configure(ctx.xdg_surface, serial);

if (ctx.configured) {
wl_surface_commit(ctx.surface);
return;
}

ctx.configured = true;

send_frame();
}

static void xdg_toplevel_handle_configure(void *data,
struct xdg_toplevel *xdg_toplevel,
int32_t width,
int32_t height,
struct wl_array *states) {
if (width == ctx.width && height == ctx.height) {
return;
}

ctx.configured = false;
ctx.width = width;
ctx.height = height;
}

static void xdg_toplevel_wm_capabilities(void *data,
struct xdg_toplevel *xdg_toplevel,
struct wl_array *capabilities) {
}

static void xdg_toplevel_handle_configure_bounds (void *data,
struct xdg_toplevel *xdg_toplevel,
int32_t width,
int32_t height) {
}

static void surface_handle_closed(void) {
if (ctx.surface)
wl_surface_destroy(ctx.surface);
ctx.surface = NULL;
Expand All @@ -111,11 +145,57 @@ static void layer_surface_handle_closed(void *data,
}
}

static void layer_surface_handle_closed(void *data,
struct zwlr_layer_surface_v1 *surface) {
LOG_I("Destroying layer");
if (ctx.layer_surface)
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
ctx.layer_surface = NULL;

surface_handle_closed();
}

static void xdg_toplevel_handle_close(void *data,
struct xdg_toplevel *surface) {
LOG_I("Destroying layer");
if (ctx.xdg_toplevel)
xdg_toplevel_destroy(ctx.xdg_toplevel);
ctx.xdg_toplevel = NULL;
if (ctx.xdg_surface)
xdg_surface_destroy(ctx.xdg_surface);
ctx.xdg_surface = NULL;

surface_handle_closed();
}

static void xdg_wm_base_handle_ping(void *data,
struct xdg_wm_base *xdg_wm_base,
uint32_t serial) {
xdg_wm_base_pong(ctx.xdg_shell, serial);
}


static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layer_surface_handle_configure,
.closed = layer_surface_handle_closed,
};

static const struct xdg_wm_base_listener xdg_wm_base_listener = {
.ping = xdg_wm_base_handle_ping,
};

static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_handle_configure,
.close = xdg_toplevel_handle_close,
.configure_bounds = xdg_toplevel_handle_configure_bounds,
.wm_capabilities = xdg_toplevel_wm_capabilities,
};


// Warning, can return NULL
static struct dunst_output *get_configured_output(void) {
int n = 0;
Expand Down Expand Up @@ -164,6 +244,10 @@ static void handle_global(void *data, struct wl_registry *registry,
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
ctx.layer_shell = wl_registry_bind(registry, name,
&zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
ctx.xdg_shell = wl_registry_bind(registry, name,
&xdg_wm_base_interface, 5);
xdg_wm_base_add_listener(ctx.xdg_shell, &xdg_wm_base_listener, NULL);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
create_seat(registry, name, version);
LOG_D("Binding to seat %i", name);
Expand Down Expand Up @@ -262,8 +346,12 @@ bool wl_init(void) {
return false;
}
if (ctx.layer_shell == NULL) {
LOG_W("compositor doesn't support zwlr_layer_shell_v1");
return false;
if (ctx.xdg_shell == NULL) {
LOG_W("compositor doesn't support zwlr_layer_shell_v1 or xdg_shell");
return false;
} else {
LOG_W("compositor doesn't support zwlr_layer_shell_v1, falling back to xdg_shell. Notification window position will be set by the compositor.");
}
}
if (wl_list_empty(&ctx.seats)) {
LOG_W("no seat was found, so dunst cannot see input");
Expand Down Expand Up @@ -334,6 +422,12 @@ void wl_deinit(void) {
if (ctx.layer_surface != NULL) {
g_clear_pointer(&ctx.layer_surface, zwlr_layer_surface_v1_destroy);
}
if (ctx.xdg_toplevel != NULL) {
g_clear_pointer(&ctx.xdg_toplevel, xdg_toplevel_destroy);
}
if (ctx.xdg_surface != NULL) {
g_clear_pointer(&ctx.xdg_surface, xdg_surface_destroy);
}
if (ctx.surface != NULL) {
g_clear_pointer(&ctx.surface, wl_surface_destroy);
}
Expand Down Expand Up @@ -371,6 +465,9 @@ void wl_deinit(void) {
if (ctx.layer_shell)
g_clear_pointer(&ctx.layer_shell, zwlr_layer_shell_v1_destroy);

if (ctx.xdg_shell)
g_clear_pointer(&ctx.xdg_shell, xdg_wm_base_destroy);

if (ctx.compositor)
g_clear_pointer(&ctx.compositor, wl_compositor_destroy);

Expand Down Expand Up @@ -411,6 +508,14 @@ static void send_frame(void) {
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
ctx.layer_surface = NULL;
}
if (ctx.xdg_toplevel != NULL) {
xdg_toplevel_destroy(ctx.xdg_toplevel);
ctx.xdg_toplevel = NULL;
}
if (ctx.xdg_surface != NULL) {
xdg_surface_destroy(ctx.xdg_surface);
ctx.xdg_surface = NULL;
}
if (ctx.surface != NULL) {
wl_surface_destroy(ctx.surface);
ctx.surface = NULL;
Expand Down Expand Up @@ -443,20 +548,32 @@ static void send_frame(void) {
// If we've made it here, there is something to draw. If the surface
// doesn't exist (this is the first notification, or we moved to a
// different output), we need to create it.
if (ctx.layer_surface == NULL) {
struct wl_output *wl_output = NULL;
if (output != NULL) {
wl_output = output->wl_output;
}
if (ctx.layer_surface == NULL && ctx.xdg_surface == NULL) {
ctx.layer_surface_output = output;
ctx.surface = wl_compositor_create_surface(ctx.compositor);
wl_surface_add_listener(ctx.surface, &surface_listener, NULL);

ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
ctx.layer_shell, ctx.surface, wl_output,
settings.layer, "notifications");
zwlr_layer_surface_v1_add_listener(ctx.layer_surface,
&layer_surface_listener, NULL);
if (ctx.layer_shell) {
struct wl_output *wl_output = NULL;
if (output != NULL) {
wl_output = output->wl_output;
}

ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
ctx.layer_shell, ctx.surface, wl_output,
settings.layer, "notifications");
zwlr_layer_surface_v1_add_listener(ctx.layer_surface,
&layer_surface_listener, NULL);
} else {
ctx.xdg_surface = xdg_wm_base_get_xdg_surface(
ctx.xdg_shell, ctx.surface);
xdg_surface_add_listener(ctx.xdg_surface, &xdg_surface_listener, NULL);

ctx.xdg_toplevel = xdg_surface_get_toplevel(ctx.xdg_surface);
xdg_toplevel_set_title(ctx.xdg_toplevel, "Dunst");
xdg_toplevel_set_app_id(ctx.xdg_toplevel, "org.knopwob.dunst");
xdg_toplevel_add_listener(ctx.xdg_toplevel, &xdg_toplevel_listener, NULL);
}

// Because we're creating a new surface, we aren't going to draw
// anything into it during this call. We don't know what size the
Expand All @@ -468,46 +585,50 @@ static void send_frame(void) {
// block to let it set the size for us.
}

assert(ctx.layer_surface);
assert(ctx.layer_surface || ctx.xdg_surface);

// We now want to resize the surface if it isn't the right size. If the
// surface is brand new, it doesn't even have a size yet. If it already
// exists, we might need to resize if the list of notifications has changed
// since the last time we drew.
if (ctx.height != height || ctx.width != width) {
struct dimensions dim = ctx.cur_dim;
// Set window size
zwlr_layer_surface_v1_set_size(ctx.layer_surface,
if (ctx.layer_surface) {
// Set window size
zwlr_layer_surface_v1_set_size(ctx.layer_surface,
dim.w, dim.h);

// Put the window at the right position
zwlr_layer_surface_v1_set_anchor(ctx.layer_surface,
settings.origin);
zwlr_layer_surface_v1_set_margin(ctx.layer_surface,
// Offsets where no anchors are specified are
// ignored. We can safely assume the offset is
// positive.
settings.offset.y, // top
settings.offset.x, // right
settings.offset.y, // bottom
settings.offset.x);// left
// Put the window at the right position
zwlr_layer_surface_v1_set_anchor(ctx.layer_surface,
settings.origin);
zwlr_layer_surface_v1_set_margin(ctx.layer_surface,
// Offsets where no anchors are specified are
// ignored. We can safely assume the offset is
// positive.
settings.offset.y, // top
settings.offset.x, // right
settings.offset.y, // bottom
settings.offset.x);// left
} else {
// Just set the window size, as positioning is not part of the xdg-shell protocol
xdg_surface_set_window_geometry(ctx.xdg_surface, 0, 0, dim.w, dim.h);
}

wl_surface_commit(ctx.surface);

// Now we're going to bail without drawing anything. This gives the
// compositor a chance to create the surface and tell us what size we
// were actually granted, which may be smaller than what we asked for
// depending on the screen size and layout of other layer surfaces.
// This information is provided in layer_surface_handle_configure,
// which will then call send_frame again. When that call happens, the
// layer surface will exist and the height will hopefully match what
// we asked for. That means we won't return here, and will actually
// draw into the surface down below.
// TODO: If the compositor doesn't send a configure with the size we
// requested, we'll enter an infinite loop. We need to keep track of
// the fact that a request was sent separately from what height we are.
wl_display_roundtrip(ctx.display);
return;
if (!ctx.configured) {
// Now we're going to bail without drawing anything. This gives the
// compositor a chance to create the surface and tell us what size we
// were actually granted, which may be smaller than what we asked for
// depending on the screen size and layout of other layer surfaces.
// This information is provided in layer_surface_handle_configure,
// which will then call send_frame again. When that call happens, the
// layer surface will exist and the height will hopefully match what
// we asked for. That means we won't return here, and will actually
// draw into the surface down below.
wl_display_roundtrip(ctx.display);
return;
}
}

assert(ctx.configured);
Expand Down
3 changes: 3 additions & 0 deletions src/wayland/wl_ctx.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ struct wl_ctx {
struct wl_compositor *compositor;
struct wl_shm *shm;
struct zwlr_layer_shell_v1 *layer_shell;
struct xdg_wm_base *xdg_shell;

struct wl_list outputs; /* list of struct dunst_output */
struct wl_list seats; /* list of struct dunst_seat */

struct wl_surface *surface;
struct dunst_output *surface_output;
struct zwlr_layer_surface_v1 *layer_surface;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
struct dunst_output *layer_surface_output;
struct wl_callback *frame_callback;
struct org_kde_kwin_idle *idle_handler;
Expand Down

0 comments on commit a1bae20

Please sign in to comment.