20 Commits
Author SHA1 Message Date
Guido Cella 2d16962424 pass the object to LISTEN_STATIC handlers
wlroots 482d6e53 stopped passing the surface to the idle inhibitor
destroy and surface commit signals, which made destroyidleinhibitor()
and commitpopup() dereference NULL. Store the object in the listener
instead.
2026-10-27 23:33:14 +01:00
Guido Cella 7622bd3187 Merge branch 'main' into wlroots-next 2026-10-27 23:33:05 +01:00
Guido Cella 12a5856427 don't allocate memory for drag_icon_destroy
Not needed since dwl supports only one drag icon at a time.
2026-10-27 23:14:43 +01:00
save196 4ecb2a9df5 fix the focused border color after leaving an unmanaged client
Clients focused after an unmanaged client that wants focus didn't get
the focused border color. Regression from 87029e2.
2026-10-26 15:49:23 +02:00
Guido Cella 4953b7ea0c keep outputs off when they reconnect
Some monitors disconnect when they are turned off and reconnect shortly
after, and createmon() enabled them again. Remember the names of
the outputs turned off through output_power_management or
output_management and don't enable them in createmon().

Fixes https://codeberg.org/dwl/dwl/issues/719
2026-10-22 23:35:06 +02:00
Guido Cella 9dfaea2f79 call updatemons() when createmon() leaves an output disabled
This lets you enable it later.

Fixes afbafd5.
2026-10-22 13:31:04 +02:00
Guido Cella afbafd53ab don't crash when initializing more monitors than supported
Fixes https://codeberg.org/dwl/dwl/issues/726
2026-10-21 19:02:00 +02:00
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 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
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
4 changed files with 363 additions and 78 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 - Support screen lockers via ext-session-lock-v1 protocol
- Various Wayland protocols - Various Wayland protocols
- XWayland support as provided by wlroots (can be enabled in `config.mk`) - 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) - Layer shell popups (used by Waybar)
- Damage tracking provided by scenegraph API - Damage tracking provided by scenegraph API
+22 -5
View File
@@ -324,19 +324,36 @@ client_set_scale(struct wlr_surface *s, float scale)
wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(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) client_set_size(Client *c, uint32_t width, uint32_t height)
{ {
#ifdef XWAYLAND #ifdef XWAYLAND
if (client_is_x11(c)) { if (client_is_x11(c)) {
wlr_xwayland_surface_configure(c->surface.xwayland, wlr_xwayland_surface_configure(c->surface.xwayland,
c->geom.x + c->bw, c->geom.y + c->bw, width, height); 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 #endif
if ((int32_t)width != c->surface.xdg->toplevel->current.width if ((int32_t)width == c->surface.xdg->toplevel->scheduled.width
|| (int32_t)height != c->surface.xdg->toplevel->current.height) && (int32_t)height == c->surface.xdg->toplevel->scheduled.height)
wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)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 static inline void
+9 -9
View File
@@ -1,4 +1,4 @@
_VERSION = 0.10-dev _VERSION = 0.9-dev
VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)` VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)`
PKG_CONFIG = pkg-config PKG_CONFIG = pkg-config
@@ -8,21 +8,21 @@ PREFIX = /usr/local
MANDIR = $(PREFIX)/share/man MANDIR = $(PREFIX)/share/man
DATADIR = $(PREFIX)/share DATADIR = $(PREFIX)/share
WLR_INCS = `$(PKG_CONFIG) --cflags wlroots-0.20` WLR_INCS = `$(PKG_CONFIG) --cflags wlroots-0.21`
WLR_LIBS = `$(PKG_CONFIG) --libs wlroots-0.20` WLR_LIBS = `$(PKG_CONFIG) --libs wlroots-0.21`
# Allow using an alternative wlroots installation # Allow using an alternative wlroots installation
# This has to have all the includes required by wlroots, e.g: # 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" # 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 \ # WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \
# -I$(PWD)/wlroots/include # -I$(PWD)/wlroots/include
# Set -rpath to avoid using the wrong library. # 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 \ #WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \
# -I$(PWD)/wlroots/0.20/include/wlroots-0.20 # -I$(PWD)/wlroots/0.21/include/wlroots-0.21
#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/0.20/lib64 -L$(PWD)/wlroots/0.20/lib64 -lwlroots-0.20 #WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/0.21/lib64 -L$(PWD)/wlroots/0.21/lib64 -lwlroots-0.21
XWAYLAND = XWAYLAND =
XLIBS = XLIBS =
+331 -64
View File
@@ -83,7 +83,7 @@
#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags]))
#define TAGMASK ((1u << TAGCOUNT) - 1) #define TAGMASK ((1u << TAGCOUNT) - 1)
#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L)))
#define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) #define LISTEN_STATIC(E, H, D) do { StaticListener *_l = ecalloc(1, sizeof(*_l)); _l->listener.notify = (H); _l->data = (D); wl_signal_add((E), &_l->listener); } while (0)
/* enums */ /* enums */
enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */
@@ -126,6 +126,9 @@ typedef struct {
struct wl_list flink; struct wl_list flink;
struct wlr_box geom; /* layout-relative, includes border */ struct wlr_box geom; /* layout-relative, includes border */
struct wlr_box prev; /* 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 */ struct wlr_box bounds; /* only width and height are used */
union { union {
struct wlr_xdg_surface *xdg; struct wlr_xdg_surface *xdg;
@@ -260,8 +263,15 @@ typedef struct {
struct wl_listener destroy; struct wl_listener destroy;
} SessionLock; } SessionLock;
typedef struct {
/* Must keep this field first */
struct wl_listener listener;
void *data;
} StaticListener;
/* function declarations */ /* function declarations */
static void applybounds(Client *c, struct wlr_box *bbox); static void applybounds(Client *c, struct wlr_box *bbox);
static void applygeom(Client *c);
static void applyrules(Client *c); static void applyrules(Client *c);
static void arrange(Monitor *m); static void arrange(Monitor *m);
static void arrangelayer(Monitor *m, struct wl_list *list, static void arrangelayer(Monitor *m, struct wl_list *list,
@@ -324,6 +334,7 @@ static void mapnotify(struct wl_listener *listener, void *data);
static void maximizenotify(struct wl_listener *listener, void *data); static void maximizenotify(struct wl_listener *listener, void *data);
static void monocle(Monitor *m); static void monocle(Monitor *m);
static void motionabsolute(struct wl_listener *listener, void *data); static void motionabsolute(struct wl_listener *listener, void *data);
static char **monoff(struct wlr_output *wlr_output);
static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx,
double sy, double sx_unaccel, double sy_unaccel); double sy, double sx_unaccel, double sy_unaccel);
static void motionrelative(struct wl_listener *listener, void *data); static void motionrelative(struct wl_listener *listener, void *data);
@@ -349,9 +360,12 @@ static void setfullscreen(Client *c, int fullscreen);
static void setlayout(const Arg *arg); static void setlayout(const Arg *arg);
static void setmfact(const Arg *arg); static void setmfact(const Arg *arg);
static void setmon(Client *c, Monitor *m, uint32_t newtags); static void setmon(Client *c, Monitor *m, uint32_t newtags);
static void setmonoff(struct wlr_output *wlr_output, int off);
static void setpsel(struct wl_listener *listener, void *data); static void setpsel(struct wl_listener *listener, void *data);
static void setsel(struct wl_listener *listener, void *data); static void setsel(struct wl_listener *listener, void *data);
static void setup(void); 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 spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data); static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg); static void tag(const Arg *arg);
@@ -361,6 +375,11 @@ static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg); static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg); static void toggletag(const Arg *arg);
static void toggleview(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 unlocksession(struct wl_listener *listener, void *data);
static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data);
static void unmapnotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data);
@@ -432,6 +451,15 @@ static struct wl_list mons;
static Monitor *selmon; static Monitor *selmon;
static int consumed[KEY_MAX + 1]; 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 */
static struct wl_array offmons; /* char *, names of outputs turned off by clients */
/* global event handlers */ /* global event handlers */
static struct wl_listener cursor_axis = {.notify = axisnotify}; static struct wl_listener cursor_axis = {.notify = axisnotify};
static struct wl_listener cursor_button = {.notify = buttonpress}; static struct wl_listener cursor_button = {.notify = buttonpress};
@@ -460,12 +488,14 @@ static struct wl_listener request_set_sel = {.notify = setsel};
static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape};
static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener request_start_drag = {.notify = requeststartdrag};
static struct wl_listener start_drag = {.notify = startdrag}; static struct wl_listener start_drag = {.notify = startdrag};
static struct wl_listener drag_icon_destroy = {.notify = destroydragicon};
static struct wl_listener new_session_lock = {.notify = locksession}; static struct wl_listener new_session_lock = {.notify = locksession};
static struct wl_listener new_foreign_toplevel_capture_request = {.notify = capturerequest}; static struct wl_listener new_foreign_toplevel_capture_request = {.notify = capturerequest};
#ifdef XWAYLAND #ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data); static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(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 configurex11(struct wl_listener *listener, void *data);
static void createnotifyx11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data);
static void dissociatex11(struct wl_listener *listener, void *data); static void dissociatex11(struct wl_listener *listener, void *data);
@@ -516,6 +546,41 @@ applybounds(Client *c, struct wlr_box *bbox)
c->geom.y = bbox->y; 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 void
applyrules(Client *c) applyrules(Client *c)
{ {
@@ -555,37 +620,18 @@ arrange(Monitor *m)
return; return;
wl_list_for_each(c, &clients, link) { wl_list_for_each(c, &clients, link) {
if (c->mon == m) { if (c->mon == m)
wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m));
client_set_suspended(c, !VISIBLEON(c, m) client_set_suspended(c, !VISIBLEON(c, m)
&& (!c->image_capture_source && (!c->image_capture_source
|| wl_list_empty(&c->image_capture_source->resources))); || 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)); 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) if (m->lt[m->sellt]->arrange)
m->lt[m->sellt]->arrange(m); m->lt[m->sellt]->arrange(m);
motionnotify(0, NULL, 0, 0, 0, 0);
checkidleinhibitor(NULL); transactionschedule();
} }
void void
@@ -745,7 +791,7 @@ buttonpress(struct wl_listener *listener, void *data)
void void
capturerequest(struct wl_listener *listener, void *data) 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; Client *view = request->toplevel_handle->data;
if (!view->image_capture_source) { if (!view->image_capture_source) {
@@ -788,6 +834,8 @@ checkidleinhibitor(struct wlr_surface *exclude)
void void
cleanup(void) cleanup(void)
{ {
char **name;
cleanuplisteners(); cleanuplisteners();
#ifdef XWAYLAND #ifdef XWAYLAND
wlr_xwayland_destroy(xwayland); wlr_xwayland_destroy(xwayland);
@@ -809,6 +857,14 @@ cleanup(void)
* Destroy it until it's fixed on the wlroots side */ * Destroy it until it's fixed on the wlroots side */
wlr_backend_destroy(backend); wlr_backend_destroy(backend);
if (transaction_idle)
wl_event_source_remove(transaction_idle);
wl_event_source_remove(transaction_timer);
wl_array_release(&ghosts);
wl_array_for_each(name, &offmons)
free(*name);
wl_array_release(&offmons);
wl_display_destroy(dpy); wl_display_destroy(dpy);
/* Destroy after the wayland display (when the monitors are already destroyed) /* Destroy after the wayland display (when the monitors are already destroyed)
to avoid destroying them with an invalid scene output. */ to avoid destroying them with an invalid scene output. */
@@ -990,14 +1046,16 @@ commitnotify(struct wl_listener *listener, void *data)
return; return;
} }
transactioncommit(c, c->resize_serial && c->surface.xdg->current.configure_serial
>= c->resize_serial);
resize(c, c->geom, (c->isfloating && !c->isfullscreen)); resize(c, c->geom, (c->isfloating && !c->isfullscreen));
} }
void void
commitpopup(struct wl_listener *listener, void *data) commitpopup(struct wl_listener *listener, void *data)
{ {
struct wlr_surface *surface = data; StaticListener *sl = wl_container_of(listener, sl, listener);
struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); struct wlr_xdg_popup *popup = sl->data;
LayerSurface *l = NULL; LayerSurface *l = NULL;
Client *c = NULL; Client *c = NULL;
struct wlr_box box; struct wlr_box box;
@@ -1040,7 +1098,7 @@ void
createidleinhibitor(struct wl_listener *listener, void *data) createidleinhibitor(struct wl_listener *listener, void *data)
{ {
struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; struct wlr_idle_inhibitor_v1 *idle_inhibitor = data;
LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor, idle_inhibitor);
checkidleinhibitor(NULL); checkidleinhibitor(NULL);
} }
@@ -1186,14 +1244,17 @@ createmon(struct wl_listener *listener, void *data)
* monitor supports only a specific set of modes. We just pick the * monitor supports only a specific set of modes. We just pick the
* monitor's preferred mode; a more sophisticated compositor would let * monitor's preferred mode; a more sophisticated compositor would let
* the user configure it. */ * the user configure it. */
wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output));
/* Set up event listeners */ /* Set up event listeners */
LISTEN(&wlr_output->events.frame, &m->frame, rendermon); LISTEN(&wlr_output->events.frame, &m->frame, rendermon);
LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon);
LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate);
wlr_output_state_set_enabled(&state, 1); /* Some monitors disconnect when they are turned off and reconnect shortly
* after, so keep outputs that were turned off disabled. */
if (!monoff(wlr_output)) {
wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output));
wlr_output_state_set_enabled(&state, 1);
}
wlr_output_commit_state(wlr_output, &state); wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state); wlr_output_state_finish(&state);
@@ -1219,6 +1280,12 @@ createmon(struct wl_listener *listener, void *data)
* output (such as DPI, scale factor, manufacturer, etc). * output (such as DPI, scale factor, manufacturer, etc).
*/ */
m->scene_output = wlr_scene_output_create(scene, wlr_output); m->scene_output = wlr_scene_output_create(scene, wlr_output);
if (!wlr_output->enabled) {
updatemons(NULL, NULL);
return;
}
if (m->m.x == -1 && m->m.y == -1) if (m->m.x == -1 && m->m.y == -1)
wlr_output_layout_add_auto(output_layout, wlr_output); wlr_output_layout_add_auto(output_layout, wlr_output);
else else
@@ -1305,7 +1372,7 @@ createpopup(struct wl_listener *listener, void *data)
/* This event is raised when a client (either xdg-shell or layer-shell) /* This event is raised when a client (either xdg-shell or layer-shell)
* creates a new popup. */ * creates a new popup. */
struct wlr_xdg_popup *popup = data; struct wlr_xdg_popup *popup = data;
LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup, popup);
} }
void void
@@ -1363,15 +1430,15 @@ destroydragicon(struct wl_listener *listener, void *data)
focusclient(focustop(selmon), NULL, 1); focusclient(focustop(selmon), NULL, 1);
motionnotify(0, NULL, 0, 0, 0, 0); motionnotify(0, NULL, 0, 0, 0, 0);
wl_list_remove(&listener->link); wl_list_remove(&listener->link);
free(listener);
} }
void void
destroyidleinhibitor(struct wl_listener *listener, void *data) destroyidleinhibitor(struct wl_listener *listener, void *data)
{ {
/* `data` is the wlr_surface of the idle inhibitor being destroyed, StaticListener *sl = wl_container_of(listener, sl, listener);
* at this point the idle inhibitor is still in the list of the manager */ struct wlr_idle_inhibitor_v1 *idle_inhibitor = sl->data;
checkidleinhibitor(wlr_surface_get_root_surface(data)); /* At this point the idle inhibitor is still in the list of the manager */
checkidleinhibitor(wlr_surface_get_root_surface(idle_inhibitor->surface));
wl_list_remove(&listener->link); wl_list_remove(&listener->link);
free(listener); free(listener);
} }
@@ -1550,7 +1617,6 @@ focusclient(Client *c, LayerSurface *l, int lift)
/* Don't change border color if there is an exclusive focus or we are /* Don't change border color if there is an exclusive focus or we are
* handling a drag operation */ * handling a drag operation */
if (!(old_l && old_l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) 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) && !seat->drag)
client_set_border_color(c, focuscolor); client_set_border_color(c, focuscolor);
} }
@@ -2019,6 +2085,17 @@ motionabsolute(struct wl_listener *listener, void *data)
motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy);
} }
char **
monoff(struct wlr_output *wlr_output)
{
char **name;
wl_array_for_each(name, &offmons) {
if (!strcmp(*name, wlr_output->name))
return name;
}
return NULL;
}
void void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel) double dx_unaccel, double dy_unaccel)
@@ -2205,6 +2282,7 @@ outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test)
/* Ensure displays previously disabled by wlr-output-power-management-v1 /* Ensure displays previously disabled by wlr-output-power-management-v1
* are properly handled*/ * are properly handled*/
m->asleep = 0; m->asleep = 0;
setmonoff(wlr_output, !config_head->state.enabled);
/* Don't move monitors if position wouldn't change. This avoids /* Don't move monitors if position wouldn't change. This avoids
* wlroots marking the output as manually configured. * wlroots marking the output as manually configured.
@@ -2323,6 +2401,10 @@ powermgrsetmode(struct wl_listener *listener, void *data)
m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the output */ m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the output */
wlr_output_state_set_enabled(&state, event->mode); wlr_output_state_set_enabled(&state, event->mode);
/* The output was reconnected while off and was never modeset */
if (event->mode && !m->wlr_output->current_mode)
wlr_output_state_set_mode(&state, wlr_output_preferred_mode(m->wlr_output));
setmonoff(m->wlr_output, !event->mode);
wlr_output_commit_state(m->wlr_output, &state); wlr_output_commit_state(m->wlr_output, &state);
m->asleep = !event->mode; m->asleep = !event->mode;
@@ -2385,7 +2467,7 @@ void
resize(Client *c, struct wlr_box geo, int interact) resize(Client *c, struct wlr_box geo, int interact)
{ {
struct wlr_box *bbox; struct wlr_box *bbox;
struct wlr_box clip; uint32_t serial;
if (!c->mon || !client_surface(c)->mapped) if (!c->mon || !client_surface(c)->mapped)
return; return;
@@ -2396,20 +2478,27 @@ resize(Client *c, struct wlr_box geo, int interact)
c->geom = geo; c->geom = geo;
applybounds(c, bbox); applybounds(c, bbox);
/* Update scene-graph, including borders */ if ((serial = client_set_size(c, c->geom.width - 2 * c->bw,
wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); c->geom.height - 2 * c->bw)))
wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); c->resize_serial = serial;
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_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); if (interact || (!c->resize_serial && wlr_box_equal(&c->geom, &c->current))) {
client_get_clip(c, &clip); c->resize_serial = 0;
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); 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 void
@@ -2510,14 +2599,10 @@ setcursorshape(struct wl_listener *listener, void *data)
void void
setfloating(Client *c, int floating) setfloating(Client *c, int floating)
{ {
Client *p = client_get_parent(c);
c->isfloating = floating; c->isfloating = floating;
/* If in floating layout do not change the client's layer */ /* 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) if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange)
return; return;
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ||
(p && p->isfullscreen) ? LyrFS
: c->isfloating ? LyrFloat : LyrTile]);
arrange(c->mon); arrange(c->mon);
printstatus(); printstatus();
} }
@@ -2530,8 +2615,6 @@ setfullscreen(Client *c, int fullscreen)
return; return;
c->bw = fullscreen ? 0 : borderpx; c->bw = fullscreen ? 0 : borderpx;
client_set_fullscreen(c, fullscreen); client_set_fullscreen(c, fullscreen);
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen
? LyrFS : c->isfloating ? LyrFloat : LyrTile]);
if (fullscreen) { if (fullscreen) {
c->prev = c->geom; c->prev = c->geom;
@@ -2597,6 +2680,22 @@ setmon(Client *c, Monitor *m, uint32_t newtags)
focusclient(focustop(selmon), NULL, 1); focusclient(focustop(selmon), NULL, 1);
} }
void
setmonoff(struct wlr_output *wlr_output, int off)
{
char **name = monoff(wlr_output), **last;
if (off && !name && (name = wl_array_add(&offmons, sizeof(*name)))) {
if (!(*name = strdup(wlr_output->name)))
offmons.size -= sizeof(*name);
} else if (!off && name) {
free(*name);
last = (char **)((char *)offmons.data + offmons.size) - 1;
*name = *last;
offmons.size -= sizeof(*name);
}
}
void void
setpsel(struct wl_listener *listener, void *data) setpsel(struct wl_listener *listener, void *data)
{ {
@@ -2735,6 +2834,10 @@ setup(void)
wl_list_init(&clients); wl_list_init(&clients);
wl_list_init(&fstack); wl_list_init(&fstack);
transaction_timer = wl_event_loop_add_timer(event_loop, transactiontimeout, NULL);
wl_array_init(&ghosts);
wl_array_init(&offmons);
xdg_shell = wlr_xdg_shell_create(dpy, 6); 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_toplevel, &new_xdg_toplevel);
wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup);
@@ -2756,14 +2859,14 @@ setup(void)
foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(dpy,1); foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(dpy,1);
ext_foreign_toplevel_image_capture_source_manager_v1 = ext_foreign_toplevel_image_capture_source_manager_v1 =
wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(dpy, 1); 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); &new_foreign_toplevel_capture_request);
/* Use decoration protocols to negotiate server-side decorations */ /* Use decoration protocols to negotiate server-side decorations */
wlr_server_decoration_manager_set_default_mode( wlr_server_decoration_manager_set_default_mode(
wlr_server_decoration_manager_create(dpy), wlr_server_decoration_manager_create(dpy),
WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); 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); wl_signal_add(&xdg_decoration_mgr->events.new_toplevel_decoration, &new_xdg_decoration);
pointer_constraints = wlr_pointer_constraints_v1_create(dpy); pointer_constraints = wlr_pointer_constraints_v1_create(dpy);
@@ -2856,6 +2959,57 @@ setup(void)
#endif #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 void
spawn(const Arg *arg) spawn(const Arg *arg)
{ {
@@ -2875,7 +3029,7 @@ startdrag(struct wl_listener *listener, void *data)
return; return;
drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node;
LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); wl_signal_add(&drag->icon->events.destroy, &drag_icon_destroy);
} }
void void
@@ -2977,6 +3131,88 @@ toggleview(const Arg *arg)
printstatus(); 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 void
unlocksession(struct wl_listener *listener, void *data) unlocksession(struct wl_listener *listener, void *data)
{ {
@@ -3003,6 +3239,7 @@ unmapnotify(struct wl_listener *listener, void *data)
{ {
/* Called when the surface is unmapped, and should no longer be shown. */ /* Called when the surface is unmapped, and should no longer be shown. */
Client *c = wl_container_of(listener, c, unmap); Client *c = wl_container_of(listener, c, unmap);
struct wlr_scene_node *ghost, **slot;
if (c == grabc) { if (c == grabc) {
cursor_mode = CurNormal; cursor_mode = CurNormal;
grabc = NULL; grabc = NULL;
@@ -3011,6 +3248,15 @@ unmapnotify(struct wl_listener *listener, void *data)
focused_client = NULL; focused_client = NULL;
if (!client_is_unmanaged(c)) { 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();
}
wl_list_remove(&c->link); wl_list_remove(&c->link);
setmon(c, NULL, 0); setmon(c, NULL, 0);
wl_list_remove(&c->flink); wl_list_remove(&c->flink);
@@ -3034,6 +3280,9 @@ unmapnotify(struct wl_listener *listener, void *data)
c->image_capture_source = NULL; c->image_capture_source = NULL;
wlr_scene_node_destroy(&c->scene->node); wlr_scene_node_destroy(&c->scene->node);
client_surface(c)->data = NULL; client_surface(c)->data = NULL;
c->snapshot = NULL;
c->resize_serial = 0;
c->current = (struct wlr_box){0};
printstatus(); printstatus();
motionnotify(0, NULL, 0, 0, 0, 0); motionnotify(0, NULL, 0, 0, 0, 0);
} }
@@ -3233,20 +3482,26 @@ xytonode(double x, double y, struct wlr_surface **psurface,
Client **pc, LayerSurface **pl, double *nx, double *ny) Client **pc, LayerSurface **pl, double *nx, double *ny)
{ {
struct wlr_scene_node *node; struct wlr_scene_node *node;
struct wlr_scene_surface *scene_surface;
struct wlr_surface *surface = NULL; struct wlr_surface *surface = NULL;
Client *c = NULL;
int layer; int layer;
for (layer = NUM_LAYERS - 1; !surface && layer >= (locked ? LyrBlock : 0); layer--) { for (layer = NUM_LAYERS - 1; !surface && !c && layer >= (locked ? LyrBlock : 0); layer--) {
if (layer == LyrIMPopup || 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; continue;
if (node->type == WLR_SCENE_NODE_BUFFER) if ((scene_surface = wlr_scene_surface_try_from_buffer(
surface = wlr_scene_surface_try_from_buffer( wlr_scene_buffer_from_node(node))))
wlr_scene_buffer_from_node(node))->surface; surface = scene_surface->surface;
else /* snapshot of a client during a transaction */
c = node->data;
} }
if (psurface) *psurface = surface; if (psurface) *psurface = surface;
if (pc) *pc = c;
toplevel_from_wlr_surface(surface, pc, pl); toplevel_from_wlr_surface(surface, pc, pl);
} }
@@ -3299,10 +3554,21 @@ associatex11(struct wl_listener *listener, void *data)
{ {
Client *c = wl_container_of(listener, c, associate); 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.map, &c->map, mapnotify);
LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); 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 void
configurex11(struct wl_listener *listener, void *data) configurex11(struct wl_listener *listener, void *data)
{ {
@@ -3351,6 +3617,7 @@ void
dissociatex11(struct wl_listener *listener, void *data) dissociatex11(struct wl_listener *listener, void *data)
{ {
Client *c = wl_container_of(listener, c, dissociate); Client *c = wl_container_of(listener, c, dissociate);
wl_list_remove(&c->commit.link);
wl_list_remove(&c->map.link); wl_list_remove(&c->map.link);
wl_list_remove(&c->unmap.link); wl_list_remove(&c->unmap.link);
} }