22 Commits
Author SHA1 Message Date
Guido Cella 5af5dd5e7e update wlroots to 0.21 2026-09-25 13:43:13 +02:00
Guido Cella d3d4da3bd2 Merge branch 'main' into wlroots-next 2026-09-25 13:36:59 +02:00
Guido Cella f1675a4800 Revert "client: do not drop configure events based on current committed size"
This reverts commit 242ffd7a75.

Removing this conditon can cause a xdg_toplevel.configure ↔
wl_surface.commit loop that uses 100% CPU.

Fixes https://codeberg.org/dwl/dwl/issues/1245
Fixes https://codeberg.org/dwl/dwl/issues/1246
2026-09-23 09:03:08 +02:00
Guido Cella a75ca79630 add missing void
Needed to compile on OpenBSD.
2026-09-22 20:42:48 +02:00
Guido Cella 87029e21c0 remove exclusive_focus variable
This is not needed because focusclient() already calls
toplevel_from_wlr_surface(seat->keyboard_state.focused_surface) and can
check if that should have exclusive focus.

When it was added there was no function to get the focused layer shell
surface in this way.
2026-09-22 14:53:00 +02:00
aloires a514a928e4 focusclient: set input method focus for layer surfaces
Focusing a layer surface only set the keyboard focus, not the text-input
focus, so layer-shell clients (launchers, menus, ...) never received a
text-input enter event and IME input didn't work. Pass the layer surface
to focusclient() and set the input method focus there, like we already do
for regular clients.
2026-09-22 16:25:29 +08:00
André Desgualdo Pereira 242ffd7a75 client: do not drop configure events based on current committed size 2026-09-21 14:35:09 -03:00
save196 f22c223cbf don't run keybindings on keyrepeat while locked 2026-09-21 17:07:38 +03:00
save196 b83881f881 don't send pointer events to clients while locked 2026-09-21 12:37:19 +03:00
save196 fa400c5d3d don't run axis bindings when locked 2026-09-21 01:06:08 +03:00
save196 40b0ae76ae bind axis to nothing by default 2026-09-21 00:07:22 +03:00
save196 2a567195e6 don't use the xdg window geometry offset as a position
It is the offset of the visible bounds inside the surface, which is not
zero for clients that pad their surface or place subsurfaces outside it,
so floating windows were mapped offset by their client side decorations.
2026-09-20 19:53:12 +03:00
Guido Cella cc23250527 bump version to 0.10-dev 2026-09-20 12:20:09 +02:00
julmajustus 88c0c68d59 Remove rendermon pending resize frame skip
This is no longer needed as wlroots => 0.19 handles this internally via scene-level surface state tracking and
linux-drm-syncobj explicit sync.

The manual commit skip now causes the c->resize to be stuck on non-zero
state which leads to choppy resizing experience.
2026-09-08 18:04:52 +03:00
A Frederick Christensen d41ecb745c add support for ext-image-copy-capture-v1 and ext-image-capture-source-v1 (wlroots!4545) 2026-03-08 17:35:11 +01:00
A Frederick Christensen 52c4fb58ec Revert "Post 0.3 release modification"
This reverts commit 38c7bd7ee8.
Accidental commit intended for a testing repository.
2026-03-01 19:54:28 -06:00
A Frederick Christensen 38c7bd7ee8 Post 0.3 release modification 2026-03-01 19:53:43 -06:00
A Frederick Christensen 6e1bfa86c7 Remove trailing whitespace from README
Adapted from PR #1188 by @diegoviola
2026-03-01 19:15:22 -06:00
A Frederick Christensen e32535cf97 Fix typos -- adapted from PR #1189 by @diegoviola 2026-03-01 19:10:43 -06:00
thanatos f4249db613 Updated README to clarify branch roles
This change effectively reverts DWL to the prior development model, where development of DWL itself happens against the current wlroots release, and changes in anticipation of the next wlroots release are made on a separate branch.
2026-02-21 11:51:17 -07:00
thanatos b2d2b701d7 Corrected README 2026-02-21 11:37:48 -07:00
Thanatos 2c9cb2af1b Bumped to 0.9 for wlroots-0.20.0. Updated cursor image buffer handling for xwayland 2026-02-21 11:33:09 -07:00
5 changed files with 93 additions and 74 deletions
+1
View File
@@ -189,6 +189,7 @@ client_get_geometry(Client *c, struct wlr_box *geom)
}
#endif
*geom = c->surface.xdg->geometry;
geom->x = geom->y = 0;
}
static inline Client *
+4 -1
View File
@@ -172,6 +172,9 @@ static const Button buttons[] = {
};
static const Axis axes[] = {
/* example of volume control:
{ MODKEY, AxisUp, spawn, SHCMD("volume-up_EXAMPLE") },
{ MODKEY, AxisDown, spawn, SHCMD("volume-down_EXAMPLE") },
{ MODKEY, AxisDown, spawn, SHCMD("volume-down_EXAMPLE") }, */
{ 0, 0, NULL, {0} },
/* does nothing, but the array cannot be empty */
};
+8 -8
View File
@@ -8,21 +8,21 @@ PREFIX = /usr/local
MANDIR = $(PREFIX)/share/man
DATADIR = $(PREFIX)/share
WLR_INCS = `$(PKG_CONFIG) --cflags wlroots-0.20`
WLR_LIBS = `$(PKG_CONFIG) --libs wlroots-0.20`
WLR_INCS = `$(PKG_CONFIG) --cflags wlroots-0.21`
WLR_LIBS = `$(PKG_CONFIG) --libs wlroots-0.21`
# Allow using an alternative wlroots installation
# This has to have all the includes required by wlroots, e.g:
# Assuming wlroots git repo is "${PWD}/wlroots" and you only ran "meson setup build && ninja -C build"
#WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \
# -I$(PWD)/wlroots/include
# WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \
# -I$(PWD)/wlroots/include
# Set -rpath to avoid using the wrong library.
#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/build -L$(PWD)/wlroots/build -lwlroots-0.20
# WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/build -L$(PWD)/wlroots/build -lwlroots-0.21
# Assuming you ran "meson setup --prefix ${PWD}/0.20 build && ninja -C build install"
# Assuming you ran "meson setup --prefix ${PWD}/0.21 build && ninja -C build install"
#WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \
# -I$(PWD)/wlroots/0.20/include/wlroots-0.20
#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/0.20/lib64 -L$(PWD)/wlroots/0.20/lib64 -lwlroots-0.20
# -I$(PWD)/wlroots/0.21/include/wlroots-0.21
#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/0.21/lib64 -L$(PWD)/wlroots/0.21/lib64 -lwlroots-0.21
XWAYLAND =
XLIBS =
+79 -64
View File
@@ -211,6 +211,7 @@ struct Monitor {
struct wl_listener frame;
struct wl_listener destroy;
struct wl_listener request_state;
struct wl_listener commit_lock_surface;
struct wl_listener destroy_lock_surface;
struct wlr_session_lock_surface_v1 *lock_surface;
struct wlr_box m; /* monitor area, layout-relative */
@@ -276,6 +277,7 @@ static void cleanupmon(struct wl_listener *listener, void *data);
static void cleanuplisteners(void);
static void closemon(Monitor *m);
static void commitlayersurfacenotify(struct wl_listener *listener, void *data);
static void commitlocksurface(struct wl_listener *listener, void *data);
static void commitnotify(struct wl_listener *listener, void *data);
static void commitpopup(struct wl_listener *listener, void *data);
static void createdecoration(struct wl_listener *listener, void *data);
@@ -303,7 +305,7 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data);
static void destroysessionlock(struct wl_listener *listener, void *data);
static void destroykeyboardgroup(struct wl_listener *listener, void *data);
static Monitor *dirtomon(enum wlr_direction dir);
static void focusclient(Client *c, int lift);
static void focusclient(Client *c, LayerSurface *l, int lift);
static void focusmon(const Arg *arg);
static void focusstack(const Arg *arg);
static Client *focustop(Monitor *m);
@@ -376,7 +378,6 @@ static void zoom(const Arg *arg);
/* variables */
static pid_t child_pid = -1;
static int locked;
static void *exclusive_focus;
static Client *focused_client;
static struct wl_display *dpy;
static struct wl_event_loop *event_loop;
@@ -642,9 +643,7 @@ arrangelayers(Monitor *m)
!l->mapped)
continue;
/* Deactivate the focused client. */
focusclient(NULL, 0);
exclusive_focus = l;
client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat));
focusclient(NULL, l, 0);
return;
}
}
@@ -663,16 +662,18 @@ axisnotify(struct wl_listener *listener, void *data)
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
if (event->orientation == WL_POINTER_AXIS_VERTICAL_SCROLL)
adir = event->delta > 0 ? AxisDown : AxisUp;
else
adir = event->delta > 0 ? AxisRight : AxisLeft;
keyboard = wlr_seat_get_keyboard(seat);
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
for (a = axes; a < END(axes); a++) {
if (CLEANMASK(mods) == CLEANMASK(a->mod) && adir == a->dir && a->func) {
a->func(&a->arg);
return;
if (!locked) {
if (event->orientation == WL_POINTER_AXIS_VERTICAL_SCROLL)
adir = event->delta > 0 ? AxisDown : AxisUp;
else
adir = event->delta > 0 ? AxisRight : AxisLeft;
keyboard = wlr_seat_get_keyboard(seat);
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
for (a = axes; a < END(axes); a++) {
if (CLEANMASK(mods) == CLEANMASK(a->mod) && adir == a->dir && a->func) {
a->func(&a->arg);
return;
}
}
}
@@ -705,10 +706,9 @@ buttonpress(struct wl_listener *listener, void *data)
or a layer surface with on-demand keyboard interactivity */
xytonode(cursor->x, cursor->y, NULL, &c, &l, NULL, NULL);
if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) {
focusclient(c, 1);
focusclient(c, NULL, 1);
} else if (l && l->layer_surface->current.keyboard_interactive) {
focusclient(NULL, 0);
client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat));
focusclient(NULL, l, 0);
}
keyboard = wlr_seat_get_keyboard(seat);
@@ -746,7 +746,7 @@ buttonpress(struct wl_listener *listener, void *data)
void
capturerequest(struct wl_listener *listener, void *data)
{
struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data;
struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event *request = data;
Client *view = request->toplevel_handle->data;
if (!view->image_capture_source) {
@@ -907,7 +907,7 @@ closemon(Monitor *m)
if (c->mon == m)
setmon(c, selmon, c->tags);
}
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
printstatus();
}
@@ -931,11 +931,6 @@ commitlayersurfacenotify(struct wl_listener *listener, void *data)
return;
}
if (layer_surface == exclusive_focus
&& layer_surface->current.keyboard_interactive !=
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE)
exclusive_focus = NULL;
if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped)
return;
l->mapped = layer_surface->surface->mapped;
@@ -951,6 +946,20 @@ commitlayersurfacenotify(struct wl_listener *listener, void *data)
arrangelayers(l->mon);
}
void
commitlocksurface(struct wl_listener *listener, void *data)
{
Monitor *m = wl_container_of(listener, m, commit_lock_surface);
if (!m->lock_surface->surface->mapped)
return;
wl_list_remove(&m->commit_lock_surface.link);
wl_list_init(&m->commit_lock_surface.link);
motionnotify(0, NULL, 0, 0, 0, 0);
}
void
commitnotify(struct wl_listener *listener, void *data)
{
@@ -1128,6 +1137,7 @@ createlocksurface(struct wl_listener *listener, void *data)
wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height);
LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface);
LISTEN(&lock_surface->surface->events.commit, &m->commit_lock_surface, commitlocksurface);
if (m == selmon)
client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat));
@@ -1349,7 +1359,7 @@ void
destroydragicon(struct wl_listener *listener, void *data)
{
/* Focus enter isn't sent during drag, so refocus the focused node. */
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
motionnotify(0, NULL, 0, 0, 0, 0);
wl_list_remove(&listener->link);
free(listener);
@@ -1388,7 +1398,7 @@ destroylock(SessionLock *lock, int unlock)
wlr_scene_node_set_enabled(&locked_bg->node, 0);
focusclient(focustop(selmon), 0);
focusclient(focustop(selmon), NULL, 0);
motionnotify(0, NULL, 0, 0, 0, 0);
destroy:
@@ -1409,6 +1419,7 @@ destroylocksurface(struct wl_listener *listener, void *data)
m->lock_surface = NULL;
wl_list_remove(&m->destroy_lock_surface.link);
wl_list_remove(&m->commit_lock_surface.link);
if (lock_surface->surface != seat->keyboard_state.focused_surface)
return;
@@ -1417,7 +1428,7 @@ destroylocksurface(struct wl_listener *listener, void *data)
surface = wl_container_of(cur_lock->surfaces.next, surface, link);
client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat));
} else if (!locked) {
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
} else {
wlr_seat_keyboard_clear_focus(seat);
}
@@ -1505,7 +1516,7 @@ dirtomon(enum wlr_direction dir)
}
void
focusclient(Client *c, int lift)
focusclient(Client *c, LayerSurface *l, int lift)
{
struct wlr_surface *old = seat->keyboard_state.focused_surface;
int unused_lx, unused_ly, old_client_type;
@@ -1528,9 +1539,6 @@ focusclient(Client *c, int lift)
wlr_xdg_popup_destroy(popup);
}
if (old_c && old_c == exclusive_focus && client_wants_focus(old_c))
exclusive_focus = NULL;
/* Put the new client atop the focus stack and select its monitor */
if (c && !client_is_unmanaged(c)) {
wl_list_remove(&c->flink);
@@ -1540,14 +1548,16 @@ focusclient(Client *c, int lift)
/* Don't change border color if there is an exclusive focus or we are
* handling a drag operation */
if (!exclusive_focus && !seat->drag)
if (!(old_l && old_l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE)
&& !(old_c && client_wants_focus(old_c))
&& !seat->drag)
client_set_border_color(c, focuscolor);
}
/* If an overlay is focused, don't focus or activate the client,
* but only update its position in fstack to render its border with focuscolor
* and focus it after the overlay is closed. */
if (old && (!c || client_surface(c) != old)
if (!l && old && (!c || client_surface(c) != old)
&& old_client_type == LayerShell && wlr_scene_node_coords(
&old_l->scene->node, &unused_lx, &unused_ly)
&& old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP
@@ -1564,6 +1574,13 @@ focusclient(Client *c, int lift)
}
printstatus();
if (l) {
/* Focus a layer surface and let the input method follow it */
input_method_relay_set_focus(input_method_relay, l->layer_surface->surface);
client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat));
return;
}
if (!c) {
/* With no client, all we have left is to clear focus */
input_method_relay_set_focus(input_method_relay, NULL);
@@ -1595,7 +1612,7 @@ focusmon(const Arg *arg)
selmon = dirtomon(arg->i);
while (!selmon->wlr_output->enabled && i++ < nmons);
}
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
}
void
@@ -1621,7 +1638,7 @@ focusstack(const Arg *arg)
}
}
/* If only one client is visible on selmon, then c == sel */
focusclient(c, 1);
focusclient(c, NULL, 1);
}
/* We probably should change the name of this: it sounds like it
@@ -1810,7 +1827,7 @@ int
keyrepeat(void *data)
{
KeyboardGroup *group = data;
if (group->wlr_group->keyboard.repeat_info.rate <= 0)
if (locked || group->wlr_group->keyboard.repeat_info.rate <= 0)
return 0;
wl_event_source_timer_update(group->key_repeat_source,
@@ -1846,12 +1863,18 @@ locksession(struct wl_listener *listener, void *data)
return;
}
lock = session_lock->data = ecalloc(1, sizeof(*lock));
focusclient(NULL, 0);
focusclient(NULL, NULL, 0);
lock->scene = wlr_scene_tree_create(layers[LyrBlock]);
cur_lock = lock->lock = session_lock;
locked = 1;
/* Cancel any interactive move/resize and take the pointer focus away
* from the clients below the lock screen */
cursor_mode = CurNormal;
grabc = NULL;
motionnotify(0, NULL, 0, 0, 0, 0);
LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface);
LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock);
LISTEN(&session_lock->events.unlock, &lock->unlock, unlocksession);
@@ -1889,10 +1912,8 @@ mapnotify(struct wl_listener *listener, void *data)
/* Unmanaged clients always are floating */
wlr_scene_node_reparent(&c->scene->node, layers[LyrUnmanaged]);
wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y);
if (client_wants_focus(c)) {
focusclient(c, 1);
exclusive_focus = c;
}
if (client_wants_focus(c))
focusclient(c, NULL, 1);
goto unset_fullscreen;
}
@@ -2228,10 +2249,9 @@ pointerfocus(Client *c, LayerSurface *l, struct wlr_surface *surface, double sx,
if (surface != seat->pointer_state.focused_surface && sloppyfocus && time) {
if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) {
focusclient(c, 0);
focusclient(c, NULL, 0);
} else if (l && l->layer_surface->current.keyboard_interactive) {
focusclient(NULL, 0);
client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat));
focusclient(NULL, l, 0);
}
}
@@ -2574,7 +2594,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags)
setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */
setfloating(c, c->isfloating);
}
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
}
void
@@ -2736,14 +2756,14 @@ setup(void)
foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(dpy,1);
ext_foreign_toplevel_image_capture_source_manager_v1 =
wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(dpy, 1);
wl_signal_add(&ext_foreign_toplevel_image_capture_source_manager_v1->events.new_request,
wl_signal_add(&ext_foreign_toplevel_image_capture_source_manager_v1->events.capture_request,
&new_foreign_toplevel_capture_request);
/* Use decoration protocols to negotiate server-side decorations */
wlr_server_decoration_manager_set_default_mode(
wlr_server_decoration_manager_create(dpy),
WLR_SERVER_DECORATION_MANAGER_MODE_SERVER);
xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy);
xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy, 2);
wl_signal_add(&xdg_decoration_mgr->events.new_toplevel_decoration, &new_xdg_decoration);
pointer_constraints = wlr_pointer_constraints_v1_create(dpy);
@@ -2866,7 +2886,7 @@ tag(const Arg *arg)
return;
sel->tags = arg->ui & TAGMASK;
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
arrange(selmon);
printstatus();
}
@@ -2939,7 +2959,7 @@ toggletag(const Arg *arg)
return;
sel->tags = newtags;
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
arrange(selmon);
printstatus();
}
@@ -2952,7 +2972,7 @@ toggleview(const Arg *arg)
return;
selmon->tagset[selmon->seltags] = newtagset;
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
arrange(selmon);
printstatus();
}
@@ -2971,12 +2991,10 @@ unmaplayersurfacenotify(struct wl_listener *listener, void *data)
l->mapped = 0;
wlr_scene_node_set_enabled(&l->scene->node, 0);
if (l == exclusive_focus)
exclusive_focus = NULL;
if (l->layer_surface->output && (l->mon = l->layer_surface->output->data))
arrangelayers(l->mon);
if (l->layer_surface->surface == seat->keyboard_state.focused_surface)
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
motionnotify(0, NULL, 0, 0, 0, 0);
}
@@ -2992,15 +3010,12 @@ unmapnotify(struct wl_listener *listener, void *data)
if (c == focused_client)
focused_client = NULL;
if (client_is_unmanaged(c)) {
if (c == exclusive_focus) {
exclusive_focus = NULL;
focusclient(focustop(selmon), 1);
}
} else {
if (!client_is_unmanaged(c)) {
wl_list_remove(&c->link);
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
} else if (client_wants_focus(c)) {
focusclient(focustop(selmon), NULL, 1);
}
if (c->foreign_toplevel_handle) {
@@ -3114,7 +3129,7 @@ updatemons(struct wl_listener *listener, void *data)
if (!c->mon && client_surface(c)->mapped)
setmon(c, selmon, c->tags);
}
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
if (selmon->lock_surface) {
client_notify_enter(selmon->lock_surface->surface,
wlr_seat_get_keyboard(seat));
@@ -3173,7 +3188,7 @@ view(const Arg *arg)
selmon->seltags ^= 1; /* toggle sel tagset */
if (arg->ui & TAGMASK)
selmon->tagset[selmon->seltags] = arg->ui & TAGMASK;
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
arrange(selmon);
printstatus();
}
@@ -3221,7 +3236,7 @@ xytonode(double x, double y, struct wlr_surface **psurface,
struct wlr_surface *surface = NULL;
int layer;
for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) {
for (layer = NUM_LAYERS - 1; !surface && layer >= (locked ? LyrBlock : 0); layer--) {
if (layer == LyrIMPopup ||
!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny)))
continue;
@@ -3264,7 +3279,7 @@ zoom(const Arg *arg)
wl_list_remove(&sel->link);
wl_list_insert(&clients, &sel->link);
focusclient(sel, 1);
focusclient(sel, NULL, 1);
arrange(selmon);
}
+1 -1
View File
@@ -594,7 +594,7 @@ handle_focused_surface_destroy(struct wl_listener *listener,
}
struct input_method_relay *
input_method_relay_create()
input_method_relay_create(void)
{
struct input_method_relay *relay = ecalloc(1, sizeof(*relay));
wl_list_init(&relay->text_inputs);