Skip to content

Commit fe2a7dc

Browse files
committed
wayland: support xdg-shell as a fallback
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.
1 parent ad210bb commit fe2a7dc

File tree

2 files changed

+170
-57
lines changed

2 files changed

+170
-57
lines changed

src/wayland/wl.c

Lines changed: 167 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct window_wl {
4040
cairo_t * c_ctx;
4141
};
4242

43-
struct wl_ctx ctx;
43+
struct wl_ctx ctx = { 0 };
4444

4545
static void surface_handle_enter(void *data, struct wl_surface *surface,
4646
struct wl_output *wl_output) {
@@ -83,13 +83,36 @@ static void layer_surface_handle_configure(void *data,
8383
send_frame();
8484
}
8585

86-
static void layer_surface_handle_closed(void *data,
87-
struct zwlr_layer_surface_v1 *surface) {
88-
LOG_I("Destroying layer");
89-
if (ctx.layer_surface)
90-
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
91-
ctx.layer_surface = NULL;
86+
static void xdg_surface_handle_configure(void *data,
87+
struct xdg_surface *surface,
88+
uint32_t serial) {
89+
xdg_surface_ack_configure(ctx.xdg_surface, serial);
90+
91+
if (ctx.configured) {
92+
wl_surface_commit(ctx.surface);
93+
return;
94+
}
95+
96+
ctx.configured = true;
97+
98+
send_frame();
99+
}
100+
101+
static void xdg_toplevel_handle_configure(void *data,
102+
struct xdg_toplevel *xdg_toplevel,
103+
int32_t width,
104+
int32_t height,
105+
struct wl_array *states) {
106+
if (width == ctx.width && height == ctx.height) {
107+
return;
108+
}
109+
110+
ctx.configured = false;
111+
ctx.width = width;
112+
ctx.height = height;
113+
}
92114

115+
static void surface_handle_closed(void) {
93116
if (ctx.surface)
94117
wl_surface_destroy(ctx.surface);
95118
ctx.surface = NULL;
@@ -111,11 +134,53 @@ static void layer_surface_handle_closed(void *data,
111134
}
112135
}
113136

137+
static void layer_surface_handle_closed(void *data,
138+
struct zwlr_layer_surface_v1 *surface) {
139+
LOG_I("Destroying layer");
140+
if (ctx.layer_surface)
141+
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
142+
ctx.layer_surface = NULL;
143+
144+
surface_handle_closed();
145+
}
146+
147+
static void xdg_toplevel_handle_close(void *data,
148+
struct xdg_toplevel *surface) {
149+
LOG_I("Destroying layer");
150+
if (ctx.xdg_toplevel)
151+
xdg_toplevel_destroy(ctx.xdg_toplevel);
152+
ctx.xdg_toplevel = NULL;
153+
if (ctx.xdg_surface)
154+
xdg_surface_destroy(ctx.xdg_surface);
155+
ctx.xdg_surface = NULL;
156+
157+
surface_handle_closed();
158+
}
159+
160+
static void xdg_wm_base_handle_ping(void *data,
161+
struct xdg_wm_base *xdg_wm_base,
162+
uint32_t serial) {
163+
xdg_wm_base_pong(ctx.xdg_shell, serial);
164+
}
165+
114166
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
115167
.configure = layer_surface_handle_configure,
116168
.closed = layer_surface_handle_closed,
117169
};
118170

171+
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
172+
.ping = xdg_wm_base_handle_ping,
173+
};
174+
175+
static const struct xdg_surface_listener xdg_surface_listener = {
176+
.configure = xdg_surface_handle_configure,
177+
};
178+
179+
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
180+
.configure = xdg_toplevel_handle_configure,
181+
.close = xdg_toplevel_handle_close,
182+
};
183+
119184
// Warning, can return NULL
120185
static struct dunst_output *get_configured_output(void) {
121186
int n = 0;
@@ -164,6 +229,10 @@ static void handle_global(void *data, struct wl_registry *registry,
164229
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
165230
ctx.layer_shell = wl_registry_bind(registry, name,
166231
&zwlr_layer_shell_v1_interface, 1);
232+
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
233+
ctx.xdg_shell = wl_registry_bind(registry, name,
234+
&xdg_wm_base_interface, 1);
235+
xdg_wm_base_add_listener(ctx.xdg_shell, &xdg_wm_base_listener, NULL);
167236
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
168237
create_seat(registry, name, version);
169238
LOG_D("Binding to seat %i", name);
@@ -262,8 +331,12 @@ bool wl_init(void) {
262331
return false;
263332
}
264333
if (ctx.layer_shell == NULL) {
265-
LOG_W("compositor doesn't support zwlr_layer_shell_v1");
266-
return false;
334+
if (ctx.xdg_shell == NULL) {
335+
LOG_W("compositor doesn't support zwlr_layer_shell_v1 or xdg_shell");
336+
return false;
337+
} else {
338+
LOG_W("compositor doesn't support zwlr_layer_shell_v1, falling back to xdg_shell. Notification window position will be set by the compositor.");
339+
}
267340
}
268341
if (wl_list_empty(&ctx.seats)) {
269342
LOG_W("no seat was found, so dunst cannot see input");
@@ -334,6 +407,12 @@ void wl_deinit(void) {
334407
if (ctx.layer_surface != NULL) {
335408
g_clear_pointer(&ctx.layer_surface, zwlr_layer_surface_v1_destroy);
336409
}
410+
if (ctx.xdg_toplevel != NULL) {
411+
g_clear_pointer(&ctx.xdg_toplevel, xdg_toplevel_destroy);
412+
}
413+
if (ctx.xdg_surface != NULL) {
414+
g_clear_pointer(&ctx.xdg_surface, xdg_surface_destroy);
415+
}
337416
if (ctx.surface != NULL) {
338417
g_clear_pointer(&ctx.surface, wl_surface_destroy);
339418
}
@@ -371,6 +450,9 @@ void wl_deinit(void) {
371450
if (ctx.layer_shell)
372451
g_clear_pointer(&ctx.layer_shell, zwlr_layer_shell_v1_destroy);
373452

453+
if (ctx.xdg_shell)
454+
g_clear_pointer(&ctx.xdg_shell, xdg_wm_base_destroy);
455+
374456
if (ctx.compositor)
375457
g_clear_pointer(&ctx.compositor, wl_compositor_destroy);
376458

@@ -393,8 +475,6 @@ static void schedule_frame_and_commit(void);
393475

394476
// Draw and commit a new frame.
395477
static void send_frame(void) {
396-
int scale = wl_get_scale();
397-
398478
if (wl_list_empty(&ctx.outputs)) {
399479
ctx.dirty = false;
400480
return;
@@ -411,6 +491,14 @@ static void send_frame(void) {
411491
zwlr_layer_surface_v1_destroy(ctx.layer_surface);
412492
ctx.layer_surface = NULL;
413493
}
494+
if (ctx.xdg_toplevel != NULL) {
495+
xdg_toplevel_destroy(ctx.xdg_toplevel);
496+
ctx.xdg_toplevel = NULL;
497+
}
498+
if (ctx.xdg_surface != NULL) {
499+
xdg_surface_destroy(ctx.xdg_surface);
500+
ctx.xdg_surface = NULL;
501+
}
414502
if (ctx.surface != NULL) {
415503
wl_surface_destroy(ctx.surface);
416504
ctx.surface = NULL;
@@ -443,20 +531,32 @@ static void send_frame(void) {
443531
// If we've made it here, there is something to draw. If the surface
444532
// doesn't exist (this is the first notification, or we moved to a
445533
// different output), we need to create it.
446-
if (ctx.layer_surface == NULL) {
447-
struct wl_output *wl_output = NULL;
448-
if (output != NULL) {
449-
wl_output = output->wl_output;
450-
}
534+
if (ctx.layer_surface == NULL && ctx.xdg_surface == NULL) {
451535
ctx.layer_surface_output = output;
452536
ctx.surface = wl_compositor_create_surface(ctx.compositor);
453537
wl_surface_add_listener(ctx.surface, &surface_listener, NULL);
454538

455-
ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
456-
ctx.layer_shell, ctx.surface, wl_output,
457-
settings.layer, "notifications");
458-
zwlr_layer_surface_v1_add_listener(ctx.layer_surface,
459-
&layer_surface_listener, NULL);
539+
if (ctx.layer_shell) {
540+
struct wl_output *wl_output = NULL;
541+
if (output != NULL) {
542+
wl_output = output->wl_output;
543+
}
544+
545+
ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
546+
ctx.layer_shell, ctx.surface, wl_output,
547+
settings.layer, "notifications");
548+
zwlr_layer_surface_v1_add_listener(ctx.layer_surface,
549+
&layer_surface_listener, NULL);
550+
} else {
551+
ctx.xdg_surface = xdg_wm_base_get_xdg_surface(
552+
ctx.xdg_shell, ctx.surface);
553+
xdg_surface_add_listener(ctx.xdg_surface, &xdg_surface_listener, NULL);
554+
555+
ctx.xdg_toplevel = xdg_surface_get_toplevel(ctx.xdg_surface);
556+
xdg_toplevel_set_title(ctx.xdg_toplevel, "Dunst");
557+
xdg_toplevel_set_app_id(ctx.xdg_toplevel, "org.knopwob.dunst");
558+
xdg_toplevel_add_listener(ctx.xdg_toplevel, &xdg_toplevel_listener, NULL);
559+
}
460560

461561
// Because we're creating a new surface, we aren't going to draw
462562
// anything into it during this call. We don't know what size the
@@ -468,56 +568,55 @@ static void send_frame(void) {
468568
// block to let it set the size for us.
469569
}
470570

471-
assert(ctx.layer_surface);
571+
assert(ctx.layer_surface || ctx.xdg_surface);
472572

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

483-
// Put the window at the right position
484-
zwlr_layer_surface_v1_set_anchor(ctx.layer_surface,
485-
settings.origin);
486-
zwlr_layer_surface_v1_set_margin(ctx.layer_surface,
487-
// Offsets where no anchors are specified are
488-
// ignored. We can safely assume the offset is
489-
// positive.
490-
settings.offset.y, // top
491-
settings.offset.x, // right
492-
settings.offset.y, // bottom
493-
settings.offset.x);// left
494-
495-
wl_surface_commit(ctx.surface);
584+
// Put the window at the right position
585+
zwlr_layer_surface_v1_set_anchor(ctx.layer_surface,
586+
settings.origin);
587+
zwlr_layer_surface_v1_set_margin(ctx.layer_surface,
588+
// Offsets where no anchors are specified are
589+
// ignored. We can safely assume the offset is
590+
// positive.
591+
settings.offset.y, // top
592+
settings.offset.x, // right
593+
settings.offset.y, // bottom
594+
settings.offset.x);// left
595+
} else {
596+
// Just set the window size, as positioning is not part of the xdg-shell protocol
597+
xdg_surface_set_window_geometry(ctx.xdg_surface, 0, 0, dim.w, dim.h);
598+
}
496599

497-
// Now we're going to bail without drawing anything. This gives the
498-
// compositor a chance to create the surface and tell us what size we
499-
// were actually granted, which may be smaller than what we asked for
500-
// depending on the screen size and layout of other layer surfaces.
501-
// This information is provided in layer_surface_handle_configure,
502-
// which will then call send_frame again. When that call happens, the
503-
// layer surface will exist and the height will hopefully match what
504-
// we asked for. That means we won't return here, and will actually
505-
// draw into the surface down below.
506-
// TODO: If the compositor doesn't send a configure with the size we
507-
// requested, we'll enter an infinite loop. We need to keep track of
508-
// the fact that a request was sent separately from what height we are.
509-
wl_display_roundtrip(ctx.display);
510-
return;
600+
if(!ctx.configured) {
601+
wl_surface_commit(ctx.surface);
602+
603+
LOG_W("Doing a roundtrip");
604+
// Now we're going to bail without drawing anything. This gives the
605+
// compositor a chance to create the surface and tell us what size we
606+
// were actually granted, which may be smaller than what we asked for
607+
// depending on the screen size and layout of other layer surfaces.
608+
// This information is provided in layer_surface_handle_configure,
609+
// which will then call send_frame again. When that call happens, the
610+
// layer surface will exist and the height will hopefully match what
611+
// we asked for. That means we won't return here, and will actually
612+
// draw into the surface down below.
613+
wl_display_roundtrip(ctx.display);
614+
return;
615+
}
511616
}
512617

513618
assert(ctx.configured);
514619

515-
// Yay we can finally draw something!
516-
wl_surface_set_buffer_scale(ctx.surface, scale);
517-
wl_surface_damage_buffer(ctx.surface, 0, 0, INT32_MAX, INT32_MAX);
518-
wl_surface_attach(ctx.surface, ctx.current_buffer->buffer, 0, 0);
519-
ctx.current_buffer->busy = true;
520-
521620
// Schedule a frame in case the state becomes dirty again
522621
schedule_frame_and_commit();
523622

@@ -540,14 +639,25 @@ static const struct wl_callback_listener frame_listener = {
540639
};
541640

542641
static void schedule_frame_and_commit(void) {
642+
int scale = wl_get_scale();
643+
543644
if (ctx.frame_callback) {
645+
// don't commit, as it probably won't make it to the display and
646+
// therefore waste resources
544647
return;
545648
}
546649
if (ctx.surface == NULL) {
547650
// We don't yet have a surface, create it immediately
548651
send_frame();
549652
return;
550653
}
654+
655+
// Yay we can finally draw something!
656+
wl_surface_set_buffer_scale(ctx.surface, scale);
657+
wl_surface_damage_buffer(ctx.surface, 0, 0, INT32_MAX, INT32_MAX);
658+
wl_surface_attach(ctx.surface, ctx.current_buffer->buffer, 0, 0);
659+
ctx.current_buffer->busy = true;
660+
551661
ctx.frame_callback = wl_surface_frame(ctx.surface);
552662
wl_callback_add_listener(ctx.frame_callback, &frame_listener, NULL);
553663
wl_surface_commit(ctx.surface);

src/wayland/wl_ctx.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ struct wl_ctx {
2020
struct wl_compositor *compositor;
2121
struct wl_shm *shm;
2222
struct zwlr_layer_shell_v1 *layer_shell;
23+
struct xdg_wm_base *xdg_shell;
2324

2425
struct wl_list outputs; /* list of struct dunst_output */
2526
struct wl_list seats; /* list of struct dunst_seat */
2627

2728
struct wl_surface *surface;
2829
struct dunst_output *surface_output;
2930
struct zwlr_layer_surface_v1 *layer_surface;
31+
struct xdg_surface *xdg_surface;
32+
struct xdg_toplevel *xdg_toplevel;
3033
struct dunst_output *layer_surface_output;
3134
struct wl_callback *frame_callback;
3235
struct org_kde_kwin_idle *idle_handler;

0 commit comments

Comments
 (0)