6 Commits
Author SHA1 Message Date
guyuming a6e2c155de fix focustop(): prefer a visible fullscreen client
Problem (reproducible on vanilla 0.9, unrelated to any local patch):
1. fullscreen client A on tag 3 (MOD+e);
2. MOD+0 to view all tags;
3. MOD+1 to switch to tag 1, focus lands on client B;
4. MOD+0 again: client A is displayed fullscreen, covering everything,
   but the focused client is the invisible B;
5. so MOD+e / MOD+q act on B behind A and the user thinks MOD+e does
   not work on A until A is clicked with the mouse.

Root cause: focustop() returns the first VISIBLEON() client on the
fstack, but VISIBLEON() only checks whether the client is on a
currently visible tag; it does not check whether the client is covered
by a fullscreen client. When switching back to tag 0, the last focused
client B is first on the fstack, so B gets the focus while A is still
displayed fullscreen.

Fix: focustop() first looks for a visible fullscreen client on the
fstack (at most one fullscreen client per monitor is actually shown,
and it always sits on top, covering the whole monitor), and only falls
back to the previous "first visible client" if there is none. As a
result:
- after a tag switch the focus goes back to the fullscreen client that
  actually occupies the screen, so MOD+e / MOD+q behave as expected;
- the fullscreen_bg decision in arrange() is fixed as a side effect
  (it used to be turned off when the focus was not on the fullscreen
  client, so a non-opaque fullscreen surface could reveal other
  clients behind it, which violates the protocol);
- focusstack() already returns early when the focused client is
  fullscreen, so MOD+j/k and the arrow keys no longer move the focus
  to a covered client either.

Verified with headless dwl, two foot clients and keys injected through
zwp_virtual_keyboard_v1: before the fix the last step gives
focal = winB / fullscreen 0; after the fix focal = winA / fullscreen 1,
and MOD+e then restores A as expected.

ta: 20261031_022753
2026-11-02 15:34:33 +08:00
Guido Cella be2bd72314 remove old comment
This was added 4 years in ago in 8cdb997. New users won't understand
what "old behavior" refers to, so this line is confusing. And it is
self-evident that you should change the alpha value if you want
transparency.
2026-10-29 17:51:48 +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
2 changed files with 58 additions and 10 deletions
-1
View File
@@ -12,7 +12,6 @@ static const float rootcolor[] = COLOR(0x222222ff);
static const float bordercolor[] = COLOR(0x444444ff); static const float bordercolor[] = COLOR(0x444444ff);
static const float focuscolor[] = COLOR(0x005577ff); static const float focuscolor[] = COLOR(0x005577ff);
static const float urgentcolor[] = COLOR(0xff0000ff); static const float urgentcolor[] = COLOR(0xff0000ff);
/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */
static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */
/* tagging - TAGCOUNT must be no greater than 31 */ /* tagging - TAGCOUNT must be no greater than 31 */
+57 -8
View File
@@ -328,6 +328,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);
@@ -353,6 +354,7 @@ 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);
@@ -450,6 +452,7 @@ static struct wl_event_source *transaction_idle;
static struct wl_event_source *transaction_timer; static struct wl_event_source *transaction_timer;
static int transaction_waiting; static int transaction_waiting;
static struct wl_array ghosts; /* struct wlr_scene_node *, unmapped clients */ 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};
@@ -479,6 +482,7 @@ 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};
@@ -824,6 +828,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);
@@ -849,6 +855,9 @@ cleanup(void)
wl_event_source_remove(transaction_idle); wl_event_source_remove(transaction_idle);
wl_event_source_remove(transaction_timer); wl_event_source_remove(transaction_timer);
wl_array_release(&ghosts); 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)
@@ -1229,14 +1238,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);
/* 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_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);
@@ -1263,8 +1275,10 @@ createmon(struct wl_listener *listener, void *data)
*/ */
m->scene_output = wlr_scene_output_create(scene, wlr_output); m->scene_output = wlr_scene_output_create(scene, wlr_output);
if (!wlr_output->enabled) if (!wlr_output->enabled) {
updatemons(NULL, NULL);
return; 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);
@@ -1410,7 +1424,6 @@ 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
@@ -1597,7 +1610,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);
} }
@@ -1688,10 +1700,14 @@ Client *
focustop(Monitor *m) focustop(Monitor *m)
{ {
Client *c; Client *c;
wl_list_for_each(c, &fstack, flink) {
wl_list_for_each(c, &fstack, flink)
if (VISIBLEON(c, m) && c->isfullscreen)
return c;
wl_list_for_each(c, &fstack, flink)
if (VISIBLEON(c, m)) if (VISIBLEON(c, m))
return c; return c;
}
return NULL; return NULL;
} }
@@ -2066,6 +2082,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)
@@ -2252,6 +2279,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.
@@ -2370,6 +2398,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;
@@ -2645,6 +2677,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)
{ {
@@ -2785,6 +2833,7 @@ setup(void)
transaction_timer = wl_event_loop_add_timer(event_loop, transactiontimeout, NULL); transaction_timer = wl_event_loop_add_timer(event_loop, transactiontimeout, NULL);
wl_array_init(&ghosts); 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);
@@ -2977,7 +3026,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