diff --git a/include/wlr/types/wlr_xdg_dialog_v1.h b/include/wlr/types/wlr_xdg_dialog_v1.h new file mode 100644 index 000000000..061c3252b --- /dev/null +++ b/include/wlr/types/wlr_xdg_dialog_v1.h @@ -0,0 +1,57 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_DIALOG_V1_H +#define WLR_TYPES_WLR_XDG_DIALOG_V1_H + +#include +#include + +struct wlr_xdg_dialog_v1 { + struct wl_resource *resource; + struct wlr_xdg_toplevel *xdg_toplevel; + + bool modal; + + struct { + struct wl_signal destroy; + // Corresponds to both xdg_dialog_v1.set_modal and xdg_dialog_v1.unset_modal + struct wl_signal set_modal; + } events; + + struct { + struct wlr_addon surface_addon; + + struct wl_listener xdg_toplevel_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_wm_dialog_v1 { + struct wl_global *global; + + struct { + struct wl_signal new_dialog; // struct wlr_xdg_dialog_v1 + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_wm_dialog_v1 *wlr_xdg_wm_dialog_v1_create(struct wl_display *display, + uint32_t version); + +/** + * Get a struct wlr_xdg_dialog_v1 from a struct wlr_xdg_toplevel. + * + * Returns NULL if there's no xdg_dialog_v1 associated with the xdg toplevel. + */ +struct wlr_xdg_dialog_v1 *wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel( + struct wlr_xdg_toplevel *xdg_toplevel); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 2889fb033..2b0e49977 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -35,6 +35,7 @@ protocols = { 'security-context-v1': wl_protocol_dir / 'staging/security-context/security-context-v1.xml', 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', 'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', + 'xdg-dialog-v1': wl_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', 'xdg-system-bell-v1': wl_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml', 'xwayland-shell-v1': wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', 'tearing-control-v1': wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', diff --git a/types/meson.build b/types/meson.build index 8753b8a0c..237d41f53 100644 --- a/types/meson.build +++ b/types/meson.build @@ -91,6 +91,7 @@ wlr_files += files( 'wlr_xcursor_manager.c', 'wlr_xdg_activation_v1.c', 'wlr_xdg_decoration_v1.c', + 'wlr_xdg_dialog_v1.c', 'wlr_xdg_foreign_v1.c', 'wlr_xdg_foreign_v2.c', 'wlr_xdg_foreign_registry.c', diff --git a/types/wlr_xdg_dialog_v1.c b/types/wlr_xdg_dialog_v1.c new file mode 100644 index 000000000..f9e73c7e4 --- /dev/null +++ b/types/wlr_xdg_dialog_v1.c @@ -0,0 +1,192 @@ +#include +#include + +#include +#include + +#include "xdg-dialog-v1-protocol.h" + +// NOTE: xdg_dialog_v1 becomes inert when the corresponding xdg_toplevel is destroyed + +#define XDG_WM_DIALOG_V1_VERSION 1 + +static const struct xdg_dialog_v1_interface dialog_impl; + +static struct wlr_xdg_dialog_v1 *dialog_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_dialog_v1_interface, &dialog_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct xdg_wm_dialog_v1_interface wm_impl; + +static struct wlr_xdg_wm_dialog_v1 *wm_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_wm_dialog_v1_interface, &wm_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + // As wlr_xdg_toplevel is always destroyed before the surface, this should + // never be reached. + abort(); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "xdg_wm_dialog_v1", + .destroy = surface_addon_destroy, +}; + +static void set_modal(struct wlr_xdg_dialog_v1 *dialog, bool modal) { + if (dialog->modal == modal) { + return; + } + dialog->modal = modal; + wl_signal_emit_mutable(&dialog->events.set_modal, NULL); +} + +static void dialog_handle_set_modal(struct wl_client *client, struct wl_resource *resource) { + struct wlr_xdg_dialog_v1 *dialog = dialog_from_resource(resource); + if (dialog == NULL) { + return; + } + set_modal(dialog, true); +} + +static void dialog_handle_unset_modal(struct wl_client *client, struct wl_resource *resource) { + struct wlr_xdg_dialog_v1 *dialog = dialog_from_resource(resource); + if (dialog == NULL) { + return; + } + set_modal(dialog, false); +}; + +static const struct xdg_dialog_v1_interface dialog_impl = { + .destroy = resource_destroy, + .set_modal = dialog_handle_set_modal, + .unset_modal = dialog_handle_unset_modal, +}; + +static void dialog_destroy(struct wlr_xdg_dialog_v1 *dialog) { + if (dialog == NULL) { + return; + } + + wl_signal_emit_mutable(&dialog->events.destroy, NULL); + + wlr_addon_finish(&dialog->surface_addon); + wl_list_remove(&dialog->xdg_toplevel_destroy.link); + + wl_resource_set_user_data(dialog->resource, NULL); + free(dialog); +} + +static void handle_xdg_toplevel_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_dialog_v1 *dialog = wl_container_of(listener, dialog, xdg_toplevel_destroy); + dialog_destroy(dialog); +} + +static void handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_dialog_v1 *dialog = dialog_from_resource(resource); + dialog_destroy(dialog); +} + +static void wm_get_xdg_dialog(struct wl_client *client, struct wl_resource *wm_resource, + uint32_t id, struct wl_resource *toplevel_resource) { + struct wlr_xdg_wm_dialog_v1 *wm = wm_from_resource(wm_resource); + struct wlr_xdg_toplevel *xdg_toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + + struct wlr_addon_set *addon_set = &xdg_toplevel->base->surface->addons; + + if (wlr_addon_find(addon_set, NULL, &surface_addon_impl) != NULL) { + wl_resource_post_error(wm_resource, XDG_WM_DIALOG_V1_ERROR_ALREADY_USED, + "the xdg_toplevel object has already been used to create a xdg_dialog_v1"); + return; + } + + struct wlr_xdg_dialog_v1 *dialog = calloc(1, sizeof(*dialog)); + if (dialog == NULL) { + wl_resource_post_no_memory(wm_resource); + return; + } + + dialog->resource = wl_resource_create(client, &xdg_dialog_v1_interface, + wl_resource_get_version(wm_resource), id); + if (dialog->resource == NULL) { + free(dialog); + wl_resource_post_no_memory(wm_resource); + return; + } + wl_resource_set_implementation(dialog->resource, &dialog_impl, + dialog, handle_resource_destroy); + + dialog->xdg_toplevel = xdg_toplevel; + wlr_addon_init(&dialog->surface_addon, addon_set, NULL, &surface_addon_impl); + + dialog->xdg_toplevel_destroy.notify = handle_xdg_toplevel_destroy; + wl_signal_add(&xdg_toplevel->events.destroy, &dialog->xdg_toplevel_destroy); + + wl_signal_init(&dialog->events.destroy); + wl_signal_init(&dialog->events.set_modal); + + wl_signal_emit_mutable(&wm->events.new_dialog, dialog); +} + +static const struct xdg_wm_dialog_v1_interface wm_impl = { + .destroy = resource_destroy, + .get_xdg_dialog = wm_get_xdg_dialog, +}; + +static void wm_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + struct wlr_xdg_wm_dialog_v1 *wm = data; + struct wl_resource *resource = + wl_resource_create(client, &xdg_wm_dialog_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &wm_impl, wm, NULL); +} + +static void xdg_wm_dialog_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_wm_dialog_v1 *wm = wl_container_of(listener, wm, display_destroy); + wl_list_remove(&wm->display_destroy.link); + wl_global_destroy(wm->global); + free(wm); +} + +struct wlr_xdg_dialog_v1 *wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel( + struct wlr_xdg_toplevel *xdg_toplevel) { + struct wlr_addon *addon = + wlr_addon_find(&xdg_toplevel->base->surface->addons, NULL, &surface_addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_xdg_dialog_v1 *dialog = wl_container_of(addon, dialog, surface_addon); + return dialog; +} + +struct wlr_xdg_wm_dialog_v1 *wlr_xdg_wm_dialog_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= XDG_WM_DIALOG_V1_VERSION); + + struct wlr_xdg_wm_dialog_v1 *wm = calloc(1, sizeof(*wm)); + if (wm == NULL) { + return NULL; + } + + wm->global = wl_global_create(display, &xdg_wm_dialog_v1_interface, version, wm, wm_bind); + if (wm->global == NULL) { + free(wm); + return NULL; + } + + wm->display_destroy.notify = xdg_wm_dialog_handle_display_destroy; + wl_display_add_destroy_listener(display, &wm->display_destroy); + + wl_signal_init(&wm->events.new_dialog); + + return wm; +}