8#define _POSIX_C_SOURCE 200112L
13#include <wayland-client.h>
14#include <wayland-client-protocol.h>
15#include <wayland-util.h>
16#include <wayland-cursor.h>
22#include <linux/input-event-codes.h>
26#include "protocols/xdg-shell-client-header.h"
27#include "protocols/xdg-shell.h"
28#include "protocols/wlr-layer-shell-unstable-v1-client-header.h"
29#include "protocols/wlr-layer-shell-unstable-v1.h"
30#include "protocols/wlr-foreign-toplevel-management-unstable-v1-client-header.h"
31#include "protocols/wlr-foreign-toplevel-management-unstable-v1.h"
38#include "libgwater-wayland.h"
45 cairo_surface_t *c_surface;
51static void surface_handle_enter(
void *data,
struct wl_surface *surface,
52 struct wl_output *wl_output) {
55 ctx.surface_output = wl_output_get_user_data(wl_output);
59static void surface_handle_leave(
void *data,
struct wl_surface *surface,
60 struct wl_output *wl_output) {
61 ctx.surface_output = NULL;
64static const struct wl_surface_listener surface_listener = {
65 .enter = surface_handle_enter,
66 .leave = surface_handle_leave,
70static void schedule_frame_and_commit(
void);
71static void send_frame(
void);
73static void layer_surface_handle_configure(
void *data,
74 struct zwlr_layer_surface_v1 *surface,
75 uint32_t serial, uint32_t width, uint32_t height) {
76 zwlr_layer_surface_v1_ack_configure(surface, serial);
79 ctx.width == (int32_t) width &&
80 ctx.height == (int32_t) height) {
81 wl_surface_commit(ctx.surface);
85 ctx.configured =
true;
92static void xdg_surface_handle_configure(
void *data,
93 struct xdg_surface *surface,
95 xdg_surface_ack_configure(ctx.xdg_surface, serial);
98 wl_surface_commit(ctx.surface);
102 ctx.configured =
true;
107static void xdg_toplevel_handle_configure(
void *data,
108 struct xdg_toplevel *xdg_toplevel,
111 struct wl_array *states) {
117static void surface_handle_closed(
void) {
119 wl_surface_destroy(ctx.surface);
122 if (ctx.frame_callback) {
123 wl_callback_destroy(ctx.frame_callback);
124 ctx.frame_callback = NULL;
128 if (ctx.configured) {
129 ctx.configured =
false;
130 ctx.width = ctx.height = 0;
135 schedule_frame_and_commit();
139static void layer_surface_handle_closed(
void *data,
140 struct zwlr_layer_surface_v1 *surface) {
141 LOG_I(
"Destroying layer");
142 if (ctx.layer_surface)
143 zwlr_layer_surface_v1_destroy(ctx.layer_surface);
144 ctx.layer_surface = NULL;
146 surface_handle_closed();
149static void xdg_toplevel_handle_close(
void *data,
150 struct xdg_toplevel *surface) {
151 LOG_I(
"Destroying layer");
152 if (ctx.xdg_toplevel)
153 xdg_toplevel_destroy(ctx.xdg_toplevel);
154 ctx.xdg_toplevel = NULL;
156 xdg_surface_destroy(ctx.xdg_surface);
157 ctx.xdg_surface = NULL;
159 surface_handle_closed();
162static void xdg_wm_base_handle_ping(
void *data,
163 struct xdg_wm_base *xdg_wm_base,
165 xdg_wm_base_pong(ctx.xdg_shell, serial);
168static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
169 .configure = layer_surface_handle_configure,
170 .closed = layer_surface_handle_closed,
173static const struct xdg_wm_base_listener xdg_wm_base_listener = {
174 .ping = xdg_wm_base_handle_ping,
177static const struct xdg_surface_listener xdg_surface_listener = {
178 .configure = xdg_surface_handle_configure,
181static const struct xdg_toplevel_listener xdg_toplevel_listener = {
182 .configure = xdg_toplevel_handle_configure,
183 .close = xdg_toplevel_handle_close,
187static struct dunst_output *get_configured_output(
void) {
189 int target_monitor =
settings.monitor_num;
190 const char *name =
settings.monitor;
192 struct dunst_output *first_output = NULL, *configured_output = NULL,
194 wl_list_for_each(tmp_output, &ctx.outputs, link) {
196 first_output = tmp_output;
197 if (n == target_monitor)
198 configured_output = tmp_output;
199 if (g_strcmp0(name, tmp_output->name) == 0)
200 configured_output = tmp_output;
210 if (!configured_output) {
211 LOG_W(
"Screen '%s' not found, using focused monitor",
settings.monitor);
213 return configured_output;
216 case FOLLOW_KEYBOARD:
223static void handle_global(
void *data,
struct wl_registry *registry,
224 uint32_t name,
const char *interface, uint32_t version) {
225 if (strcmp(interface, wl_compositor_interface.name) == 0) {
226 ctx.compositor = wl_registry_bind(registry, name,
227 &wl_compositor_interface, 4);
228 }
else if (strcmp(interface, wl_shm_interface.name) == 0) {
229 ctx.shm = wl_registry_bind(registry, name,
230 &wl_shm_interface, 1);
231 }
else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
232 ctx.layer_shell = wl_registry_bind(registry, name,
233 &zwlr_layer_shell_v1_interface, 1);
234 }
else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
235 ctx.xdg_shell = wl_registry_bind(registry, name,
236 &xdg_wm_base_interface, 1);
237 xdg_wm_base_add_listener(ctx.xdg_shell, &xdg_wm_base_listener, NULL);
238 }
else if (strcmp(interface, wl_seat_interface.name) == 0) {
239 create_seat(registry, name, version);
240 LOG_D(
"Binding to seat %i", name);
241 }
else if (strcmp(interface, wl_output_interface.name) == 0) {
242 create_output(registry, name, version);
243 LOG_D(
"Binding to output %i", name);
244 }
else if (strcmp(interface, org_kde_kwin_idle_interface.name) == 0 &&
245 version >= ORG_KDE_KWIN_IDLE_TIMEOUT_IDLE_SINCE_VERSION) {
246#ifdef HAVE_WL_EXT_IDLE_NOTIFY
247 if (ctx.ext_idle_notifier)
250 ctx.idle_handler = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1);
251 LOG_D(
"Found org_kde_kwin_idle");
252#ifdef HAVE_WL_EXT_IDLE_NOTIFY
253 }
else if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0) {
254 ctx.ext_idle_notifier = wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1);
255 LOG_D(
"Found ext-idle-notify-v1");
257#ifdef HAVE_WL_CURSOR_SHAPE
258 }
else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
259 ctx.cursor_shape_manager = wl_registry_bind(registry, name,
260 &wp_cursor_shape_manager_v1_interface, 1);
262 }
else if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0 &&
263 version >= ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION) {
264 LOG_D(
"Found toplevel manager %i", name);
265 ctx.toplevel_manager_name = name;
269static void handle_global_remove(
void *data,
struct wl_registry *registry,
272 wl_list_for_each_safe(
output, tmp, &ctx.outputs, link) {
273 if (
output->global_name == name) {
280 wl_list_for_each_safe(seat, tmp_seat, &ctx.seats, link) {
281 if (seat->global_name == name) {
288static const struct wl_registry_listener registry_listener = {
289 .global = handle_global,
290 .global_remove = handle_global_remove,
295 wl_list_init(&ctx.outputs);
296 wl_list_init(&ctx.seats);
297 wl_list_init(&toplevel_list);
299 ctx.esrc = g_water_wayland_source_new(NULL, NULL);
300 ctx.display = g_water_wayland_source_get_display(ctx.esrc);
301#if GLIB_CHECK_VERSION(2, 58, 0)
302 g_water_wayland_source_set_error_callback(ctx.esrc, G_SOURCE_FUNC(quit_signal), NULL, NULL);
304 g_water_wayland_source_set_error_callback(ctx.esrc, (GSourceFunc)quit_signal, NULL, NULL);
307 if (ctx.display == NULL) {
308 LOG_W(
"failed to create display");
312 ctx.toplevel_manager_name = UINT32_MAX;
314 ctx.registry = wl_display_get_registry(ctx.display);
315 wl_registry_add_listener(ctx.registry, ®istry_listener, NULL);
316 wl_display_roundtrip(ctx.display);
317 if (ctx.toplevel_manager_name != UINT32_MAX) {
318 ctx.toplevel_manager = wl_registry_bind(ctx.registry, ctx.toplevel_manager_name,
319 &zwlr_foreign_toplevel_manager_v1_interface,
321 zwlr_foreign_toplevel_manager_v1_add_listener(ctx.toplevel_manager,
322 &toplevel_manager_impl, NULL);
324 wl_display_roundtrip(ctx.display);
325 wl_display_roundtrip(ctx.display);
326 wl_display_flush(ctx.display);
328 if (ctx.compositor == NULL) {
329 LOG_W(
"compositor doesn't support wl_compositor");
332 if (ctx.shm == NULL) {
333 LOG_W(
"compositor doesn't support wl_shm");
336 if (ctx.layer_shell == NULL) {
337 if (ctx.xdg_shell == NULL) {
338 LOG_W(
"compositor doesn't support zwlr_layer_shell_v1 or xdg_shell");
341 LOG_W(
"compositor doesn't support zwlr_layer_shell_v1, falling back to xdg_shell. Notification window position will be set by the compositor.");
344 if (wl_list_empty(&ctx.seats)) {
345 LOG_W(
"no seat was found, so dunst cannot see input");
346 }
else if (ctx.idle_handler == NULL
347#ifdef HAVE_WL_EXT_IDLE_NOTIFY
348 && ctx.ext_idle_notifier == NULL
351 LOG_I(
"compositor doesn't support org_kde_kwin_idle or ext-idle-notify-v1");
355 wl_list_for_each(seat, &ctx.seats, link) {
356 add_seat_to_idle_handler(seat);
360 if (ctx.toplevel_manager == NULL) {
361 LOG_W(
"compositor doesn't support zwlr_foreign_toplevel_v1. Fullscreen detection won't work");
366 const char *cursor_size_env = getenv(
"XCURSOR_SIZE");
367 int cursor_size = 24;
368 if (cursor_size_env != NULL) {
371 int temp_size = (int)strtol(cursor_size_env, &end, 10);
372 if (errno == 0 && cursor_size_env[0] != 0 && end[0] == 0 && temp_size > 0) {
373 cursor_size = temp_size;
375 LOG_W(
"Error: XCURSOR_SIZE is invalid");
378 const char *xcursor_theme = getenv(
"XCURSOR_THEME");
379 ctx.cursor_theme = wl_cursor_theme_load(xcursor_theme, cursor_size, ctx.shm);
380 if (ctx.cursor_theme == NULL) {
381 LOG_W(
"couldn't find a cursor theme");
385 struct wl_cursor *cursor = wl_cursor_theme_get_cursor(ctx.cursor_theme,
"default");
386 if (cursor == NULL) {
388 cursor = wl_cursor_theme_get_cursor(ctx.cursor_theme,
"left_ptr");
390 if (cursor == NULL) {
391 LOG_W(
"couldn't find cursor icons \"default\" or \"left_ptr\"");
392 wl_cursor_theme_destroy(ctx.cursor_theme);
394 ctx.cursor_theme = NULL;
397 ctx.cursor_image = cursor->images[0];
398 struct wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor->images[0]);
399 ctx.cursor_surface = wl_compositor_create_surface(ctx.compositor);
400 wl_surface_attach(ctx.cursor_surface, cursor_buffer, 0, 0);
401 wl_surface_commit(ctx.cursor_surface);
406void wl_deinit(
void) {
410 if (ctx.layer_surface != NULL) {
411 g_clear_pointer(&ctx.layer_surface, zwlr_layer_surface_v1_destroy);
413 if (ctx.xdg_toplevel != NULL) {
414 g_clear_pointer(&ctx.xdg_toplevel, xdg_toplevel_destroy);
416 if (ctx.xdg_surface != NULL) {
417 g_clear_pointer(&ctx.xdg_surface, xdg_surface_destroy);
419 if (ctx.frame_callback) {
420 g_clear_pointer(&ctx.frame_callback, wl_callback_destroy);
422 if (ctx.surface != NULL) {
423 g_clear_pointer(&ctx.surface, wl_surface_destroy);
425 finish_buffer(&ctx.buffers[0]);
426 finish_buffer(&ctx.buffers[1]);
427 ctx.current_buffer = NULL;
432 wl_list_for_each_safe(
output, output_tmp, &ctx.outputs, link) {
437 wl_list_for_each_safe(seat, seat_tmp, &ctx.seats, link) {
441 ctx.outputs = (
struct wl_list) {0};
442 ctx.seats = (
struct wl_list) {0};
444#ifdef HAVE_WL_CURSOR_SHAPE
445 if (ctx.cursor_shape_manager)
446 g_clear_pointer(&ctx.cursor_shape_manager, wp_cursor_shape_manager_v1_destroy);
449#ifdef HAVE_WL_EXT_IDLE_NOTIFY
450 if (ctx.ext_idle_notifier)
451 g_clear_pointer(&ctx.ext_idle_notifier, ext_idle_notifier_v1_destroy);
454 if (ctx.idle_handler)
455 g_clear_pointer(&ctx.idle_handler, org_kde_kwin_idle_destroy);
458 g_clear_pointer(&ctx.layer_shell, zwlr_layer_shell_v1_destroy);
461 g_clear_pointer(&ctx.xdg_shell, xdg_wm_base_destroy);
464 g_clear_pointer(&ctx.compositor, wl_compositor_destroy);
467 g_clear_pointer(&ctx.shm, wl_shm_destroy);
470 g_clear_pointer(&ctx.registry, wl_registry_destroy);
472 if (ctx.cursor_theme != NULL) {
473 g_clear_pointer(&ctx.cursor_theme, wl_cursor_theme_destroy);
474 g_clear_pointer(&ctx.cursor_surface, wl_surface_destroy);
475 ctx.cursor_image = NULL;
478 if (ctx.toplevel_manager) {
479 zwlr_foreign_toplevel_manager_v1_stop(ctx.toplevel_manager);
480 g_clear_pointer(&ctx.toplevel_manager, zwlr_foreign_toplevel_manager_v1_destroy);
483 ctx.toplevel_manager_name = 0;
487 g_clear_pointer(&ctx.esrc, g_water_wayland_source_free);
491static void schedule_frame_and_commit(
void);
494static void send_frame(
void) {
495 int scale = wl_get_scale();
497 if (wl_list_empty(&ctx.outputs)) {
503 int height = ctx.cur_dim.h;
504 int width = ctx.cur_dim.w;
508 if (height == 0 || ctx.layer_surface_output !=
output) {
509 if (ctx.layer_surface != NULL) {
510 zwlr_layer_surface_v1_destroy(ctx.layer_surface);
511 ctx.layer_surface = NULL;
513 if (ctx.xdg_toplevel != NULL) {
514 xdg_toplevel_destroy(ctx.xdg_toplevel);
515 ctx.xdg_toplevel = NULL;
517 if (ctx.xdg_surface != NULL) {
518 xdg_surface_destroy(ctx.xdg_surface);
519 ctx.xdg_surface = NULL;
521 if (ctx.surface != NULL) {
522 wl_surface_destroy(ctx.surface);
525 ctx.width = ctx.height = 0;
526 ctx.surface_output = NULL;
527 ctx.configured =
false;
533 wl_list_for_each(o, &ctx.outputs, link) {
553 if (ctx.layer_surface == NULL && ctx.xdg_surface == NULL) {
554 ctx.layer_surface_output =
output;
555 ctx.surface = wl_compositor_create_surface(ctx.compositor);
556 wl_surface_add_listener(ctx.surface, &surface_listener, NULL);
558 if (ctx.layer_shell) {
559 struct wl_output *wl_output = NULL;
561 wl_output =
output->wl_output;
564 ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(
565 ctx.layer_shell, ctx.surface, wl_output,
567 zwlr_layer_surface_v1_add_listener(ctx.layer_surface,
568 &layer_surface_listener, NULL);
570 ctx.xdg_surface = xdg_wm_base_get_xdg_surface(
571 ctx.xdg_shell, ctx.surface);
572 xdg_surface_add_listener(ctx.xdg_surface, &xdg_surface_listener, NULL);
574 ctx.xdg_toplevel = xdg_surface_get_toplevel(ctx.xdg_surface);
575 xdg_toplevel_set_title(ctx.xdg_toplevel,
"Dunst");
576 xdg_toplevel_set_app_id(ctx.xdg_toplevel,
"org.knopwob.dunst");
577 xdg_toplevel_add_listener(ctx.xdg_toplevel, &xdg_toplevel_listener, NULL);
590 assert(ctx.layer_surface || ctx.xdg_surface);
602 if (ctx.layer_surface && (ctx.height != height || ctx.width != width)) {
608 zwlr_layer_surface_v1_set_size(ctx.layer_surface,
612 zwlr_layer_surface_v1_set_anchor(ctx.layer_surface,
614 zwlr_layer_surface_v1_set_margin(ctx.layer_surface,
627 ctx.configured =
false;
630 if (!ctx.configured) {
631 wl_surface_commit(ctx.surface);
642 wl_display_roundtrip(ctx.display);
646 assert(ctx.configured);
649 wl_surface_set_buffer_scale(ctx.surface, scale);
650 wl_surface_damage_buffer(ctx.surface, 0, 0, INT32_MAX, INT32_MAX);
651 wl_surface_attach(ctx.surface, ctx.current_buffer->buffer, 0, 0);
652 ctx.current_buffer->busy =
true;
655 schedule_frame_and_commit();
660static void frame_handle_done(
void *data,
struct wl_callback *callback,
662 wl_callback_destroy(ctx.frame_callback);
663 ctx.frame_callback = NULL;
671static const struct wl_callback_listener frame_listener = {
672 .done = frame_handle_done,
675static void schedule_frame_and_commit(
void) {
676 if (ctx.frame_callback) {
681 if (ctx.surface == NULL) {
686 ctx.frame_callback = wl_surface_frame(ctx.surface);
687 wl_callback_add_listener(ctx.frame_callback, &frame_listener, NULL);
688 wl_surface_commit(ctx.surface);
691void set_dirty(
void) {
696 schedule_frame_and_commit();
699window wl_win_create(
void) {
704void wl_win_destroy(window winptr) {
710void wl_win_show(window win) {
715void wl_win_hide(window win) {
716 LOG_I(
"Wayland: Hiding window");
719 wl_display_roundtrip(ctx.display);
722void wl_display_surface(cairo_surface_t *srf, window winptr,
const struct dimensions* dim) {
724 int scale = wl_get_scale();
725 LOG_D(
"Buffer size (scaled) %ix%i", dim->w * scale, dim->h * scale);
726 ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers,
727 dim->w * scale, dim->h * scale);
729 if(ctx.current_buffer == NULL) {
733 cairo_t *c = ctx.current_buffer->cairo;
735 cairo_set_source_surface(c, srf, 0, 0);
736 cairo_rectangle(c, 0, 0, dim->w * scale, dim->h * scale);
743 wl_display_roundtrip(ctx.display);
746cairo_t* wl_win_get_context(window winptr) {
748 ctx.current_buffer = get_next_buffer(ctx.shm, ctx.buffers, 500, 500);
750 if(ctx.current_buffer == NULL) {
754 win->c_surface = ctx.current_buffer->surface;
755 win->c_ctx = ctx.current_buffer->cairo;
759const struct screen_info* wl_get_active_screen(
void) {
768 scr.dpi = wl_get_scale() * 96;
770 struct dunst_output *current_output = get_configured_output();
771 if (current_output != NULL) {
772 scr.w = current_output->width;
773 scr.h = current_output->height;
783bool wl_is_idle(
void) {
788 if (
settings.idle_threshold == 0 || wl_list_empty(&ctx.seats)) {
791 wl_list_for_each(seat, &ctx.seats, link) {
793 is_idle &= seat->is_idle;
796 LOG_I(
"Idle status queried: %i", is_idle);
800bool wl_have_fullscreen_window(
void) {
801 struct dunst_output *current_output = get_configured_output();
802 uint32_t output_name = UINT32_MAX;
804 output_name = current_output->global_name;
807 wl_list_for_each(toplevel, &toplevel_list, link) {
808 if (!(toplevel->current.state & TOPLEVEL_STATE_FULLSCREEN &&
809 toplevel->current.state &
810 TOPLEVEL_STATE_ACTIVATED))
814 wl_list_for_each(pos, &toplevel->output_list, link) {
815 if (output_name == UINT32_MAX ||
816 pos->dunst_output->global_name ==
825double wl_get_scale(
void) {
833 wl_list_for_each(
output, &ctx.outputs, link) {
834 scale = MAX((
int)
output->scale, scale);
Wayland foreign toplevel support.
Logging subsystem and helpers.
Wayland rendering buffer pool.
Type definitions for settings.
Wayland window managment.
Wayland context tracking.