protocols: support xdg-dialog modal toplevels

Advertise xdg_wm_dialog_v1 and mirror each dialog object onto its
window. A modal dialog demands attention: on map (or when set modal
while mapped) its tab is shown and it takes window focus, in both
--tab-groups modes; placement itself is unchanged and session lock
still wins.

wltest gains --dialog-modal/--extra-modal/--extra-delay; smoke tests
cover the global, the background-modal steal with tab-groups on, and
the own-tab steal with tab-groups off.
This commit is contained in:
Andrew Hamilton 2026-09-04 10:00:34 +10:00
parent 42853dcc18
commit b189318124
5 changed files with 264 additions and 2 deletions

View file

@ -380,6 +380,18 @@ on headless/nested outputs such tools learn the output is unsupported
rather than hanging. Like all hardware LUTs, this only takes visible rather than hanging. Like all hardware LUTs, this only takes visible
effect on DRM in standalone mode. effect on DRM in standalone mode.
## Modal dialogs (`xdg-dialog`)
Kick advertises `xdg_wm_dialog_v1`, so toolkits can mark a toplevel modal
instead of relying on process ancestry alone. A modal dialog demands
attention: when it maps (or is set modal while mapped) its tab is shown
and the dialog takes window focus -- the deliberate exception to the
no-steal rule, alongside `Alt+Shift+T`. Placement is unchanged
(`--tab-groups on` still stacks it with its tab; `off` still gives it its
own reachable tab), and modality works identically nested and standalone
(it never depends on the host compositor). Session lock still wins over a
modal.
## Logging (`KICK_LOG`) ## Logging (`KICK_LOG`)
Quiet by default: a plain launch prints nothing to the launching terminal Quiet by default: a plain launch prints nothing to the launching terminal

124
kick.c
View file

@ -118,6 +118,7 @@
#include <wlr/types/wlr_virtual_pointer_v1.h> #include <wlr/types/wlr_virtual_pointer_v1.h>
#include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_activation_v1.h> #include <wlr/types/wlr_xdg_activation_v1.h>
#include <wlr/types/wlr_xdg_dialog_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h> #include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
@ -229,6 +230,17 @@ struct kick_window {
struct wl_listener request_minimize; struct wl_listener request_minimize;
struct wl_listener request_move; struct wl_listener request_move;
struct wl_listener request_resize; struct wl_listener request_resize;
/* xdg-dialog wiring: set when the toplevel carries an xdg_dialog_v1
object (modal flag mirrored). dialog_wired guards removal: the
dialog object lives and dies on its own schedule, and a toplevel
may gain a second one after the first is destroyed. */
struct wl_listener dialog_set_modal;
struct wl_listener dialog_destroy;
bool dialog_wired;
bool modal;
/* The wired xdg_dialog_v1 object (wlroots emits set_modal with NULL
data, so the object is recovered here, not from the event). */
struct wlr_xdg_dialog_v1 *dialog;
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
/* Extra X11-only listeners: surface association and the ICCCM requests /* Extra X11-only listeners: surface association and the ICCCM requests
with no xdg counterpart. map/commit/destroy/request_maximize/ with no xdg counterpart. map/commit/destroy/request_maximize/
@ -400,6 +412,11 @@ struct kick_server {
request_activate we switch to the tab owning that surface. */ request_activate we switch to the tab owning that surface. */
struct wlr_xdg_activation_v1 *xdg_activation; struct wlr_xdg_activation_v1 *xdg_activation;
struct wl_listener xdg_activation_request; struct wl_listener xdg_activation_request;
/* xdg-dialog (xdg-dialog-v1): modal toplevels demand attention -- on
map (or when set modal while mapped) their tab is shown and the
dialog takes window focus, in both --tab-groups modes. */
struct wlr_xdg_wm_dialog_v1 *dialog_mgr;
struct wl_listener dialog_new;
/* Pointer constraints + relative pointer (zwp_pointer_constraints_v1 / /* Pointer constraints + relative pointer (zwp_pointer_constraints_v1 /
zwp_relative_pointer_v1). Only the focused surface's constraint is zwp_relative_pointer_v1). Only the focused surface's constraint is
active; its region lives in the surface's local space, so the surface's active; its region lives in the surface's local space, so the surface's
@ -822,6 +839,10 @@ static void kick_request_redraw(void) {
static struct kick_window *window_for_toplevel(struct wlr_xdg_toplevel *toplevel); static struct kick_window *window_for_toplevel(struct wlr_xdg_toplevel *toplevel);
static void host_teardown_window(void); static void host_teardown_window(void);
static int tab_index(struct kick_tab *tab); static int tab_index(struct kick_tab *tab);
/* xdg-dialog bodies live with the seat handlers; called from claim/map. */
static void modal_activate(struct kick_window *w);
static void dialog_wire(struct kick_window *w,
struct wlr_xdg_dialog_v1 *dialog);
/* Foreign-toplevel (taskbar) handle lifecycle; bodies live with the idle /* Foreign-toplevel (taskbar) handle lifecycle; bodies live with the idle
handlers. Called from window claim/teardown and state sync points. */ handlers. Called from window claim/teardown and state sync points. */
static void foreign_handle_create(struct kick_window *w); static void foreign_handle_create(struct kick_window *w);
@ -2641,6 +2662,11 @@ static void window_detach_xdg_listeners(struct kick_window *w) {
wl_list_remove(&w->request_minimize.link); wl_list_remove(&w->request_minimize.link);
wl_list_remove(&w->request_move.link); wl_list_remove(&w->request_move.link);
wl_list_remove(&w->request_resize.link); wl_list_remove(&w->request_resize.link);
if (w->dialog_wired) {
wl_list_remove(&w->dialog_set_modal.link);
wl_list_remove(&w->dialog_destroy.link);
w->dialog_wired = false;
}
} }
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
@ -3453,12 +3479,19 @@ static void handle_window_map(struct wl_listener *listener, void *data) {
in the tab is enabled, not whether active_toplevel() is set. */ in the tab is enabled, not whether active_toplevel() is set. */
bool activate = window_map_should_activate(w); bool activate = window_map_should_activate(w);
/* Modal dialogs demand attention: a late dialog object (created between
claim and map) is mirrored here, then the modal takes its tab. */
if (!w->dialog_wired && w->toplevel)
dialog_wire(w, wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel(w->toplevel));
/* A window claimed while its tab was still black may have been drawn with /* A window claimed while its tab was still black may have been drawn with
a placeholder icon; once a real client maps it, re-resolve the icon so a placeholder icon; once a real client maps it, re-resolve the icon so
the overlay picks up its app_id. */ the overlay picks up its app_id. */
w->icon_resolved = false; w->icon_resolved = false;
if (activate) { if (w->modal) {
modal_activate(w);
} else if (activate) {
if (w->origin == ORIGIN_GROUP && w->tab == w->parent_tab) { if (w->origin == ORIGIN_GROUP && w->tab == w->parent_tab) {
/* A grouped window of a shown parent tab takes the keyboard /* A grouped window of a shown parent tab takes the keyboard
within it; whether it wins the seat depends on the parent tab within it; whether it wins the seat depends on the parent tab
@ -3673,6 +3706,9 @@ static void handle_new_toplevel(struct wl_listener *listener, void *data) {
} }
claim_xdg_window(w->tab, w, toplevel, cpid, pgid); claim_xdg_window(w->tab, w, toplevel, cpid, pgid);
/* A dialog object created in the same commit as the toplevel already
exists: mirror it now (later ones arrive via handle_dialog_new). */
dialog_wire(w, wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel(toplevel));
} }
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
@ -8463,6 +8499,86 @@ static bool im_grab_forward_modifiers(struct wlr_keyboard *kb) {
return true; return true;
} }
/* ===================== xdg-dialog (modal toplevels) =================
A modal dialog demands attention: when it maps (or is set modal while
mapped) its tab is shown and it takes window focus, even under the
no-steal rule and even with --tab-groups off (where it still owns its
own tab, so it stays individually reachable). Placement itself is
untouched: ancestry grouping stacks it with --tab-groups on exactly as
before. Session lock still wins (set_active_tab/window_enter refuse
while locked). */
/* Show the modal's tab (unless already shown) and focus the dialog. */
static void modal_activate(struct kick_window *w) {
if (!w || !w->mapped || !w->modal)
return;
if (w->tab != server.active_tab)
set_active_tab(w->tab);
window_enter(w);
kick_log(KICK_LOG_INFO, "[dialog] modal app=%d\n", tab_index(w->tab));
}
static void dialog_unwire(struct kick_window *w) {
if (!w || !w->dialog_wired)
return;
wl_list_remove(&w->dialog_set_modal.link);
wl_list_remove(&w->dialog_destroy.link);
w->dialog_wired = false;
w->dialog = NULL;
w->modal = false;
}
static void handle_dialog_set_modal(struct wl_listener *listener,
void *data) {
struct kick_window *w = wl_container_of(listener, w, dialog_set_modal);
(void)data; /* wlroots emits NULL; the object lives on the window. */
if (!w->dialog)
return;
w->modal = w->dialog->modal;
kick_log(KICK_LOG_DEBUG, "[dialog] modal=%d app=%d\n",
w->modal, tab_index(w->tab));
if (w->modal && w->mapped)
modal_activate(w);
}
static void handle_dialog_destroy(struct wl_listener *listener, void *data) {
struct kick_window *w = wl_container_of(listener, w, dialog_destroy);
(void)data;
dialog_unwire(w);
}
/* Mirror an xdg_dialog_v1 object onto its window. The object may appear
before claim (same commit as the toplevel), between claim and map, or
long after mapping -- so claim, manager-new-dialog and map all funnel
here. Re-wiring replaces a dead object when the client makes a second
one for the same toplevel. */
static void dialog_wire(struct kick_window *w,
struct wlr_xdg_dialog_v1 *dialog) {
if (!w || !dialog)
return;
dialog_unwire(w);
w->dialog = dialog;
w->modal = dialog->modal;
w->dialog_set_modal.notify = handle_dialog_set_modal;
wl_signal_add(&dialog->events.set_modal, &w->dialog_set_modal);
w->dialog_destroy.notify = handle_dialog_destroy;
wl_signal_add(&dialog->events.destroy, &w->dialog_destroy);
w->dialog_wired = true;
kick_log(KICK_LOG_DEBUG, "[dialog] wired modal=%d app=%d\n",
w->modal, tab_index(w->tab));
}
static void handle_dialog_new(struct wl_listener *listener, void *data) {
(void)listener;
struct wlr_xdg_dialog_v1 *dialog = data;
struct kick_window *w = window_for_toplevel(dialog->xdg_toplevel);
if (!w)
return;
dialog_wire(w, dialog);
if (w->modal && w->mapped)
modal_activate(w);
}
/* Clamp an absolute pointer position to whatever the active constraint /* Clamp an absolute pointer position to whatever the active constraint
allows, in place. LOCKED pins to the lock position; CONFINED keeps the allows, in place. LOCKED pins to the lock position; CONFINED keeps the
pointer inside the constraint region (empty region = whole surface). */ pointer inside the constraint region (empty region = whole surface). */
@ -10700,6 +10816,12 @@ int main(int argc, char *argv[]) {
wl_signal_add(&server.xdg_activation->events.request_activate, wl_signal_add(&server.xdg_activation->events.request_activate,
&server.xdg_activation_request); &server.xdg_activation_request);
/* xdg-dialog: modal toplevels demand attention (see modal_activate).
Version 1 is the only one wlroots speaks. */
server.dialog_mgr = wlr_xdg_wm_dialog_v1_create(server.wl_display, 1);
server.dialog_new.notify = handle_dialog_new;
wl_signal_add(&server.dialog_mgr->events.new_dialog, &server.dialog_new);
/* The output layout exists in both modes so xdg-output can advertise /* The output layout exists in both modes so xdg-output can advertise
logical output geometry to clients; nested mode simply has one output logical output geometry to clients; nested mode simply has one output
(the host window) in the layout. */ (the host window) in the layout. */

View file

@ -231,6 +231,14 @@ protocols = {
'wlr-protocols/wlr-gamma-control-unstable-v1.xml', 'wlr-protocols/wlr-gamma-control-unstable-v1.xml',
'wlr-gamma-control-unstable-v1', 'wlr-gamma-control-unstable-v1',
], ],
# xdg-dialog (modal toplevels): staging protocol from the system
# wayland-protocols. Only the client stub is generated for wltest;
# the server side is wlroots' wlr_xdg_wm_dialog_v1 (no quoted
# server-header include, so no extra custom_target needed).
'xdg-dialog': [
wl_protocols_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml',
'xdg-dialog-v1',
],
# Content hints (photo/video/game) + single-pixel buffers (1x1 solid # Content hints (photo/video/game) + single-pixel buffers (1x1 solid
# color without a shm pool). Both are staging protocols from the # color without a shm pool). Both are staging protocols from the
# system wayland-protocols, like wlroots itself uses. # system wayland-protocols, like wlroots itself uses.
@ -364,6 +372,7 @@ executable('wltest',
code_by_name['single-pixel'], code_by_name['single-pixel'],
code_by_name['viewporter'], code_by_name['viewporter'],
code_by_name['gamma-control'], code_by_name['gamma-control'],
code_by_name['xdg-dialog'],
client_headers['xdg-shell'], client_headers['xdg-shell'],
client_headers['cursor-shape'], client_headers['cursor-shape'],
client_headers['wlr-layer-shell-unstable-v1'], client_headers['wlr-layer-shell-unstable-v1'],
@ -383,6 +392,7 @@ executable('wltest',
client_headers['single-pixel'], client_headers['single-pixel'],
client_headers['viewporter'], client_headers['viewporter'],
client_headers['gamma-control'], client_headers['gamma-control'],
client_headers['xdg-dialog'],
dependencies: [dependency('wayland-client'), dependencies: [dependency('wayland-client'),
dependency('xkbcommon')]) dependency('xkbcommon')])

View file

@ -1353,6 +1353,64 @@ def test_standalone_headless_gamma_headless_fails_clean(tmp_path):
s.close() s.close()
def test_standalone_headless_dialog_globals(tmp_path):
"""The standalone compositor advertises xdg_wm_dialog_v1 so clients
can mark toplevels modal."""
out = tmp_path / "globals.txt"
s = _spawn_standalone({}, "%s --globals-out '%s' '#112233'" % (WL, out))
try:
deadline = time.monotonic() + 15.0
while time.monotonic() < deadline:
if out.exists():
break
time.sleep(0.1)
assert out.exists(), "wltest --globals-out never wrote its file"
names = {line.split()[0] for line in out.read_text().splitlines()}
assert "xdg_wm_dialog_v1" in names, \
f"missing dialog global; advertised: {sorted(names)}"
finally:
s.close()
def test_standalone_headless_modal_steals_tab(tmp_path):
"""A modal dialog mapping in a background tab takes its tab: the
dialog client opens its modal 1.5s after boot (past settle), and the
settled foreground tab must yield to the dialog's tab."""
s = _spawn_standalone(
{},
"%s --extra '#00ff00' --extra-modal --extra-delay 1500 "
"--app-id dlg '#0000ff'" % WL,
"%s '#ff0000'" % WL)
try:
assert s.wait_any_active() == 1
s.wait_active_frame()
assert s.proc.poll() is None
assert s.wait_active_change(1, timeout=10.0) == 0
s.wait_for(r"\[dialog\] modal app=0\b")
assert s.proc.poll() is None, f"log tail:\n{s.read_log()[-400:]}"
finally:
s.close()
def test_standalone_headless_modal_tab_groups_off(tmp_path):
"""With --tab-groups off a background modal still takes its (own) tab:
it is glued after its parent and activated on map."""
s = _spawn_standalone(
{}, "--tab-groups", "off",
"%s --extra '#00ff00' --extra-modal --extra-delay 1500 "
"--app-id dlg '#0000ff'" % WL,
"%s '#ff0000'" % WL)
try:
assert s.wait_any_active() == 1
s.wait_active_frame()
assert s.proc.poll() is None
# The modal owns the tab glued right after its parent tab.
s.wait_for(r"\[dialog\] modal app=1\b")
assert s.proc.poll() is None, f"log tail:\n{s.read_log()[-400:]}"
finally:
s.close()
def test_nested_primary_selection_bridged(tmp_path): def test_nested_primary_selection_bridged(tmp_path):
"""Nested mode: a tab's primary selection is fanned out to a second tab """Nested mode: a tab's primary selection is fanned out to a second tab
exactly as standalone, and the host-side bridge publishes it over exactly as standalone, and the host-side bridge publishes it over

View file

@ -37,6 +37,7 @@
#include "single-pixel-buffer-v1-client.h" #include "single-pixel-buffer-v1-client.h"
#include "viewporter-client.h" #include "viewporter-client.h"
#include "wlr-gamma-control-unstable-v1-client.h" #include "wlr-gamma-control-unstable-v1-client.h"
#include "xdg-dialog-v1-client.h"
/* Minimal deterministic xdg toplevel test client: shows a solid-color shm /* Minimal deterministic xdg toplevel test client: shows a solid-color shm
buffer and runs until killed. Used by test.sh to give the smoke test two buffer and runs until killed. Used by test.sh to give the smoke test two
@ -143,6 +144,7 @@ static struct zwlr_output_power_manager_v1 *power_mgr;
static struct zwp_virtual_keyboard_manager_v1 *virtual_kb_mgr; static struct zwp_virtual_keyboard_manager_v1 *virtual_kb_mgr;
static struct zwlr_virtual_pointer_manager_v1 *virtual_ptr_mgr; static struct zwlr_virtual_pointer_manager_v1 *virtual_ptr_mgr;
static struct xdg_activation_v1 *activation_mgr; static struct xdg_activation_v1 *activation_mgr;
static struct xdg_wm_dialog_v1 *dialog_mgr;
static struct zwp_text_input_manager_v3 *text_input_mgr; static struct zwp_text_input_manager_v3 *text_input_mgr;
static struct zwp_input_method_manager_v2 *input_method_mgr; static struct zwp_input_method_manager_v2 *input_method_mgr;
static struct wp_content_type_manager_v1 *content_type_mgr; static struct wp_content_type_manager_v1 *content_type_mgr;
@ -369,6 +371,12 @@ static uint8_t PIXEL[4] = { 0xff, 0x00, 0x00, 0xff }; /* RGBA */
toplevel of an already-structured tab gets its own slot glued right after toplevel of an already-structured tab gets its own slot glued right after
its parent). */ its parent). */
static bool want_extra; static bool want_extra;
/* --dialog-modal marks the main toplevel modal; --extra-modal marks the
extra one. --extra-delay <ms> sleeps before opening the extra window
so boot settles first (lets a modal steal a settled tab). */
static bool dialog_modal;
static bool extra_modal;
static int extra_delay_ms;
static bool extra_done; static bool extra_done;
static uint8_t EXTRA_PIXEL[4] = { 0x00, 0xff, 0x00, 0xff }; static uint8_t EXTRA_PIXEL[4] = { 0x00, 0xff, 0x00, 0xff };
static int EX_W = 640, EX_H = 400; static int EX_W = 640, EX_H = 400;
@ -627,6 +635,13 @@ static void xdg_surface_configure(void *data, struct xdg_surface *xs, uint32_t s
wl_surface_commit(surface); wl_surface_commit(surface);
if (want_extra && !extra_done) { if (want_extra && !extra_done) {
extra_done = true; extra_done = true;
if (extra_delay_ms > 0) {
/* Let boot settle (main window mapped, tabs crowned) before
the second window arrives; flush first so the main commit
is not held back by the sleep. */
wl_display_flush(display);
usleep((useconds_t)extra_delay_ms * 1000);
}
extra_open(); extra_open();
} }
} }
@ -801,6 +816,9 @@ static void registry_global(void *data, struct wl_registry *reg, uint32_t name,
activation_mgr = wl_registry_bind(reg, name, activation_mgr = wl_registry_bind(reg, name,
&xdg_activation_v1_interface, &xdg_activation_v1_interface,
version < 1 ? version : 1); version < 1 ? version : 1);
} else if (strcmp(interface, xdg_wm_dialog_v1_interface.name) == 0) {
dialog_mgr = wl_registry_bind(reg, name, &xdg_wm_dialog_v1_interface,
version < 1 ? version : 1);
} else if (strcmp(interface, } else if (strcmp(interface,
zwp_text_input_manager_v3_interface.name) == 0) { zwp_text_input_manager_v3_interface.name) == 0) {
text_input_mgr = wl_registry_bind(reg, name, text_input_mgr = wl_registry_bind(reg, name,
@ -979,6 +997,15 @@ static void extra_open(void) {
xdg_toplevel_set_title(ex_toplevel, "extra"); xdg_toplevel_set_title(ex_toplevel, "extra");
xdg_surface_add_listener(ex_xdg, &extra_xdg_listener, NULL); xdg_surface_add_listener(ex_xdg, &extra_xdg_listener, NULL);
xdg_toplevel_add_listener(ex_toplevel, &extra_toplevel_listener, NULL); xdg_toplevel_add_listener(ex_toplevel, &extra_toplevel_listener, NULL);
if (extra_modal) {
if (!dialog_mgr) {
fprintf(stderr, "wltest: extra-modal: xdg_wm_dialog_v1 missing\n");
exit(1);
}
struct xdg_dialog_v1 *d =
xdg_wm_dialog_v1_get_xdg_dialog(dialog_mgr, ex_toplevel);
xdg_dialog_v1_set_modal(d);
}
wl_surface_commit(ex_surface); wl_surface_commit(ex_surface);
} }
@ -2219,6 +2246,7 @@ static void print_usage(const char *argv0) {
"[--input-method [--im-commit <text>] [--im-out <file>]] " "[--input-method [--im-commit <text>] [--im-out <file>]] "
"[--content-type <none|photo|video|game>] [--single-pixel] " "[--content-type <none|photo|video|game>] [--single-pixel] "
"[--power <on|off>] [--gamma [--gamma-out <file>]] " "[--power <on|off>] [--gamma [--gamma-out <file>]] "
"[--dialog-modal] [--extra-modal] [--extra-delay <ms>] "
"#RRGGBB [WIDTHxHEIGHT]\n" "#RRGGBB [WIDTHxHEIGHT]\n"
" %s shows a solid-color xdg_toplevel, e.g. #ff0000\n" " %s shows a solid-color xdg_toplevel, e.g. #ff0000\n"
" --extra also opens a second toplevel from the same client\n" " --extra also opens a second toplevel from the same client\n"
@ -2252,7 +2280,10 @@ static void print_usage(const char *argv0) {
" --single-pixel backs the toplevel with a 1x1\n" " --single-pixel backs the toplevel with a 1x1\n"
" single-pixel buffer instead of shm\n" " single-pixel buffer instead of shm\n"
" --gamma asks for gamma control, writing gamma_size or\n" " --gamma asks for gamma control, writing gamma_size or\n"
" failed to --gamma-out and exiting", " failed to --gamma-out and exiting\n"
" --dialog-modal marks the toplevel modal via xdg-dialog;\n"
" --extra-modal marks the --extra window modal instead;\n"
" --extra-delay sleeps <ms> before opening it",
argv0, argv0); argv0, argv0);
} }
@ -2283,6 +2314,24 @@ int main(int argc, char **argv) {
} }
want_extra = true; want_extra = true;
i += 2; i += 2;
} else if (strcmp(argv[i], "--dialog-modal") == 0) {
dialog_modal = true;
i += 1;
} else if (strcmp(argv[i], "--extra-modal") == 0) {
extra_modal = true;
want_extra = true;
i += 1;
} else if (strcmp(argv[i], "--extra-delay") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "wltest: --extra-delay needs ms\n");
return 1;
}
extra_delay_ms = atoi(argv[i + 1]);
if (extra_delay_ms < 0) {
fprintf(stderr, "wltest: bad --extra-delay\n");
return 1;
}
i += 2;
} else if (strcmp(argv[i], "--cursor-shape") == 0) { } else if (strcmp(argv[i], "--cursor-shape") == 0) {
if (i + 1 >= argc) { if (i + 1 >= argc) {
fprintf(stderr, "wltest: --cursor-shape needs a value " fprintf(stderr, "wltest: --cursor-shape needs a value "
@ -2859,6 +2908,17 @@ int main(int argc, char **argv) {
viewport = wp_viewporter_get_viewport(viewporter, surface); viewport = wp_viewporter_get_viewport(viewporter, surface);
} }
/* Modal main window: mark the toplevel a dialog before it maps. */
if (dialog_modal) {
if (!dialog_mgr) {
fprintf(stderr, "wltest: dialog-modal: xdg_wm_dialog_v1 missing\n");
return 1;
}
struct xdg_dialog_v1 *d =
xdg_wm_dialog_v1_get_xdg_dialog(dialog_mgr, toplevel);
xdg_dialog_v1_set_modal(d);
}
if (constrain_mode != NO_CONSTRAINT) if (constrain_mode != NO_CONSTRAINT)
request_constraint(); request_constraint();