20 Commits
Author SHA1 Message Date
Guido Cella 4ff9db2c40 Revert "remove zero flickering lie"
This reverts commit b1c101515a.

Fixed by d3fa329.
2026-10-20 21:26:05 +02:00
Guido Cella d3fa32993c fix flicker when arranging tiled windows
Fixes https://codeberg.org/dwl/dwl/issues/552
Fixes https://codeberg.org/dwl/dwl/issues/705

Also, floating children of fullscreen windows in floating layout now go
in LyrFS instead of LyrTile.
2026-10-20 21:23:49 +02:00
Guido Cella f08d8b3913 fix segfault when XWayland fails to initialize
Fixes https://codeberg.org/dwl/dwl/issues/1202
2026-10-19 22:57:02 +02:00
Guido Cella 90e7c722e9 remove client_is_rendered_on_mon() and client_is_stopped()
Unused since 70c33c0.
2026-10-19 21:17:43 +02:00
Guido CellaandDanD e2038450f2 fix popup positioning
Related: https://codeberg.org/dwl/dwl/pulls/1244

Co-authored-by: DanD <dandgg.ftw@gmail.com>
2026-09-29 13:54:59 +02:00
save196 4fd683cc84 fix X11 clients shifting by the border width on map
c->geom includes the border, but client_get_geometry() returns the
content position. Subtract the border, clamped to the layout edges.
2026-09-29 00:18:49 +03:00
Guido Cella d29259c26a refactor focusclient() 2026-09-28 13:18:49 +02:00
Guido Cella fccce97448 remove obsolete comment
No longer accurate since a514a92.
2026-09-28 12:19:24 +02:00
Guido Cella d9ddaba762 also make clients tiled on map
While making them tiled on the first commit was needed to fix Firefox
starting with rounded corners, if you don't set them tiled again on map
resizing mpv after starting it with a rule that makes it floating
behaves wrong.

Fixes d1ebf3c.
Fixes https://codeberg.org/dwl/dwl/issues/1247.
2026-09-26 19:31:15 +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
6 changed files with 378 additions and 172 deletions
+1
View File
@@ -153,6 +153,7 @@ given the base on which it is built. Implemented default features are:
- Support screen lockers via ext-session-lock-v1 protocol
- Various Wayland protocols
- XWayland support as provided by wlroots (can be enabled in `config.mk`)
- Zero flickering - Wayland users naturally expect that "every frame is perfect"
- Layer shell popups (used by Waybar)
- Damage tracking provided by scenegraph API
+23 -47
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 *
@@ -257,48 +258,6 @@ client_is_float_type(Client *c)
(min.width == max.width || min.height == max.height));
}
static inline int
client_is_rendered_on_mon(Client *c, Monitor *m)
{
/* This is needed for when you don't want to check formal assignment,
* but rather actual displaying of the pixels.
* Usually VISIBLEON suffices and is also faster. */
struct wlr_surface_output *s;
int unused_lx, unused_ly;
if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly))
return 0;
wl_list_for_each(s, &client_surface(c)->current_outputs, link)
if (s->output == m->wlr_output)
return 1;
return 0;
}
static inline int
client_is_stopped(Client *c)
{
int pid;
siginfo_t in = {0};
#ifdef XWAYLAND
if (client_is_x11(c))
return 0;
#endif
wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL);
if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) {
/* This process is not our child process, while is very unlikely that
* it is stopped, in order to do not skip frames, assume that it is. */
if (errno == ECHILD)
return 1;
} else if (in.si_pid) {
if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED)
return 1;
if (in.si_code == CLD_CONTINUED)
return 0;
}
return 0;
}
static inline int
client_is_unmanaged(Client *c)
{
@@ -365,19 +324,36 @@ client_set_scale(struct wlr_surface *s, float scale)
wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale));
}
static inline void
/* Returns nonzero if the client has to commit a buffer with the new size */
static inline uint32_t
client_set_size(Client *c, uint32_t width, uint32_t height)
{
#ifdef XWAYLAND
if (client_is_x11(c)) {
wlr_xwayland_surface_configure(c->surface.xwayland,
c->geom.x + c->bw, c->geom.y + c->bw, width, height);
return;
return (int)width != client_surface(c)->current.width
|| (int)height != client_surface(c)->current.height;
}
#endif
if ((int32_t)width != c->surface.xdg->toplevel->current.width
|| (int32_t)height != c->surface.xdg->toplevel->current.height)
wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height);
if ((int32_t)width == c->surface.xdg->toplevel->scheduled.width
&& (int32_t)height == c->surface.xdg->toplevel->scheduled.height)
return 0;
return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height);
}
static inline void
client_send_frame_done_iterator(struct wlr_surface *s, int sx, int sy, void *data)
{
wlr_surface_send_frame_done(s, data);
}
static inline void
client_send_frame_done(Client *c)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_surface_for_each_surface(client_surface(c), client_send_frame_done_iterator, &now);
}
static inline void
+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 */
};
+1 -1
View File
@@ -1,4 +1,4 @@
_VERSION = 0.9-dev
_VERSION = 0.10-dev
VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)`
PKG_CONFIG = pkg-config
+348 -122
View File
@@ -126,6 +126,9 @@ typedef struct {
struct wl_list flink;
struct wlr_box geom; /* layout-relative, includes border */
struct wlr_box prev; /* layout-relative, includes border */
struct wlr_box current; /* geom currently shown in the scene graph */
struct wlr_scene_node *snapshot; /* content shown while resizing */
uint32_t resize_serial; /* nonzero while waiting for a resized buffer */
struct wlr_box bounds; /* only width and height are used */
union {
struct wlr_xdg_surface *xdg;
@@ -211,6 +214,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 */
@@ -261,6 +265,7 @@ typedef struct {
/* function declarations */
static void applybounds(Client *c, struct wlr_box *bbox);
static void applygeom(Client *c);
static void applyrules(Client *c);
static void arrange(Monitor *m);
static void arrangelayer(Monitor *m, struct wl_list *list,
@@ -276,6 +281,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 +309,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);
@@ -350,6 +356,8 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags);
static void setpsel(struct wl_listener *listener, void *data);
static void setsel(struct wl_listener *listener, void *data);
static void setup(void);
static struct wlr_scene_node *snapshot(struct wlr_scene_node *node,
struct wlr_scene_tree *parent, void *data);
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
@@ -359,6 +367,11 @@ static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void transactionapply(void);
static void transactioncheck(void *data);
static void transactioncommit(Client *c, int ready);
static void transactionschedule(void);
static int transactiontimeout(void *data);
static void unlocksession(struct wl_listener *listener, void *data);
static void unmaplayersurfacenotify(struct wl_listener *listener, void *data);
static void unmapnotify(struct wl_listener *listener, void *data);
@@ -376,7 +389,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;
@@ -431,6 +443,14 @@ static struct wl_list mons;
static Monitor *selmon;
static int consumed[KEY_MAX + 1];
/* Transactions: the scene graph is updated only once all visible clients have
* committed buffers with their new size, or after TRANSACTION_TIMEOUT ms */
#define TRANSACTION_TIMEOUT 100
static struct wl_event_source *transaction_idle;
static struct wl_event_source *transaction_timer;
static int transaction_waiting;
static struct wl_array ghosts; /* struct wlr_scene_node *, unmapped clients */
/* global event handlers */
static struct wl_listener cursor_axis = {.notify = axisnotify};
static struct wl_listener cursor_button = {.notify = buttonpress};
@@ -465,6 +485,7 @@ static struct wl_listener new_foreign_toplevel_capture_request = {.notify = capt
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
static void commitx11(struct wl_listener *listener, void *data);
static void configurex11(struct wl_listener *listener, void *data);
static void createnotifyx11(struct wl_listener *listener, void *data);
static void dissociatex11(struct wl_listener *listener, void *data);
@@ -515,6 +536,41 @@ applybounds(Client *c, struct wlr_box *bbox)
c->geom.y = bbox->y;
}
void
applygeom(Client *c)
{
struct wlr_box clip;
Client *p = client_get_parent(c);
/* We move all floating clients to LyrTile while in floating layout
* to avoid "real" floating clients be always on top */
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen
|| (p && p->isfullscreen) ? LyrFS
: c->isfloating && c->mon->lt[c->mon->sellt]->arrange ? LyrFloat
: LyrTile]);
c->current = c->geom;
if (c->snapshot) {
wlr_scene_node_destroy(c->snapshot);
c->snapshot = NULL;
wlr_scene_node_set_enabled(&c->scene_surface->node, 1);
}
/* Update scene-graph, including borders */
wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y);
wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw);
wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw);
wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw);
wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw);
wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw);
wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw);
wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw);
wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw);
client_get_clip(c, &clip);
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip);
}
void
applyrules(Client *c)
{
@@ -554,37 +610,18 @@ arrange(Monitor *m)
return;
wl_list_for_each(c, &clients, link) {
if (c->mon == m) {
wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m));
if (c->mon == m)
client_set_suspended(c, !VISIBLEON(c, m)
&& (!c->image_capture_source
|| wl_list_empty(&c->image_capture_source->resources)));
}
}
wlr_scene_node_set_enabled(&m->fullscreen_bg->node,
(c = focustop(m)) && c->isfullscreen);
strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol));
/* We move all clients (except fullscreen and unmanaged) to LyrTile while
* in floating layout to avoid "real" floating clients be always on top */
wl_list_for_each(c, &clients, link) {
if (c->mon != m || c->scene->node.parent == layers[LyrFS])
continue;
wlr_scene_node_reparent(&c->scene->node,
(!m->lt[m->sellt]->arrange && c->isfloating)
? layers[LyrTile]
: (m->lt[m->sellt]->arrange && c->isfloating)
? layers[LyrFloat]
: c->scene->node.parent);
}
if (m->lt[m->sellt]->arrange)
m->lt[m->sellt]->arrange(m);
motionnotify(0, NULL, 0, 0, 0, 0);
checkidleinhibitor(NULL);
transactionschedule();
}
void
@@ -641,10 +678,7 @@ arrangelayers(Monitor *m)
l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE ||
!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 +697,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 +741,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);
@@ -810,6 +845,11 @@ cleanup(void)
* Destroy it until it's fixed on the wlroots side */
wlr_backend_destroy(backend);
if (transaction_idle)
wl_event_source_remove(transaction_idle);
wl_event_source_remove(transaction_timer);
wl_array_release(&ghosts);
wl_display_destroy(dpy);
/* Destroy after the wayland display (when the monitors are already destroyed)
to avoid destroying them with an invalid scene output. */
@@ -877,8 +917,10 @@ cleanuplisteners(void)
wl_list_remove(&new_session_lock.link);
wl_list_remove(&new_foreign_toplevel_capture_request.link);
#ifdef XWAYLAND
wl_list_remove(&new_xwayland_surface.link);
wl_list_remove(&xwayland_ready.link);
if (xwayland) {
wl_list_remove(&new_xwayland_surface.link);
wl_list_remove(&xwayland_ready.link);
}
#endif
}
@@ -907,7 +949,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 +973,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 +988,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)
{
@@ -980,6 +1031,8 @@ commitnotify(struct wl_listener *listener, void *data)
return;
}
transactioncommit(c, c->resize_serial && c->surface.xdg->current.configure_serial
>= c->resize_serial);
resize(c, c->geom, (c->isfloating && !c->isfullscreen));
}
@@ -1006,8 +1059,8 @@ commitpopup(struct wl_listener *listener, void *data)
return;
}
box = type == LayerShell ? l->mon->m : c->mon->w;
box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x);
box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y);
box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x - c->surface.xdg->geometry.x);
box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y - c->surface.xdg->geometry.y);
wlr_xdg_popup_unconstrain_from_box(popup, &box);
wl_list_remove(&listener->link);
free(listener);
@@ -1128,6 +1181,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 +1403,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 +1442,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 +1463,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 +1472,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,9 +1560,9 @@ 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;
struct wlr_surface *surface, *old = seat->keyboard_state.focused_surface;
int unused_lx, unused_ly, old_client_type;
Client *old_c = NULL;
LayerSurface *old_l = NULL;
@@ -1528,9 +1583,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 +1592,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,24 +1618,23 @@ focusclient(Client *c, int lift)
}
printstatus();
if (!c) {
if (!c && !l) {
/* With no client, all we have left is to clear focus */
input_method_relay_set_focus(input_method_relay, NULL);
wlr_seat_keyboard_notify_clear_focus(seat);
return;
}
/* Change cursor surface */
surface = c ? client_surface(c) : l->layer_surface->surface;
input_method_relay_set_focus(input_method_relay, surface);
client_notify_enter(surface, wlr_seat_get_keyboard(seat));
if (l)
return;
client_activate_surface(surface, 1);
motionnotify(0, NULL, 0, 0, 0, 0);
input_method_relay_set_focus(input_method_relay, client_surface(c));
/* Have a client, so focus its top-level wlr_surface */
client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat));
/* Activate the new client */
client_activate_surface(client_surface(c), 1);
if (!client_is_unmanaged(c))
focused_client = c;
}
@@ -1595,7 +1648,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 +1674,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 +1863,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 +1899,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 +1948,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;
}
@@ -1903,8 +1960,15 @@ mapnotify(struct wl_listener *listener, void *data)
}
/* Initialize client geometry with room for border */
client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT |
WLR_EDGE_RIGHT);
c->geom.width += 2 * c->bw;
c->geom.height += 2 * c->bw;
if (client_is_x11(c)) {
int x = c->geom.x - (int)c->bw, y = c->geom.y - (int)c->bw;
c->geom.x = c->geom.x < sgeom.x ? x : MAX(x, sgeom.x);
c->geom.y = c->geom.y < sgeom.y ? y : MAX(y, sgeom.y);
}
/* Insert this client into client lists. */
wl_list_insert(&clients, &c->link);
@@ -2228,10 +2292,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);
}
}
@@ -2365,7 +2428,7 @@ void
resize(Client *c, struct wlr_box geo, int interact)
{
struct wlr_box *bbox;
struct wlr_box clip;
uint32_t serial;
if (!c->mon || !client_surface(c)->mapped)
return;
@@ -2376,20 +2439,27 @@ resize(Client *c, struct wlr_box geo, int interact)
c->geom = geo;
applybounds(c, bbox);
/* Update scene-graph, including borders */
wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y);
wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw);
wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw);
wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw);
wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw);
wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw);
wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw);
wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw);
wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw);
if ((serial = client_set_size(c, c->geom.width - 2 * c->bw,
c->geom.height - 2 * c->bw)))
c->resize_serial = serial;
client_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw);
client_get_clip(c, &clip);
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip);
if (interact || (!c->resize_serial && wlr_box_equal(&c->geom, &c->current))) {
c->resize_serial = 0;
applygeom(c);
return;
}
/* Until the transaction is applied, keep showing the current content
* instead of buffers with the new size at the old position. */
if (c->resize_serial) {
if (!c->snapshot && c->scene->node.enabled
&& (c->snapshot = snapshot(&c->scene_surface->node, c->scene, c)))
wlr_scene_node_set_enabled(&c->scene_surface->node, 0);
/* The scene graph doesn't send frame done events to hidden
* surfaces, but the client may be waiting for one to redraw */
client_send_frame_done(c);
}
transactionschedule();
}
void
@@ -2490,14 +2560,10 @@ setcursorshape(struct wl_listener *listener, void *data)
void
setfloating(Client *c, int floating)
{
Client *p = client_get_parent(c);
c->isfloating = floating;
/* If in floating layout do not change the client's layer */
if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange)
return;
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ||
(p && p->isfullscreen) ? LyrFS
: c->isfloating ? LyrFloat : LyrTile]);
arrange(c->mon);
printstatus();
}
@@ -2510,8 +2576,6 @@ setfullscreen(Client *c, int fullscreen)
return;
c->bw = fullscreen ? 0 : borderpx;
client_set_fullscreen(c, fullscreen);
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen
? LyrFS : c->isfloating ? LyrFloat : LyrTile]);
if (fullscreen) {
c->prev = c->geom;
@@ -2574,7 +2638,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
@@ -2715,6 +2779,9 @@ setup(void)
wl_list_init(&clients);
wl_list_init(&fstack);
transaction_timer = wl_event_loop_add_timer(event_loop, transactiontimeout, NULL);
wl_array_init(&ghosts);
xdg_shell = wlr_xdg_shell_create(dpy, 6);
wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel);
wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup);
@@ -2836,6 +2903,57 @@ setup(void)
#endif
}
struct wlr_scene_node *
snapshot(struct wlr_scene_node *node, struct wlr_scene_tree *parent, void *data)
{
/* Copy the visible part of a scene graph subtree, keeping references to
* the buffers so that they can be shown after the client changes them */
struct wlr_scene_node *child, *copy;
struct wlr_scene_tree *tree;
struct wlr_scene_rect *rect;
struct wlr_scene_buffer *src, *buffer;
if (!node->enabled)
return NULL;
switch (node->type) {
case WLR_SCENE_NODE_TREE:
tree = wlr_scene_tree_create(parent);
wl_list_for_each(child, &wlr_scene_tree_from_node(node)->children, link)
snapshot(child, tree, data);
copy = &tree->node;
break;
case WLR_SCENE_NODE_RECT:
rect = wlr_scene_rect_from_node(node);
copy = &wlr_scene_rect_create(parent, rect->width, rect->height, rect->color)->node;
break;
case WLR_SCENE_NODE_BUFFER:
src = wlr_scene_buffer_from_node(node);
buffer = wlr_scene_buffer_create(parent, NULL);
wlr_scene_buffer_set_dest_size(buffer, src->dst_width, src->dst_height);
wlr_scene_buffer_set_opaque_region(buffer, &src->opaque_region);
wlr_scene_buffer_set_opacity(buffer, src->opacity);
wlr_scene_buffer_set_filter_mode(buffer, src->filter_mode);
wlr_scene_buffer_set_transfer_function(buffer, src->transfer_function);
wlr_scene_buffer_set_primaries(buffer, src->primaries);
wlr_scene_buffer_set_color_encoding(buffer, src->color_encoding);
wlr_scene_buffer_set_color_range(buffer, src->color_range);
wlr_scene_buffer_set_source_box(buffer, &src->src_box);
wlr_scene_buffer_set_transform(buffer, src->transform);
wlr_scene_buffer_set_buffer(buffer, src->buffer);
copy = &buffer->node;
break;
default:
return NULL;
}
copy->data = data;
wlr_scene_node_set_position(copy, node->x, node->y);
if (node->parent == parent)
wlr_scene_node_place_above(copy, node);
return copy;
}
void
spawn(const Arg *arg)
{
@@ -2866,7 +2984,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 +3057,7 @@ toggletag(const Arg *arg)
return;
sel->tags = newtags;
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
arrange(selmon);
printstatus();
}
@@ -2952,11 +3070,93 @@ toggleview(const Arg *arg)
return;
selmon->tagset[selmon->seltags] = newtagset;
focusclient(focustop(selmon), 1);
focusclient(focustop(selmon), NULL, 1);
arrange(selmon);
printstatus();
}
void
transactionapply(void)
{
struct wlr_scene_node **ghost;
Client *c;
Monitor *m;
wl_event_source_timer_update(transaction_timer, 0);
transaction_waiting = 0;
wl_array_for_each(ghost, &ghosts)
wlr_scene_node_destroy(*ghost);
ghosts.size = 0;
wl_list_for_each(c, &clients, link) {
c->resize_serial = 0;
if (!c->mon)
continue;
if (c->mon->wlr_output->enabled)
wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, c->mon));
applygeom(c);
}
wl_list_for_each(m, &mons, link) {
if (m->wlr_output->enabled)
wlr_scene_node_set_enabled(&m->fullscreen_bg->node,
(c = focustop(m)) && c->isfullscreen);
}
motionnotify(0, NULL, 0, 0, 0, 0);
checkidleinhibitor(NULL);
}
void
transactioncheck(void *data)
{
Client *c;
transaction_idle = NULL;
/* Hidden clients don't have to redraw before the layout is shown */
wl_list_for_each(c, &clients, link) {
if (c->resize_serial && VISIBLEON(c, c->mon) && c->mon->wlr_output->enabled) {
if (!transaction_waiting) {
/* Don't restart the timer on updates of the pending
* layout, so the delay is bounded */
wl_event_source_timer_update(transaction_timer, TRANSACTION_TIMEOUT);
transaction_waiting = 1;
}
return;
}
}
transactionapply();
}
void
transactioncommit(Client *c, int ready)
{
if (c->resize_serial && ready) {
c->resize_serial = 0;
transactionschedule();
} else if (c->snapshot) {
/* This buffer is not shown, let the client draw again */
client_send_frame_done(c);
}
}
void
transactionschedule(void)
{
if (!transaction_idle)
transaction_idle = wl_event_loop_add_idle(event_loop, transactioncheck, NULL);
}
int
transactiontimeout(void *data)
{
transactionapply();
return 0;
}
void
unlocksession(struct wl_listener *listener, void *data)
{
@@ -2971,12 +3171,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);
}
@@ -2985,6 +3183,7 @@ unmapnotify(struct wl_listener *listener, void *data)
{
/* Called when the surface is unmapped, and should no longer be shown. */
Client *c = wl_container_of(listener, c, unmap);
struct wlr_scene_node *ghost, **slot;
if (c == grabc) {
cursor_mode = CurNormal;
grabc = NULL;
@@ -2992,15 +3191,21 @@ 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);
if (!client_is_unmanaged(c)) {
/* Keep showing the client until the transaction rearranging the
* remaining ones is applied, to not show a gap */
if ((ghost = snapshot(&c->scene->node, c->scene->node.parent, NULL))) {
if ((slot = wl_array_add(&ghosts, sizeof(ghost))))
*slot = ghost;
else
wlr_scene_node_destroy(ghost);
transactionschedule();
}
} else {
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) {
@@ -3019,6 +3224,9 @@ unmapnotify(struct wl_listener *listener, void *data)
c->image_capture_source = NULL;
wlr_scene_node_destroy(&c->scene->node);
client_surface(c)->data = NULL;
c->snapshot = NULL;
c->resize_serial = 0;
c->current = (struct wlr_box){0};
printstatus();
motionnotify(0, NULL, 0, 0, 0, 0);
}
@@ -3114,7 +3322,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 +3381,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();
}
@@ -3218,20 +3426,26 @@ xytonode(double x, double y, struct wlr_surface **psurface,
Client **pc, LayerSurface **pl, double *nx, double *ny)
{
struct wlr_scene_node *node;
struct wlr_scene_surface *scene_surface;
struct wlr_surface *surface = NULL;
Client *c = NULL;
int layer;
for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) {
for (layer = NUM_LAYERS - 1; !surface && !c && layer >= (locked ? LyrBlock : 0); layer--) {
if (layer == LyrIMPopup ||
!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny)))
!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))
|| node->type != WLR_SCENE_NODE_BUFFER)
continue;
if (node->type == WLR_SCENE_NODE_BUFFER)
surface = wlr_scene_surface_try_from_buffer(
wlr_scene_buffer_from_node(node))->surface;
if ((scene_surface = wlr_scene_surface_try_from_buffer(
wlr_scene_buffer_from_node(node))))
surface = scene_surface->surface;
else /* snapshot of a client during a transaction */
c = node->data;
}
if (psurface) *psurface = surface;
if (pc) *pc = c;
toplevel_from_wlr_surface(surface, pc, pl);
}
@@ -3264,7 +3478,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);
}
@@ -3284,10 +3498,21 @@ associatex11(struct wl_listener *listener, void *data)
{
Client *c = wl_container_of(listener, c, associate);
LISTEN(&client_surface(c)->events.commit, &c->commit, commitx11);
LISTEN(&client_surface(c)->events.map, &c->map, mapnotify);
LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify);
}
void
commitx11(struct wl_listener *listener, void *data)
{
Client *c = wl_container_of(listener, c, commit);
struct wlr_surface *surface = client_surface(c);
transactioncommit(c, surface->current.width == c->geom.width - 2 * (int)c->bw
&& surface->current.height == c->geom.height - 2 * (int)c->bw);
}
void
configurex11(struct wl_listener *listener, void *data)
{
@@ -3336,6 +3561,7 @@ void
dissociatex11(struct wl_listener *listener, void *data)
{
Client *c = wl_container_of(listener, c, dissociate);
wl_list_remove(&c->commit.link);
wl_list_remove(&c->map.link);
wl_list_remove(&c->unmap.link);
}
+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);