From 53e3e60d4de4ee3c8521ae0896bf1cf3d96c7222 Mon Sep 17 00:00:00 2001 From: A Frederick Christensen Date: Sat, 27 Dec 2025 16:47:38 -0600 Subject: [PATCH 01/10] config.def.h whitespace cleanup --- config.def.h | 68 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/config.def.h b/config.def.h index 804a5cb..bcaa8a0 100644 --- a/config.def.h +++ b/config.def.h @@ -124,40 +124,40 @@ static const char *menucmd[] = { "wmenu-run", NULL }; static const Key keys[] = { /* Note that Shift changes certain key codes: 2 -> at, etc. */ - /* modifier key function argument */ - { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, - { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, - { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, - { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, - { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, - { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, - { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, - { MODKEY, XKB_KEY_Return, zoom, {0} }, - { MODKEY, XKB_KEY_Tab, view, {0} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} }, - { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, - { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, - { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, - { MODKEY, XKB_KEY_space, setlayout, {0} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, - { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, - { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, - { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, - { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, - TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), - TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), - TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), - TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), - TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), - TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), - TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), - TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), - TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_q, quit, {0} }, + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), + TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), + TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), + TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_q, quit, {0} }, /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, From 9b11a49cb757b80f8589088b487b5f1e56643aa1 Mon Sep 17 00:00:00 2001 From: A Frederick Christensen Date: Sat, 27 Dec 2025 17:06:27 -0600 Subject: [PATCH 02/10] config.def.h clarify Rule and MonitorRule comments closes #660 --- config.def.h | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/config.def.h b/config.def.h index bcaa8a0..8a6eda0 100644 --- a/config.def.h +++ b/config.def.h @@ -20,12 +20,11 @@ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You ca /* logging */ static int log_level = WLR_ERROR; -/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ static const Rule rules[] = { /* app_id title tags mask isfloating monitor */ - /* examples: */ { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + /* default/example rule: can be changed but cannot be eliminated; at least one rule must exist */ }; /* layout(s) */ @@ -38,17 +37,14 @@ static const Layout layouts[] = { /* monitors */ /* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator - * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients - * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 -*/ -/* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients due to + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 */ static const MonitorRule monrules[] = { - /* name mfact nmaster scale layout rotate/reflect x y */ - /* example of a HiDPI laptop monitor: - { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, - */ - /* defaults */ + /* name mfact nmaster scale layout rotate/reflect x y + * example of a HiDPI laptop monitor: + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, */ { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + /* default monitor rule: can be changed but cannot be eliminated; at least one monitor rule must exist */ }; /* keyboard */ From a8915224e8159e6e0b6ff0fe7b2c6436fb3a1b6d Mon Sep 17 00:00:00 2001 From: fauxmight Date: Mon, 9 Feb 2026 08:06:25 +0100 Subject: [PATCH 03/10] Remove "unmaintained" notice from README User @thanatos has offered to take on the lead developer role for dwl. --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 38211c4..390788d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,5 @@ # dwl - dwm for Wayland -2025-08-16: -dwl IS CURRENTLY UN-MAINTAINED. -AT THE PRESENT TIME, I (@fauxmight) DO NOT HAVE -THE TIME OR CAPACITY TO KEEP UP WITH [wlroots] CHANGES. -IF YOU ARE INTERESTED IN TAKING ON LEAD DEVELOPER RESPONSIBILITIES, -SEE ISSUE [#1166](https://codeberg.org/dwl/dwl/issues/1166). ---- - Join us on our IRC channel: [#dwl on Libera Chat] Or on the community-maintained [Discord server]. From ca4123072d63ff52351f3a6bd64ae4a3e7a9888f Mon Sep 17 00:00:00 2001 From: Guido Cella Date: Tue, 3 Feb 2026 11:44:47 +0100 Subject: [PATCH 04/10] add support for the data control protocol Enables the protocol documented at https://wayland.app/protocols/ext-data-control-v1 It is the upstreamed version of the old wlr_data_control. It is used e.g. by mpv to read and write the clipboard. --- dwl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dwl.c b/dwl.c index 44f3ad9..320910d 100644 --- a/dwl.c +++ b/dwl.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -2518,6 +2519,7 @@ setup(void) wlr_export_dmabuf_manager_v1_create(dpy); wlr_screencopy_manager_v1_create(dpy); wlr_data_control_manager_v1_create(dpy); + wlr_ext_data_control_manager_v1_create(dpy, 1); wlr_primary_selection_v1_device_manager_create(dpy); wlr_viewporter_create(dpy); wlr_single_pixel_buffer_manager_v1_create(dpy); From 227cdf012812955ceda554fadc0f97240a845632 Mon Sep 17 00:00:00 2001 From: thanatos Date: Sat, 21 Feb 2026 16:46:08 +0100 Subject: [PATCH 05/10] Added Matrix to README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 390788d..30ffea3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # dwl - dwm for Wayland -Join us on our IRC channel: [#dwl on Libera Chat] -Or on the community-maintained [Discord server]. +Join us on our [Discord server] +Or Matrix: [#dwl-official:matrix.org] +Or on our IRC channel: [#dwl on Libera Chat] dwl is a compact, hackable compositor for [Wayland] based on [wlroots]. It is intended to fill the same space in the Wayland world that [dwm] does in X11, @@ -209,3 +210,4 @@ inspiration, and to the various contributors to the project, including: [wiki]: https://codeberg.org/dwl/dwl/wiki/Home#compatible-status-bars [Discord server]: https://discord.gg/jJxZnrGPWN [Wayland]: https://wayland.freedesktop.org/ +[#dwl-official:matrix.org]: https://matrix.to/#/#dwl-official:matrix.org \ No newline at end of file From 908a73da8228a9e0ce0dab1dbeaa18b795724cf9 Mon Sep 17 00:00:00 2001 From: thanatos Date: Sat, 21 Feb 2026 16:53:19 +0100 Subject: [PATCH 06/10] Corrected README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 30ffea3..d5f2098 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # dwl - dwm for Wayland -Join us on our [Discord server] -Or Matrix: [#dwl-official:matrix.org] +Join us on our [Discord server] +Or Matrix: [#dwl-official:matrix.org] Or on our IRC channel: [#dwl on Libera Chat] dwl is a compact, hackable compositor for [Wayland] based on [wlroots]. It is From 9b76ee8e90b6ad3600b4370aca36be1f6b12b1b7 Mon Sep 17 00:00:00 2001 From: thanatos Date: Sat, 21 Feb 2026 19:50:17 +0100 Subject: [PATCH 07/10] 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. --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d5f2098..c81efd0 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,13 @@ with each release on the [release] page ### Development branch [main] Active development progresses on the `main` branch. The `main` branch is built -against a late (and often changing) git commit of wlroots. While the adventurous -are welcome to use `main`, it is a rocky road. Using `main` requires that the -user be willing to chase git commits of wlroots. Testing development pull -requests may involve merging unmerged pull requests in [wlroots]' git repository -and/or git commits of wayland. +against the latest release of [wlroots]. PRs should target this branch unless they +depend on functionality that is not in the current release of `wlroots`. + +### Preview branch [wlroots-next] +The `wlroots-next` branch is built against the git version of [wlroots], which +is unstable and changes frequently. PRs requiring functionality from the git +version of `wlroots` should target this branch. ### Building dwl dwl has the following dependencies: @@ -203,6 +205,7 @@ inspiration, and to the various contributors to the project, including: [dwl-patches]: https://codeberg.org/dwl/dwl-patches [list of useful resources on our wiki]: https://codeberg.org/dwl/dwl/wiki/Home#migrating-from-x [main]: https://codeberg.org/dwl/dwl/src/branch/main +[wlroots-next]: https://codeberg.org/dwl/dwl/src/branch/wlroots-next [release]: https://codeberg.org/dwl/dwl/releases [runit]: http://smarden.org/runit/faq.html#userservices [s6]: https://skarnet.org/software/s6/ From cb4cc3ae8ff2bdc7e41ae7ab6e888b0c3b176b16 Mon Sep 17 00:00:00 2001 From: Diego Viola Date: Wed, 25 Feb 2026 02:38:09 -0300 Subject: [PATCH 08/10] Fix typos --- dwl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dwl.c b/dwl.c index 320910d..101a45f 100644 --- a/dwl.c +++ b/dwl.c @@ -586,7 +586,7 @@ arrangelayers(Monitor *m) arrange(m); } - /* Arrange non-exlusive surfaces from top->bottom */ + /* Arrange non-exclusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 0); @@ -2134,7 +2134,7 @@ powermgrsetmode(struct wl_listener *listener, void *data) if (!m) return; - m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the ouput */ + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the output */ wlr_output_state_set_enabled(&state, event->mode); wlr_output_commit_state(m->wlr_output, &state); @@ -2456,7 +2456,7 @@ setup(void) wlr_log_init(log_level, NULL); /* The Wayland display is managed by libwayland. It handles accepting - * clients from the Unix socket, manging Wayland globals, and so on. */ + * clients from the Unix socket, managing Wayland globals, and so on. */ dpy = wl_display_create(); event_loop = wl_display_get_event_loop(dpy); From a2d03cf6188350005dbdaac12f3b0fbc4d60c567 Mon Sep 17 00:00:00 2001 From: Diego Viola Date: Wed, 25 Feb 2026 02:26:38 -0300 Subject: [PATCH 09/10] Remove trailing whitespace from README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c81efd0..3a3b9bd 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ depend on functionality that is not in the current release of `wlroots`. The `wlroots-next` branch is built against the git version of [wlroots], which is unstable and changes frequently. PRs requiring functionality from the git version of `wlroots` should target this branch. - + ### Building dwl dwl has the following dependencies: - libinput @@ -213,4 +213,4 @@ inspiration, and to the various contributors to the project, including: [wiki]: https://codeberg.org/dwl/dwl/wiki/Home#compatible-status-bars [Discord server]: https://discord.gg/jJxZnrGPWN [Wayland]: https://wayland.freedesktop.org/ -[#dwl-official:matrix.org]: https://matrix.to/#/#dwl-official:matrix.org \ No newline at end of file +[#dwl-official:matrix.org]: https://matrix.to/#/#dwl-official:matrix.org From 47a1135b594c0b61a15c8ac6ebf8e38b8adab7cb Mon Sep 17 00:00:00 2001 From: Xory Date: Sun, 15 Mar 2026 18:22:56 +0200 Subject: [PATCH 10/10] patchset 1 --- Makefile | 2 +- client.h | 6 + client.h.orig | 404 +++ config.def.h | 56 +- config.def.h.orig | 200 ++ config.def.h.rej | 11 + drwl.h | 310 +++ dwl.c | 732 +++++- dwl.c.orig | 3658 +++++++++++++++++++++++++++ dwl.c.rej | 32 + patches/autostart.patch | 141 ++ patches/bar.patch | 1271 ++++++++++ patches/controlled_fullscreen.patch | 132 + patches/dim_unfocused.patch | 216 ++ patches/gaps.patch | 127 + patches/movestack.patch | 87 + patches/simpleborders.patch | 73 + patches/unclutter.patch | 192 ++ patches/warpcursor.patch | 71 + 19 files changed, 7580 insertions(+), 141 deletions(-) create mode 100644 client.h.orig create mode 100644 config.def.h.orig create mode 100644 config.def.h.rej create mode 100644 drwl.h create mode 100644 dwl.c.orig create mode 100644 dwl.c.rej create mode 100644 patches/autostart.patch create mode 100644 patches/bar.patch create mode 100644 patches/controlled_fullscreen.patch create mode 100644 patches/dim_unfocused.patch create mode 100644 patches/gaps.patch create mode 100644 patches/movestack.patch create mode 100644 patches/simpleborders.patch create mode 100644 patches/unclutter.patch create mode 100644 patches/warpcursor.patch diff --git a/Makefile b/Makefile index 578194f..279b1c0 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ -Wfloat-conversion # CFLAGS / LDFLAGS -PKGS = wayland-server xkbcommon libinput $(XLIBS) +PKGS = wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) diff --git a/client.h b/client.h index d9f90bb..7a7792d 100644 --- a/client.h +++ b/client.h @@ -318,6 +318,12 @@ client_set_border_color(Client *c, const float color[static 4]) wlr_scene_rect_set_color(c->border[i], color); } +static inline void +client_set_dimmer_state(Client *c, const int dim) +{ + wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); +} + static inline void client_set_fullscreen(Client *c, int fullscreen) { diff --git a/client.h.orig b/client.h.orig new file mode 100644 index 0000000..d9f90bb --- /dev/null +++ b/client.h.orig @@ -0,0 +1,404 @@ +/* + * Attempt to consolidate unavoidable suck into one file, away from dwl.c. This + * file is not meant to be pretty. We use a .h file with static inline + * functions instead of a separate .c module, or function pointers like sway, so + * that they will simply compile out if the chosen #defines leave them unused. + */ + +/* Leave these functions first; they're used in the others */ +static inline int +client_is_x11(Client *c) +{ +#ifdef XWAYLAND + return c->type == X11; +#endif + return 0; +} + +static inline struct wlr_surface * +client_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->surface; +#endif + return c->surface.xdg->surface; +} + +static inline int +toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) +{ + struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; + struct wlr_surface *root_surface; + struct wlr_layer_surface_v1 *layer_surface; + Client *c = NULL; + LayerSurface *l = NULL; + int type = -1; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; +#endif + + if (!s) + return -1; + root_surface = wlr_surface_get_root_surface(s); + +#ifdef XWAYLAND + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { + c = xsurface->data; + type = c->type; + goto end; + } +#endif + + if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { + l = layer_surface->data; + type = LayerShell; + goto end; + } + + xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); + while (xdg_surface) { + tmp_xdg_surface = NULL; + switch (xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_POPUP: + if (!xdg_surface->popup || !xdg_surface->popup->parent) + return -1; + + tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); + + if (!tmp_xdg_surface) + return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); + + xdg_surface = tmp_xdg_surface; + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + c = xdg_surface->data; + type = c->type; + goto end; + case WLR_XDG_SURFACE_ROLE_NONE: + return -1; + } + } + +end: + if (pl) + *pl = l; + if (pc) + *pc = c; + return type; +} + +/* The others */ +static inline void +client_activate_surface(struct wlr_surface *s, int activated) +{ + struct wlr_xdg_toplevel *toplevel; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { + wlr_xwayland_surface_activate(xsurface, activated); + return; + } +#endif + if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) + wlr_xdg_toplevel_set_activated(toplevel, activated); +} + +static inline uint32_t +client_set_bounds(Client *c, int32_t width, int32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= + XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION && width >= 0 && height >= 0 + && (c->bounds.width != width || c->bounds.height != height)) { + c->bounds.width = width; + c->bounds.height = height; + return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); + } + return 0; +} + +static inline const char * +client_get_appid(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->class ? c->surface.xwayland->class : "broken"; +#endif + return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken"; +} + +static inline void +client_get_clip(Client *c, struct wlr_box *clip) +{ + *clip = (struct wlr_box){ + .x = 0, + .y = 0, + .width = c->geom.width - c->bw, + .height = c->geom.height - c->bw, + }; + +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + clip->x = c->surface.xdg->geometry.x; + clip->y = c->surface.xdg->geometry.y; +} + +static inline void +client_get_geometry(Client *c, struct wlr_box *geom) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + geom->x = c->surface.xwayland->x; + geom->y = c->surface.xwayland->y; + geom->width = c->surface.xwayland->width; + geom->height = c->surface.xwayland->height; + return; + } +#endif + *geom = c->surface.xdg->geometry; +} + +static inline Client * +client_get_parent(Client *c) +{ + Client *p = NULL; +#ifdef XWAYLAND + if (client_is_x11(c)) { + if (c->surface.xwayland->parent) + toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); + return p; + } +#endif + if (c->surface.xdg->toplevel->parent) + toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); + return p; +} + +static inline int +client_has_children(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return !wl_list_empty(&c->surface.xwayland->children); +#endif + /* surface.xdg->link is never empty because it always contains at least the + * surface itself. */ + return wl_list_length(&c->surface.xdg->link) > 1; +} + +static inline const char * +client_get_title(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->title ? c->surface.xwayland->title : "broken"; +#endif + return c->surface.xdg->toplevel->title ? c->surface.xdg->toplevel->title : "broken"; +} + +static inline int +client_is_float_type(Client *c) +{ + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_toplevel_state state; + +#ifdef XWAYLAND + if (client_is_x11(c)) { + struct wlr_xwayland_surface *surface = c->surface.xwayland; + xcb_size_hints_t *size_hints = surface->size_hints; + if (surface->modal) + return 1; + + if (wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY)) { + return 1; + } + + return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 + && (size_hints->max_width == size_hints->min_width + || size_hints->max_height == size_hints->min_height); + } +#endif + + toplevel = c->surface.xdg->toplevel; + state = toplevel->current; + return toplevel->parent || (state.min_width != 0 && state.min_height != 0 + && (state.min_width == state.max_width + || state.min_height == state.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) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->override_redirect; +#endif + return 0; +} + +static inline void +client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) +{ + if (kb) + wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes, + kb->num_keycodes, &kb->modifiers); + else + wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL); +} + +static inline void +client_send_close(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_close(c->surface.xwayland); + return; + } +#endif + wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); +} + +static inline void +client_set_border_color(Client *c, const float color[static 4]) +{ + int i; + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], color); +} + +static inline void +client_set_fullscreen(Client *c, int fullscreen) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen); + return; + } +#endif + wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); +} + +static inline void +client_set_scale(struct wlr_surface *s, float scale) +{ + wlr_fractional_scale_v1_notify_scale(s, scale); + wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale)); +} + +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 0; + } +#endif + if ((int32_t)width == c->surface.xdg->toplevel->current.width + && (int32_t)height == c->surface.xdg->toplevel->current.height) + return 0; + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); +} + +static inline void +client_set_tiled(Client *c, uint32_t edges) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_maximized(c->surface.xwayland, + edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE); + return; + } +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) + >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { + wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); + } else { + wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); + } +} + +static inline void +client_set_suspended(Client *c, int suspended) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); +} + +static inline int +client_wants_focus(Client *c) +{ +#ifdef XWAYLAND + return client_is_unmanaged(c) + && wlr_xwayland_surface_override_redirect_wants_focus(c->surface.xwayland) + && wlr_xwayland_surface_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; +#endif + return 0; +} + +static inline int +client_wants_fullscreen(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->fullscreen; +#endif + return c->surface.xdg->toplevel->requested.fullscreen; +} diff --git a/config.def.h b/config.def.h index 8a6eda0..1525337 100644 --- a/config.def.h +++ b/config.def.h @@ -6,25 +6,42 @@ /* appearance */ static const int sloppyfocus = 1; /* focus follows mouse */ static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ +static int gaps = 1; /* 1 means gaps between windows are added */ +static const unsigned int gappx = 10; /* gap pixel between windows */ static const unsigned int borderpx = 1; /* border pixel of windows */ -static const float rootcolor[] = COLOR(0x222222ff); -static const float bordercolor[] = COLOR(0x444444ff); -static const float focuscolor[] = COLOR(0x005577ff); -static const float urgentcolor[] = COLOR(0xff0000ff); +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = {"monospace:size=10"}; +static const float rootcolor[] = COLOR(0x000000ff); +static const float unfocuseddim[] = COLOR(0x00000088); /* 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 uint32_t colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, +}; -/* tagging - TAGCOUNT must be no greater than 31 */ -#define TAGCOUNT (9) +/* tagging */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; /* logging */ static int log_level = WLR_ERROR; +/* Autostart */ +static const char *const autostart[] = { + "wbg", "/path/to/your/image", NULL, + NULL /* terminate */ +}; + static const Rule rules[] = { - /* app_id title tags mask isfloating monitor */ - { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ - { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ - /* default/example rule: can be changed but cannot be eliminated; at least one rule must exist */ + /* app_id title tags mask isfloating neverdim monitor */ + /* examples: + { "Gimp_example", NULL, 0, 1, 0, -1 }, + */ + { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, }; /* layout(s) */ @@ -102,6 +119,8 @@ LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right */ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; +static const int cursor_timeout = 5; + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ #define MODKEY WLR_MODIFIER_ALT @@ -123,8 +142,11 @@ static const Key keys[] = { /* modifier key function argument */ { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_b, togglebar, {0} }, { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, movestack, {.i = +1} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, movestack, {.i = -1} }, { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, @@ -166,7 +188,15 @@ static const Key keys[] = { }; static const Button buttons[] = { - { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, - { MODKEY, BTN_MIDDLE, togglefloating, {0} }, - { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, + { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, + { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, + { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, + { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + { ClkTagBar, 0, BTN_LEFT, view, {0} }, + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, }; diff --git a/config.def.h.orig b/config.def.h.orig new file mode 100644 index 0000000..4d756c6 --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,200 @@ +/* Taken from https://github.com/djpohly/dwl/issues/466 */ +#define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ + ((hex >> 16) & 0xFF) / 255.0f, \ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } +/* appearance */ +static const int sloppyfocus = 1; /* focus follows mouse */ +static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ +static int gaps = 1; /* 1 means gaps between windows are added */ +static const unsigned int gappx = 10; /* gap pixel between windows */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = {"monospace:size=10"}; +static const float rootcolor[] = COLOR(0x000000ff); +/* 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 uint32_t colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, +}; + +/* tagging */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +/* logging */ +static int log_level = WLR_ERROR; + +/* Autostart */ +static const char *const autostart[] = { + "wbg", "/path/to/your/image", NULL, + NULL /* terminate */ +}; + +static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + /* default/example rule: can be changed but cannot be eliminated; at least one rule must exist */ +}; + +/* layout(s) */ +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* monitors */ +/* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients due to + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 */ +static const MonitorRule monrules[] = { + /* name mfact nmaster scale layout rotate/reflect x y + * example of a HiDPI laptop monitor: + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, */ + { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + /* default monitor rule: can be changed but cannot be eliminated; at least one monitor rule must exist */ +}; + +/* keyboard */ +static const struct xkb_rule_names xkb_rules = { + /* can specify fields: rules, model, layout, variant, options */ + /* example: + .options = "ctrl:nocaps", + */ + .options = NULL, +}; + +static const int repeat_rate = 25; +static const int repeat_delay = 600; + +/* Trackpad */ +static const int tap_to_click = 1; +static const int tap_and_drag = 1; +static const int drag_lock = 1; +static const int natural_scrolling = 0; +static const int disable_while_typing = 1; +static const int left_handed = 0; +static const int middle_button_emulation = 0; +/* You can choose between: +LIBINPUT_CONFIG_SCROLL_NO_SCROLL +LIBINPUT_CONFIG_SCROLL_2FG +LIBINPUT_CONFIG_SCROLL_EDGE +LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN +*/ +static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + +/* You can choose between: +LIBINPUT_CONFIG_CLICK_METHOD_NONE +LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS +LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER +*/ +static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + +/* You can choose between: +LIBINPUT_CONFIG_SEND_EVENTS_ENABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +*/ +static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +/* You can choose between: +LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT +LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +*/ +static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +static const double accel_speed = 0.0; + +/* You can choose between: +LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right +*/ +static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; + +static const int cursor_timeout = 5; + +/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ +#define MODKEY WLR_MODIFIER_ALT + +#define TAGKEYS(KEY,SKEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *termcmd[] = { "foot", NULL }; +static const char *menucmd[] = { "wmenu-run", NULL }; + +static const Key keys[] = { + /* Note that Shift changes certain key codes: 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, movestack, {.i = +1} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, movestack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), + TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), + TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), + TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; + +static const Button buttons[] = { + { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, + { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, + { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, + { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, + { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + { ClkTagBar, 0, BTN_LEFT, view, {0} }, + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, +}; diff --git a/config.def.h.rej b/config.def.h.rej new file mode 100644 index 0000000..2422d5c --- /dev/null +++ b/config.def.h.rej @@ -0,0 +1,11 @@ +--- config.def.h ++++ config.def.h +@@ -129,6 +129,8 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, movestack, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, movestack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, diff --git a/drwl.h b/drwl.h new file mode 100644 index 0000000..21afd21 --- /dev/null +++ b/drwl.h @@ -0,0 +1,310 @@ +/* + * drwl - https://codeberg.org/sewn/drwl + * + * Copyright (c) 2023-2025 sewn + * Copyright (c) 2024 notchoc + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The UTF-8 Decoder included is from Bjoern Hoehrmann: + * Copyright (c) 2008-2010 Bjoern Hoehrmann + * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + */ +#pragma once + +#include +#include +#include + +enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ + +typedef struct fcft_font Fnt; +typedef pixman_image_t Img; + +typedef struct { + Img *image; + Fnt *font; + uint32_t *scheme; +} Drwl; + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 +#define UTF8_INVALID 0xFFFD + +static const uint8_t utf8d[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static inline uint32_t +utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) +{ + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +static int +drwl_init(void) +{ + return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); +} + +static Drwl * +drwl_create(void) +{ + Drwl *drwl; + + if (!(drwl = calloc(1, sizeof(Drwl)))) + return NULL; + + return drwl; +} + +static void +drwl_setfont(Drwl *drwl, Fnt *font) +{ + if (drwl) + drwl->font = font; +} + +static void +drwl_setimage(Drwl *drwl, Img *image) +{ + if (drwl) + drwl->image = image; +} + +static Fnt * +drwl_font_create(Drwl *drwl, size_t count, + const char *names[static count], const char *attributes) +{ + Fnt *font = fcft_from_name(count, names, attributes); + if (drwl) + drwl_setfont(drwl, font); + return font; +} + +static void +drwl_font_destroy(Fnt *font) +{ + fcft_destroy(font); +} + +static inline pixman_color_t +convert_color(uint32_t clr) +{ + return (pixman_color_t){ + ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + (clr & 0xFF) * 0x101 + }; +} + +static void +drwl_setscheme(Drwl *drwl, uint32_t *scm) +{ + if (drwl) + drwl->scheme = scm; +} + +static Img * +drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) +{ + Img *image; + pixman_region32_t clip; + + image = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, w, h, bits, w * 4); + if (!image) + return NULL; + pixman_region32_init_rect(&clip, 0, 0, w, h); + pixman_image_set_clip_region32(image, &clip); + pixman_region32_fini(&clip); + + if (drwl) + drwl_setimage(drwl, image); + return image; +} + +static void +drwl_rect(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + int filled, int invert) +{ + pixman_color_t clr; + if (!drwl || !drwl->scheme || !drwl->image) + return; + + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + if (filled) + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, + &(pixman_rectangle16_t){x, y, w, h}); + else + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, + (pixman_rectangle16_t[4]){ + { x, y, w, 1 }, + { x, y + h - 1, w, 1 }, + { x, y, 1, h }, + { x + w - 1, y, 1, h }}); +} + +static int +drwl_text(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + unsigned int lpad, const char *text, int invert) +{ + int ty; + int render = x || y || w || h; + long x_kern; + uint32_t cp = 0, last_cp = 0, state; + pixman_color_t clr; + pixman_image_t *fg_pix = NULL; + int noellipsis = 0; + const struct fcft_glyph *glyph, *eg = NULL; + int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; + + if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + fg_pix = pixman_image_create_solid_fill(&clr); + + drwl_rect(drwl, x, y, w, h, 1, !invert); + + x += lpad; + w -= lpad; + } + + if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) + fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; + + if (render) + eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); + + for (const char *p = text, *pp; pp = p, *p; p++) { + for (state = UTF8_ACCEPT; *p && + utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) + ; + if (!*p || state == UTF8_REJECT) { + cp = UTF8_INVALID; + if (p > pp) + p--; + } + + glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); + if (!glyph) + continue; + + x_kern = 0; + if (last_cp) + fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); + last_cp = cp; + + ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; + + if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && + *(p + 1) != '\0') { + /* cannot fit ellipsis after current codepoint */ + if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { + noellipsis = 1; + } else { + w -= eg->advance.x; + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, + x + eg->x, ty - eg->y, eg->width, eg->height); + } + } + + if ((x_kern + glyph->advance.x) > w) + break; + + x += x_kern; + + if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) + /* pre-rendered glyphs (eg. emoji) */ + pixman_image_composite32( + PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + else if (render) + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + + x += glyph->advance.x; + w -= glyph->advance.x; + } + + if (render) + pixman_image_unref(fg_pix); + + return x + (render ? w : 0); +} + +static unsigned int +drwl_font_getwidth(Drwl *drwl, const char *text) +{ + if (!drwl || !drwl->font || !text) + return 0; + return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); +} + +static void +drwl_image_destroy(Img *image) +{ + pixman_image_unref(image); +} + +static void +drwl_destroy(Drwl *drwl) +{ + if (drwl->font) + drwl_font_destroy(drwl->font); + if (drwl->image) + drwl_image_destroy(drwl->image); + free(drwl); +} + +static void +drwl_fini(void) +{ + fcft_fini(); +} diff --git a/dwl.c b/dwl.c index 101a45f..48d9f6d 100644 --- a/dwl.c +++ b/dwl.c @@ -1,10 +1,12 @@ -/* +tr* * See LICENSE file for copyright and license details. */ +#include #include #include #include #include +#include #include #include #include @@ -60,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +73,7 @@ #endif #include "util.h" +#include "drwl.h" /* macros */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) @@ -78,14 +82,17 @@ #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) -#define TAGMASK ((1u << TAGCOUNT) - 1) +#define TAGMASK ((1u << LENGTH(tags)) - 1) #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 TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) /* enums */ +enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ enum { XDGShell, LayerShell, X11 }; /* client types */ enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ typedef union { int i; @@ -95,6 +102,7 @@ typedef union { } Arg; typedef struct { + unsigned int click; unsigned int mod; unsigned int button; void (*func)(const Arg *); @@ -109,6 +117,7 @@ typedef struct { Monitor *mon; struct wlr_scene_tree *scene; struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_rect *dimmer; struct wlr_scene_tree *scene_surface; struct wl_list link; struct wl_list flink; @@ -138,7 +147,7 @@ typedef struct { #endif unsigned int bw; uint32_t tags; - int isfloating, isurgent, isfullscreen; + int isfloating, isurgent, isfullscreen, neverdim; uint32_t resize; /* configure serial of a pending resize */ } Client; @@ -184,10 +193,19 @@ typedef struct { void (*arrange)(Monitor *); } Layout; +typedef struct { + struct wlr_buffer base; + struct wl_listener release; + bool busy; + Img *image; + uint32_t data[]; +} Buffer; + struct Monitor { struct wl_list link; struct wlr_output *wlr_output; struct wlr_scene_output *scene_output; + struct wlr_scene_buffer *scene_buffer; /* bar buffer */ struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ struct wl_listener frame; struct wl_listener destroy; @@ -195,9 +213,15 @@ struct Monitor { struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; struct wlr_box m; /* monitor area, layout-relative */ + struct { + int width, height; + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface.link */ const Layout *lt[2]; + int gaps; unsigned int seltags; unsigned int sellt; uint32_t tagset[2]; @@ -206,6 +230,9 @@ struct Monitor { int nmaster; char ltsymbol[16]; int asleep; + Drwl *drw; + Buffer *pool[2]; + int lrpad; }; typedef struct { @@ -228,6 +255,7 @@ typedef struct { const char *title; uint32_t tags; int isfloating; + int neverdim; int monitor; } Rule; @@ -247,7 +275,15 @@ static void arrange(Monitor *m); static void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive); static void arrangelayers(Monitor *m); +static void autostartexec(void); static void axisnotify(struct wl_listener *listener, void *data); +static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +static void bufdestroy(struct wlr_buffer *buffer); +static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +static void bufdataend(struct wlr_buffer *buffer); +static Buffer *bufmon(Monitor *m); +static void bufrelease(struct wl_listener *listener, void *data); static void buttonpress(struct wl_listener *listener, void *data); static void chvt(const Arg *arg); static void checkidleinhibitor(struct wlr_surface *exclude); @@ -283,12 +319,16 @@ 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 drawbar(Monitor *m); +static void drawbars(void); static void focusclient(Client *c, int lift); static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); +static void handlecursoractivity(void); +static int hidecursor(void *data); static void handlesig(int signo); static void incnmaster(const Arg *arg); static void inputdevice(struct wl_listener *listener, void *data); @@ -301,6 +341,7 @@ static void locksession(struct wl_listener *listener, void *data); static void mapnotify(struct wl_listener *listener, void *data); static void maximizenotify(struct wl_listener *listener, void *data); static void monocle(Monitor *m); +static void movestack(const Arg *arg); static void motionabsolute(struct wl_listener *listener, void *data); static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, double sy, double sx_unaccel, double sy_unaccel); @@ -311,7 +352,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int static void outputmgrtest(struct wl_listener *listener, void *data); static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time); -static void printstatus(void); static void powermgrsetmode(struct wl_listener *listener, void *data); static void quit(const Arg *arg); static void rendermon(struct wl_listener *listener, void *data); @@ -332,17 +372,21 @@ static void setsel(struct wl_listener *listener, void *data); static void setup(void); static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); +static int statusin(int fd, unsigned int mask, void *data); static void tag(const Arg *arg); static void tagmon(const Arg *arg); static void tile(Monitor *m); +static void togglebar(const Arg *arg); static void togglefloating(const Arg *arg); static void togglefullscreen(const Arg *arg); +static void togglegaps(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); 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); static void updatemons(struct wl_listener *listener, void *data); +static void updatebar(Monitor *m); static void updatetitle(struct wl_listener *listener, void *data); static void urgent(struct wl_listener *listener, void *data); static void view(const Arg *arg); @@ -390,6 +434,14 @@ static struct wlr_pointer_constraint_v1 *active_constraint; static struct wlr_cursor *cursor; static struct wlr_xcursor_manager *cursor_mgr; +static struct wl_event_source *hide_source; +static bool cursor_hidden = false; +static struct { + enum wp_cursor_shape_device_v1_shape shape; + struct wlr_surface *surface; + int hotspot_x; + int hotspot_y; +} last_cursor; static struct wlr_scene_rect *root_bg; static struct wlr_session_lock_manager_v1 *session_lock_mgr; @@ -406,6 +458,16 @@ static struct wlr_output_layout *output_layout; static struct wlr_box sgeom; static struct wl_list mons; static Monitor *selmon; +static int DIMOPT = 1; + +static char stext[256]; +static struct wl_event_source *status_event_source; + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, + .end_data_ptr_access = bufdataend, +}; /* global event handlers */ static struct wl_listener cursor_axis = {.notify = axisnotify}; @@ -450,6 +512,9 @@ static struct wl_listener xwayland_ready = {.notify = xwaylandready}; static struct wlr_xwayland *xwayland; #endif +static pid_t *autostart_pids; +static size_t autostart_len; + /* configuration, allows nested code to access above variables */ #include "config.h" @@ -491,6 +556,7 @@ applyrules(Client *c) if ((!r->title || strstr(title, r->title)) && (!r->id || strstr(appid, r->id))) { c->isfloating = r->isfloating; + c->neverdim = r-> neverdim; newtags |= r->tags; i = 0; wl_list_for_each(m, &mons, link) { @@ -519,10 +585,7 @@ arrange(Monitor *m) } } - 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, sizeof(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 */ @@ -577,6 +640,11 @@ arrangelayers(Monitor *m) if (!m->wlr_output->enabled) return; + if (m->scene_buffer->node.enabled) { + usable_area.height -= m->b.real_height; + usable_area.y += topbar ? m->b.real_height : 0; + } + /* Arrange exclusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 1); @@ -604,6 +672,27 @@ arrangelayers(Monitor *m) } } +void +autostartexec(void) { + const char *const *p; + size_t i = 0; + + /* count entries */ + for (p = autostart; *p; autostart_len++, p++) + while (*++p); + + autostart_pids = calloc(autostart_len, sizeof(pid_t)); + for (p = autostart; *p; i++, p++) { + if ((autostart_pids[i] = fork()) == 0) { + setsid(); + execvp(*p, (char *const *)p); + die("dwl: execvp %s:", *p); + } + /* skip arguments */ + while (*++p); + } +} + void axisnotify(struct wl_listener *listener, void *data) { @@ -611,24 +700,111 @@ axisnotify(struct wl_listener *listener, void *data) * for example when you move the scroll wheel. */ struct wlr_pointer_axis_event *event = data; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented - * by checking the event's orientation and the delta of the event */ + * checking the event's orientation and the delta of the event */ /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis(seat, event->time_msec, event->orientation, event->delta, event->delta_discrete, event->source, event->relative_direction); } +bool +baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) +{ + return true; +} + +void +bufdestroy(struct wlr_buffer *wlr_buffer) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + if (buf->busy) + wl_list_remove(&buf->release.link); + drwl_image_destroy(buf->image); + free(buf); +} + +bool +bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; + + *data = buf->data; + *stride = wlr_buffer->width * 4; + *format = DRM_FORMAT_ARGB8888; + + return true; +} + +void +bufdataend(struct wlr_buffer *wlr_buffer) +{ +} + +Buffer * +bufmon(Monitor *m) +{ + size_t i; + Buffer *buf = NULL; + + for (i = 0; i < LENGTH(m->pool); i++) { + if (m->pool[i]) { + if (m->pool[i]->busy) + continue; + buf = m->pool[i]; + break; + } + + buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); + buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); + wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); + m->pool[i] = buf; + break; + } + if (!buf) + return NULL; + + buf->busy = true; + LISTEN(&buf->base.events.release, &buf->release, bufrelease); + wlr_buffer_lock(&buf->base); + drwl_setimage(m->drw, buf->image); + return buf; +} + +void +bufrelease(struct wl_listener *listener, void *data) +{ + Buffer *buf = wl_container_of(listener, buf, release); + buf->busy = false; + wl_list_remove(&buf->release.link); +} + void buttonpress(struct wl_listener *listener, void *data) { + unsigned int i = 0, x = 0; + double cx; + unsigned int click; struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; + struct wlr_scene_node *node; + struct wlr_scene_buffer *buffer; uint32_t mods; + Arg arg = {0}; Client *c; const Button *b; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); + + click = ClkRoot; + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c) + click = ClkClient; switch (event->state) { case WL_POINTER_BUTTON_STATE_PRESSED: @@ -637,17 +813,34 @@ buttonpress(struct wl_listener *listener, void *data) if (locked) break; + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; + else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { + click = ClkStatus; + } else + click = ClkTitle; + } + /* Change focus if the button was _pressed_ over a client */ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); - if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) + if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) focusclient(c, 1); keyboard = wlr_seat_get_keyboard(seat); mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; for (b = buttons; b < END(buttons); b++) { - if (CLEANMASK(mods) == CLEANMASK(b->mod) && - event->button == b->button && b->func) { - b->func(&b->arg); + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { + b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); return; } } @@ -700,12 +893,23 @@ checkidleinhibitor(struct wlr_surface *exclude) void cleanup(void) { + size_t i; + cleanuplisteners(); #ifdef XWAYLAND wlr_xwayland_destroy(xwayland); xwayland = NULL; #endif wl_display_destroy_clients(dpy); + + /* kill child processes */ + for (i = 0; i < autostart_len; i++) { + if (0 < autostart_pids[i]) { + kill(autostart_pids[i], SIGTERM); + waitpid(autostart_pids[i], NULL, 0); + } + } + if (child_pid > 0) { kill(-child_pid, SIGTERM); waitpid(child_pid, NULL, 0); @@ -722,6 +926,8 @@ cleanup(void) /* Destroy after the wayland display (when the monitors are already destroyed) to avoid destroying them with an invalid scene output. */ wlr_scene_node_destroy(&scene->tree.node); + + drwl_fini(); } void @@ -737,6 +943,12 @@ cleanupmon(struct wl_listener *listener, void *data) wlr_layer_surface_v1_destroy(l->layer_surface); } + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + wl_list_remove(&m->destroy.link); wl_list_remove(&m->frame.link); wl_list_remove(&m->link); @@ -749,6 +961,7 @@ cleanupmon(struct wl_listener *listener, void *data) closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); + wlr_scene_node_destroy(&m->scene_buffer->node); free(m); } @@ -815,7 +1028,7 @@ closemon(Monitor *m) setmon(c, selmon, c->tags); } focusclient(focustop(selmon), 1); - printstatus(); + drawbars(); } void @@ -879,7 +1092,7 @@ commitnotify(struct wl_listener *listener, void *data) return; } - resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + resize(c, c->geom, (c->isfloating)); /* mark a pending resize as completed */ if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) @@ -1058,6 +1271,8 @@ createmon(struct wl_listener *listener, void *data) wlr_output_state_init(&state); /* Initialize monitor state using configured rules */ + m->gaps = gaps; + m->tagset[0] = m->tagset[1] = 1; for (r = monrules; r < END(monrules); r++) { if (!r->name || strstr(wlr_output->name, r->name)) { @@ -1067,7 +1282,7 @@ createmon(struct wl_listener *listener, void *data) m->nmaster = r->nmaster; m->lt[0] = r->lt; m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; - strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); wlr_output_state_set_scale(&state, r->scale); wlr_output_state_set_transform(&state, r->rr); break; @@ -1089,8 +1304,15 @@ createmon(struct wl_listener *listener, void *data) wlr_output_commit_state(wlr_output, &state); wlr_output_state_finish(&state); + if (!(m->drw = drwl_create())) + die("failed to create drwl context"); + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; + updatebar(m); + wl_list_insert(&mons, &m->link); - printstatus(); + drawbars(); /* The xdg-protocol specifies: * @@ -1400,6 +1622,80 @@ dirtomon(enum wlr_direction dir) return selmon; } +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; + Client *c; + Buffer *buf; + + if (!m->scene_buffer->node.enabled) + return; + if (!(buf = bufmon(m))) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ + drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); + } + } + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); +} + +void +drawbars(void) +{ + Monitor *m = NULL; + + wl_list_for_each(m, &mons, link) + drawbar(m); +} + void focusclient(Client *c, int lift) { @@ -1433,14 +1729,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) - client_set_border_color(c, focuscolor); + if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + client_set_dimmer_state(c, 0); + } } /* Deactivate old client if focus is changing */ if (old && (!c || client_surface(c) != old)) { /* 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 + * but only update its position in fstack to render its border with its color * and focus it after the overlay is closed. */ if (old_client_type == LayerShell && wlr_scene_node_coords( &old_l->scene->node, &unused_lx, &unused_ly) @@ -1451,12 +1749,12 @@ focusclient(Client *c, int lift) /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg * and probably other clients */ } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { - client_set_border_color(old_c, bordercolor); - + client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); client_activate_surface(old, 0); + client_set_dimmer_state(old_c, 1); } } - printstatus(); + drawbars(); if (!c) { /* With no client, all we have left is to clear focus */ @@ -1491,7 +1789,7 @@ focusstack(const Arg *arg) { /* Focus the next or previous client (in tiling order) on selmon */ Client *c, *sel = focustop(selmon); - if (!sel || (sel->isfullscreen && !client_has_children(sel))) + if (!sel) return; if (arg->i > 0) { wl_list_for_each(c, &sel->link, link) { @@ -1561,10 +1859,51 @@ gpureset(struct wl_listener *listener, void *data) void handlesig(int signo) { - if (signo == SIGCHLD) - while (waitpid(-1, NULL, WNOHANG) > 0); - else if (signo == SIGINT || signo == SIGTERM) + if (signo == SIGCHLD) { + pid_t pid, *p, *lim; + while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { + if (pid == child_pid) + child_pid = -1; + if (!(p = autostart_pids)) + continue; + lim = &p[autostart_len]; + + for (; p < lim; p++) { + if (*p == pid) { + *p = -1; + break; + } + } + } + } else if (signo == SIGINT || signo == SIGTERM) { quit(NULL); + } +} + +void +handlecursoractivity(void) +{ + wl_event_source_timer_update(hide_source, cursor_timeout * 1000); + + if (!cursor_hidden) + return; + + cursor_hidden = false; + + if (last_cursor.shape) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(last_cursor.shape)); + else + wlr_cursor_set_surface(cursor, last_cursor.surface, + last_cursor.hotspot_x, last_cursor.hotspot_y); +} + +int +hidecursor(void *data) +{ + wlr_cursor_unset_image(cursor); + cursor_hidden = true; + return 1; } void @@ -1739,8 +2078,7 @@ void mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ - Client *p = NULL; - Client *w, *c = wl_container_of(listener, c, map); + Client *p, *w, *d, *c = wl_container_of(listener, c, map); Monitor *m; int i; @@ -1770,10 +2108,14 @@ mapnotify(struct wl_listener *listener, void *data) for (i = 0; i < 4; i++) { c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, - c->isurgent ? urgentcolor : bordercolor); + (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); c->border[i]->node.data = c; } + c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); + c->dimmer->node.data = c; + client_set_dimmer_state(c, 1); + /* 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; @@ -1792,8 +2134,12 @@ mapnotify(struct wl_listener *listener, void *data) setmon(c, p->mon, p->tags); } else { applyrules(c); + d = focustop(selmon); + if (d) { + client_set_dimmer_state(d, 0); + } } - printstatus(); + drawbars(); unset_fullscreen: m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); @@ -1828,7 +2174,7 @@ monocle(Monitor *m) int n = 0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + if (!VISIBLEON(c, m) || c->isfloating) continue; resize(c, m->w, 0); n++; @@ -1839,6 +2185,48 @@ monocle(Monitor *m) wlr_scene_node_raise_to_top(&c->scene->node); } +void +movestack(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + + if (!sel) { + return; + } + + if (wl_list_length(&clients) <= 1) { + return; + } + + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { + if (&c->link == &clients) { + c = wl_container_of(&clients, c, link); + break; /* wrap past the sentinel node */ + } + if (VISIBLEON(c, selmon) || &c->link == &clients) { + break; /* found it */ + } + } + } else { + wl_list_for_each_reverse(c, &sel->link, link) { + if (&c->link == &clients) { + c = wl_container_of(&clients, c, link); + break; /* wrap past the sentinel node */ + } + if (VISIBLEON(c, selmon) || &c->link == &clients) { + break; /* found it */ + } + } + /* backup one client */ + c = wl_container_of(c->link.prev, c, link); + } + + wl_list_remove(&sel->link); + wl_list_insert(&c->link, &sel->link); + arrange(selmon); +} + void motionabsolute(struct wl_listener *listener, void *data) { @@ -1908,6 +2296,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d wlr_cursor_move(cursor, device, dx, dy); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); /* Update selmon (even while dragging a window) */ if (sloppyfocus) @@ -1932,7 +2321,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d /* If there's no client surface under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it * off of a client or over its border. */ - if (!surface && !seat->drag) + if (!surface && !seat->drag && !cursor_hidden) wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); pointerfocus(c, surface, sx, sy, time); @@ -1959,7 +2348,7 @@ moveresize(const Arg *arg) if (cursor_mode != CurNormal && cursor_mode != CurPressed) return; xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); - if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + if (!grabc || client_is_unmanaged(grabc)) return; /* Float the window and tell motionnotify to grab it */ @@ -2086,44 +2475,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, wlr_seat_pointer_notify_motion(seat, time, sx, sy); } -void -printstatus(void) -{ - Monitor *m = NULL; - Client *c; - uint32_t occ, urg, sel; - - wl_list_for_each(m, &mons, link) { - occ = urg = 0; - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - occ |= c->tags; - if (c->isurgent) - urg |= c->tags; - } - if ((c = focustop(m))) { - printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); - printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); - printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); - printf("%s floating %d\n", m->wlr_output->name, c->isfloating); - sel = c->tags; - } else { - printf("%s title \n", m->wlr_output->name); - printf("%s appid \n", m->wlr_output->name); - printf("%s fullscreen \n", m->wlr_output->name); - printf("%s floating \n", m->wlr_output->name); - sel = 0; - } - - printf("%s selmon %u\n", m->wlr_output->name, m == selmon); - printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", - m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); - printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); - } - fflush(stdout); -} - void powermgrsetmode(struct wl_listener *listener, void *data) { @@ -2218,7 +2569,7 @@ resize(Client *c, struct wlr_box geo, int interact) c->geom = geo; applybounds(c, bbox); - /* Update scene-graph, including borders */ + /* Update scene-graph, including borders and dimmer*/ 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); @@ -2228,6 +2579,8 @@ resize(Client *c, struct wlr_box geo, int interact) 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); + wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); + wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); /* this is a no-op if size hasn't changed */ c->resize = client_set_size(c, c->geom.width - 2 * c->bw, @@ -2251,23 +2604,16 @@ run(char *startup_cmd) die("startup: backend_start"); /* Now that the socket exists and the backend is started, run the startup command */ + autostartexec(); if (startup_cmd) { - int piperw[2]; - if (pipe(piperw) < 0) - die("startup: pipe:"); if ((child_pid = fork()) < 0) die("startup: fork:"); if (child_pid == 0) { + close(STDIN_FILENO); setsid(); - dup2(piperw[0], STDIN_FILENO); - close(piperw[0]); - close(piperw[1]); execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); die("startup: execl:"); } - dup2(piperw[1], STDOUT_FILENO); - close(piperw[1]); - close(piperw[0]); } /* Mark stdout as non-blocking to avoid the startup script @@ -2277,7 +2623,7 @@ run(char *startup_cmd) if (fd_set_nonblock(STDOUT_FILENO) < 0) close(STDOUT_FILENO); - printstatus(); + drawbars(); /* At this point the outputs are initialized, choose initial selmon based on * cursor position, and set default cursor image */ @@ -2289,6 +2635,7 @@ run(char *startup_cmd) * monitor when displayed here */ wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + handlecursoractivity(); /* Run the Wayland event loop. This does not return until you exit the * compositor. Starting the backend rigged up all of the necessary event @@ -2312,9 +2659,16 @@ setcursor(struct wl_listener *listener, void *data) * use the provided surface as the cursor image. It will set the * hardware cursor on the output that it's currently on and continue to * do so as the cursor moves between outputs. */ - if (event->seat_client == seat->pointer_state.focused_client) - wlr_cursor_set_surface(cursor, event->surface, - event->hotspot_x, event->hotspot_y); + if (event->seat_client == seat->pointer_state.focused_client) { + last_cursor.shape = 0; + last_cursor.surface = event->surface; + last_cursor.hotspot_x = event->hotspot_x; + last_cursor.hotspot_y = event->hotspot_y; + + if (!cursor_hidden) + wlr_cursor_set_surface(cursor, event->surface, + event->hotspot_x, event->hotspot_y); + } } void @@ -2326,24 +2680,27 @@ setcursorshape(struct wl_listener *listener, void *data) /* This can be sent by any client, so we check to make sure this one * actually has pointer focus first. If so, we can tell the cursor to * use the provided cursor shape. */ - if (event->seat_client == seat->pointer_state.focused_client) - wlr_cursor_set_xcursor(cursor, cursor_mgr, - wlr_cursor_shape_v1_name(event->shape)); + if (event->seat_client == seat->pointer_state.focused_client) { + last_cursor.shape = event->shape; + last_cursor.surface = NULL; + + if (!cursor_hidden) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(event->shape)); + } } 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]); + wlr_scene_node_reparent(&c->scene->node, + layers[c->isfloating ? LyrFloat : LyrTile]); arrange(c->mon); - printstatus(); + drawbars(); } void @@ -2354,19 +2711,19 @@ 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]); + wlr_scene_node_reparent(&c->scene->node, + layers[c->isfloating ? LyrFloat : LyrTile]); if (fullscreen) { c->prev = c->geom; - resize(c, c->mon->m, 0); + resize(c, c->mon->w, 0); } else { /* restore previous size instead of arrange for floating windows since * client positions are set by the user and cannot be recalculated */ resize(c, c->prev, 0); } arrange(c->mon); - printstatus(); + drawbars(); } void @@ -2378,9 +2735,9 @@ setlayout(const Arg *arg) selmon->sellt ^= 1; if (arg && arg->v) selmon->lt[selmon->sellt] = (Layout *)arg->v; - strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof(selmon->ltsymbol)); arrange(selmon); - printstatus(); + drawbar(selmon); } /* arg > 1.0 will set mfact absolutely */ @@ -2453,6 +2810,7 @@ setup(void) for (i = 0; i < (int)LENGTH(sig); i++) sigaction(sig[i], &sa, NULL); + wlr_log_init(log_level, NULL); /* The Wayland display is managed by libwayland. It handles accepting @@ -2620,6 +2978,9 @@ setup(void) cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hidecursor, cursor); + /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -2648,6 +3009,11 @@ setup(void) wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); + drwl_init(); + + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + /* Make sure XWayland clients don't connect to the parent X server, * e.g when running in the x11 backend or the wayland backend and the * compositor has Xwayland support */ @@ -2672,6 +3038,8 @@ void spawn(const Arg *arg) { if (fork() == 0) { + close(STDIN_FILENO); + open("/dev/null", O_RDWR); dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); execvp(((char **)arg->v)[0], (char **)arg->v); @@ -2690,6 +3058,30 @@ startdrag(struct wl_listener *listener, void *data) LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); } +int +statusin(int fd, unsigned int mask, void *data) +{ + char status[256]; + ssize_t n; + + if (mask & WL_EVENT_ERROR) + die("status in event error"); + if (mask & WL_EVENT_HANGUP) + wl_event_source_remove(status_event_source); + + n = read(fd, status, sizeof(status) - 1); + if (n < 0 && errno != EWOULDBLOCK) + die("read:"); + + status[n] = '\0'; + status[strcspn(status, "\n")] = '\0'; + + strncpy(stext, status, sizeof(stext)); + drawbars(); + + return 0; +} + void tag(const Arg *arg) { @@ -2700,7 +3092,7 @@ tag(const Arg *arg) sel->tags = arg->ui & TAGMASK; focusclient(focustop(selmon), 1); arrange(selmon); - printstatus(); + drawbars(); } void @@ -2714,43 +3106,80 @@ tagmon(const Arg *arg) void tile(Monitor *m) { - unsigned int mw, my, ty; + unsigned int h, r, e = m->gaps, mw, my, ty; int i, n = 0; Client *c; wl_list_for_each(c, &clients, link) - if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + if (VISIBLEON(c, m) && !c->isfloating) n++; if (n == 0) return; + if (smartgaps == n) + e = 0; if (n > m->nmaster) - mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; else mw = m->w.width; - i = my = ty = 0; + i = 0; + my = ty = gappx*e; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + if (!VISIBLEON(c, m) || c->isfloating) continue; if (i < m->nmaster) { - resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, - .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); - my += c->geom.height; + r = MIN(n, m->nmaster) - i; + h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, + .width = mw - 2*gappx*e, .height = h}, 0); + my += c->geom.height + gappx*e; } else { + r = n - i; + h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, - .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); - ty += c->geom.height; + .width = m->w.width - mw - gappx*e, .height = h}, 0); + ty += c->geom.height + gappx*e; } i++; } } +void +togglebar(const Arg *arg) +{ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); + drawbars(); +} + +void toggledimming(const Arg *arg) +{ + Client *c; + DIMOPT ^= 1; + wl_list_for_each(c, &clients, link) + { + client_set_dimmer_state(c, 1); + } + c = focustop(selmon); + if (c) + client_set_dimmer_state(c, 0); +} + +void +toggledimmingclient(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + sel -> neverdim ^= 1; +} + void togglefloating(const Arg *arg) { Client *sel = focustop(selmon); /* return if fullscreen */ - if (sel && !sel->isfullscreen) + if (sel) setfloating(sel, !sel->isfloating); } @@ -2762,6 +3191,13 @@ togglefullscreen(const Arg *arg) setfullscreen(sel, !sel->isfullscreen); } +void +togglegaps(const Arg *arg) +{ + selmon->gaps = !selmon->gaps; + arrange(selmon); +} + void toggletag(const Arg *arg) { @@ -2773,7 +3209,7 @@ toggletag(const Arg *arg) sel->tags = newtags; focusclient(focustop(selmon), 1); arrange(selmon); - printstatus(); + drawbars(); } void @@ -2786,7 +3222,7 @@ toggleview(const Arg *arg) selmon->tagset[selmon->seltags] = newtagset; focusclient(focustop(selmon), 1); arrange(selmon); - printstatus(); + drawbars(); } void @@ -2834,7 +3270,7 @@ unmapnotify(struct wl_listener *listener, void *data) } wlr_scene_node_destroy(&c->scene->node); - printstatus(); + drawbars(); motionnotify(0, NULL, 0, 0, 0, 0); } @@ -2905,9 +3341,6 @@ updatemons(struct wl_listener *listener, void *data) arrangelayers(m); /* Don't move clients to the left output when plugging monitors */ arrange(m); - /* make sure fullscreen clients have the right size */ - if ((c = focustop(m)) && c->isfullscreen) - resize(c, m->m, 0); /* Try to re-set the gamma LUT when updating monitors, * it's only really needed when enabling a disabled output, but meh. */ @@ -2934,6 +3367,13 @@ updatemons(struct wl_listener *listener, void *data) } } + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { + updatebar(m); + drawbar(m); + } + /* FIXME: figure out why the cursor image is at 0,0 after turning all * the monitors on. * Move the cursor image where it used to be. It does not generate a @@ -2944,12 +3384,45 @@ updatemons(struct wl_listener *listener, void *data) wlr_output_manager_v1_set_configuration(output_mgr, config); } +void +updatebar(Monitor *m) +{ + size_t i; + int rw, rh; + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; + m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + + for (i = 0; i < LENGTH(m->pool); i++) + if (m->pool[i]) { + wlr_buffer_drop(&m->pool[i]->base); + m->pool[i] = NULL; + } + + if (m->b.scale == m->wlr_output->scale && m->drw) + return; + + drwl_font_destroy(m->drw->font); + snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); + if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) + die("Could not load font"); + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); +} + void updatetitle(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, set_title); if (c == focustop(c->mon)) - printstatus(); + drawbars(); } void @@ -2962,10 +3435,10 @@ urgent(struct wl_listener *listener, void *data) return; c->isurgent = 1; - printstatus(); + drawbars(); if (client_surface(c)->mapped) - client_set_border_color(c, urgentcolor); + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); } void @@ -2978,7 +3451,7 @@ view(const Arg *arg) selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; focusclient(focustop(selmon), 1); arrange(selmon); - printstatus(); + drawbars(); } void @@ -3004,6 +3477,7 @@ virtualpointer(struct wl_listener *listener, void *data) wlr_cursor_attach_input_device(cursor, device); if (event->suggested_output) wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); + handlecursoractivity(); } Monitor * @@ -3019,6 +3493,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, { struct wlr_scene_node *node, *pnode; struct wlr_surface *surface = NULL; + struct wlr_scene_surface *scene_surface = NULL; Client *c = NULL; LayerSurface *l = NULL; int layer; @@ -3027,9 +3502,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) continue; - if (node->type == WLR_SCENE_NODE_BUFFER) - surface = wlr_scene_surface_try_from_buffer( - wlr_scene_buffer_from_node(node))->surface; + if (node->type == WLR_SCENE_NODE_BUFFER) { + scene_surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (!scene_surface) continue; + surface = scene_surface->surface; + } /* Walk the tree to find a node that knows the client */ for (pnode = node; pnode && !c; pnode = &pnode->parent->node) c = pnode->data; @@ -3162,10 +3640,10 @@ sethints(struct wl_listener *listener, void *data) return; c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); - printstatus(); + drawbars(); if (c->isurgent && surface && surface->mapped) - client_set_border_color(c, urgentcolor); + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); } void diff --git a/dwl.c.orig b/dwl.c.orig new file mode 100644 index 0000000..f07329f --- /dev/null +++ b/dwl.c.orig @@ -0,0 +1,3658 @@ +tr* + * See LICENSE file for copyright and license details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XWAYLAND +#include +#include +#include +#endif + +#include "util.h" +#include "drwl.h" + +/* macros */ +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) +#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define END(A) ((A) + LENGTH(A)) +#define TAGMASK ((1u << LENGTH(tags)) - 1) +#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 TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ +enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct { + /* Must keep this field first */ + unsigned int type; /* XDGShell or X11* */ + + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; + struct wlr_box geom; /* layout-relative, includes border */ + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; /* only width and height are used */ + union { + struct wlr_xdg_surface *xdg; + struct wlr_xwayland_surface *xwayland; + } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wl_listener commit; + struct wl_listener map; + struct wl_listener maximize; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener set_title; + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; +#ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +#endif + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +} Client; + +typedef struct { + uint32_t mod; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + struct wlr_keyboard_group *wlr_group; + + int nsyms; + const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ + uint32_t mods; /* invalid if nsyms == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +} KeyboardGroup; + +typedef struct { + /* Must keep this field first */ + unsigned int type; /* LayerShell */ + + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct wl_list link; + int mapped; + struct wlr_layer_surface_v1 *layer_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener surface_commit; +} LayerSurface; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +typedef struct { + struct wlr_buffer base; + struct wl_listener release; + bool busy; + Img *image; + uint32_t data[]; +} Buffer; + +struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ + struct { + int width, height; + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; + int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; + int asleep; + Drwl *drw; + Buffer *pool[2]; + int lrpad; +}; + +typedef struct { + const char *name; + float mfact; + int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; + int x, y; +} MonitorRule; + +typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +} PointerConstraint; + +typedef struct { + const char *id; + const char *title; + uint32_t tags; + int isfloating; + int monitor; +} Rule; + +typedef struct { + struct wlr_scene_tree *scene; + + struct wlr_session_lock_v1 *lock; + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +} SessionLock; + +/* function declarations */ +static void applybounds(Client *c, struct wlr_box *bbox); +static void applyrules(Client *c); +static void arrange(Monitor *m); +static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +static void arrangelayers(Monitor *m); +static void autostartexec(void); +static void axisnotify(struct wl_listener *listener, void *data); +static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +static void bufdestroy(struct wlr_buffer *buffer); +static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +static void bufdataend(struct wlr_buffer *buffer); +static Buffer *bufmon(Monitor *m); +static void bufrelease(struct wl_listener *listener, void *data); +static void buttonpress(struct wl_listener *listener, void *data); +static void chvt(const Arg *arg); +static void checkidleinhibitor(struct wlr_surface *exclude); +static void cleanup(void); +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 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); +static void createidleinhibitor(struct wl_listener *listener, void *data); +static void createkeyboard(struct wlr_keyboard *keyboard); +static KeyboardGroup *createkeyboardgroup(void); +static void createlayersurface(struct wl_listener *listener, void *data); +static void createlocksurface(struct wl_listener *listener, void *data); +static void createmon(struct wl_listener *listener, void *data); +static void createnotify(struct wl_listener *listener, void *data); +static void createpointer(struct wlr_pointer *pointer); +static void createpointerconstraint(struct wl_listener *listener, void *data); +static void createpopup(struct wl_listener *listener, void *data); +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); +static void cursorframe(struct wl_listener *listener, void *data); +static void cursorwarptohint(void); +static void destroydecoration(struct wl_listener *listener, void *data); +static void destroydragicon(struct wl_listener *listener, void *data); +static void destroyidleinhibitor(struct wl_listener *listener, void *data); +static void destroylayersurfacenotify(struct wl_listener *listener, void *data); +static void destroylock(SessionLock *lock, int unlocked); +static void destroylocksurface(struct wl_listener *listener, void *data); +static void destroynotify(struct wl_listener *listener, void *data); +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 drawbar(Monitor *m); +static void drawbars(void); +static void focusclient(Client *c, int lift); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Client *focustop(Monitor *m); +static void fullscreennotify(struct wl_listener *listener, void *data); +static void gpureset(struct wl_listener *listener, void *data); +static void handlecursoractivity(void); +static int hidecursor(void *data); +static void handlesig(int signo); +static void incnmaster(const Arg *arg); +static void inputdevice(struct wl_listener *listener, void *data); +static int keybinding(uint32_t mods, xkb_keysym_t sym); +static void keypress(struct wl_listener *listener, void *data); +static void keypressmod(struct wl_listener *listener, void *data); +static int keyrepeat(void *data); +static void killclient(const Arg *arg); +static void locksession(struct wl_listener *listener, void *data); +static void mapnotify(struct wl_listener *listener, void *data); +static void maximizenotify(struct wl_listener *listener, void *data); +static void monocle(Monitor *m); +static void movestack(const Arg *arg); +static void motionabsolute(struct wl_listener *listener, void *data); +static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +static void motionrelative(struct wl_listener *listener, void *data); +static void moveresize(const Arg *arg); +static void outputmgrapply(struct wl_listener *listener, void *data); +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); +static void outputmgrtest(struct wl_listener *listener, void *data); +static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +static void powermgrsetmode(struct wl_listener *listener, void *data); +static void quit(const Arg *arg); +static void rendermon(struct wl_listener *listener, void *data); +static void requestdecorationmode(struct wl_listener *listener, void *data); +static void requeststartdrag(struct wl_listener *listener, void *data); +static void requestmonstate(struct wl_listener *listener, void *data); +static void resize(Client *c, struct wlr_box geo, int interact); +static void run(char *startup_cmd); +static void setcursor(struct wl_listener *listener, void *data); +static void setcursorshape(struct wl_listener *listener, void *data); +static void setfloating(Client *c, int floating); +static void setfullscreen(Client *c, int fullscreen); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +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 void spawn(const Arg *arg); +static void startdrag(struct wl_listener *listener, void *data); +static int statusin(int fd, unsigned int mask, void *data); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *m); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void togglefullscreen(const Arg *arg); +static void togglegaps(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +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); +static void updatemons(struct wl_listener *listener, void *data); +static void updatebar(Monitor *m); +static void updatetitle(struct wl_listener *listener, void *data); +static void urgent(struct wl_listener *listener, void *data); +static void view(const Arg *arg); +static void virtualkeyboard(struct wl_listener *listener, void *data); +static void virtualpointer(struct wl_listener *listener, void *data); +static Monitor *xytomon(double x, double y); +static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +static void zoom(const Arg *arg); + +/* variables */ +static pid_t child_pid = -1; +static int locked; +static void *exclusive_focus; +static struct wl_display *dpy; +static struct wl_event_loop *event_loop; +static struct wlr_backend *backend; +static struct wlr_scene *scene; +static struct wlr_scene_tree *layers[NUM_LAYERS]; +static struct wlr_scene_tree *drag_icon; +/* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ +static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; +static struct wlr_renderer *drw; +static struct wlr_allocator *alloc; +static struct wlr_compositor *compositor; +static struct wlr_session *session; + +static struct wlr_xdg_shell *xdg_shell; +static struct wlr_xdg_activation_v1 *activation; +static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; +static struct wl_list clients; /* tiling order */ +static struct wl_list fstack; /* focus order */ +static struct wlr_idle_notifier_v1 *idle_notifier; +static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; +static struct wlr_layer_shell_v1 *layer_shell; +static struct wlr_output_manager_v1 *output_mgr; +static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; +static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; +static struct wlr_output_power_manager_v1 *power_mgr; + +static struct wlr_pointer_constraints_v1 *pointer_constraints; +static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +static struct wlr_pointer_constraint_v1 *active_constraint; + +static struct wlr_cursor *cursor; +static struct wlr_xcursor_manager *cursor_mgr; +static struct wl_event_source *hide_source; +static bool cursor_hidden = false; +static struct { + enum wp_cursor_shape_device_v1_shape shape; + struct wlr_surface *surface; + int hotspot_x; + int hotspot_y; +} last_cursor; + +static struct wlr_scene_rect *root_bg; +static struct wlr_session_lock_manager_v1 *session_lock_mgr; +static struct wlr_scene_rect *locked_bg; +static struct wlr_session_lock_v1 *cur_lock; + +static struct wlr_seat *seat; +static KeyboardGroup *kb_group; +static unsigned int cursor_mode; +static Client *grabc; +static int grabcx, grabcy; /* client-relative */ + +static struct wlr_output_layout *output_layout; +static struct wlr_box sgeom; +static struct wl_list mons; +static Monitor *selmon; + +static char stext[256]; +static struct wl_event_source *status_event_source; + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, + .end_data_ptr_access = bufdataend, +}; + +/* global event handlers */ +static struct wl_listener cursor_axis = {.notify = axisnotify}; +static struct wl_listener cursor_button = {.notify = buttonpress}; +static struct wl_listener cursor_frame = {.notify = cursorframe}; +static struct wl_listener cursor_motion = {.notify = motionrelative}; +static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute}; +static struct wl_listener gpu_reset = {.notify = gpureset}; +static struct wl_listener layout_change = {.notify = updatemons}; +static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor}; +static struct wl_listener new_input_device = {.notify = inputdevice}; +static struct wl_listener new_virtual_keyboard = {.notify = virtualkeyboard}; +static struct wl_listener new_virtual_pointer = {.notify = virtualpointer}; +static struct wl_listener new_pointer_constraint = {.notify = createpointerconstraint}; +static struct wl_listener new_output = {.notify = createmon}; +static struct wl_listener new_xdg_toplevel = {.notify = createnotify}; +static struct wl_listener new_xdg_popup = {.notify = createpopup}; +static struct wl_listener new_xdg_decoration = {.notify = createdecoration}; +static struct wl_listener new_layer_surface = {.notify = createlayersurface}; +static struct wl_listener output_mgr_apply = {.notify = outputmgrapply}; +static struct wl_listener output_mgr_test = {.notify = outputmgrtest}; +static struct wl_listener output_power_mgr_set_mode = {.notify = powermgrsetmode}; +static struct wl_listener request_activate = {.notify = urgent}; +static struct wl_listener request_cursor = {.notify = setcursor}; +static struct wl_listener request_set_psel = {.notify = setpsel}; +static struct wl_listener request_set_sel = {.notify = setsel}; +static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; +static struct wl_listener request_start_drag = {.notify = requeststartdrag}; +static struct wl_listener start_drag = {.notify = startdrag}; +static struct wl_listener new_session_lock = {.notify = locksession}; + +#ifdef XWAYLAND +static void activatex11(struct wl_listener *listener, void *data); +static void associatex11(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); +static void sethints(struct wl_listener *listener, void *data); +static void xwaylandready(struct wl_listener *listener, void *data); +static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; +static struct wl_listener xwayland_ready = {.notify = xwaylandready}; +static struct wlr_xwayland *xwayland; +#endif + +static pid_t *autostart_pids; +static size_t autostart_len; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* attempt to encapsulate suck into one file */ +#include "client.h" + +/* function implementations */ +void +applybounds(Client *c, struct wlr_box *bbox) +{ + /* set minimum possible */ + c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); + c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); + + if (c->geom.x >= bbox->x + bbox->width) + c->geom.x = bbox->x + bbox->width - c->geom.width; + if (c->geom.y >= bbox->y + bbox->height) + c->geom.y = bbox->y + bbox->height - c->geom.height; + if (c->geom.x + c->geom.width <= bbox->x) + c->geom.x = bbox->x; + if (c->geom.y + c->geom.height <= bbox->y) + c->geom.y = bbox->y; +} + +void +applyrules(Client *c) +{ + /* rule matching */ + const char *appid, *title; + uint32_t newtags = 0; + int i; + const Rule *r; + Monitor *mon = selmon, *m; + + appid = client_get_appid(c); + title = client_get_title(c); + + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; + } + } + } + + c->isfloating |= client_is_float_type(c); + setmon(c, mon, newtags); +} + +void +arrange(Monitor *m) +{ + Client *c; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + client_set_suspended(c, !VISIBLEON(c, m)); + } + } + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(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); +} + +void +arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) +{ + LayerSurface *l; + struct wlr_box full_area = m->m; + + wl_list_for_each(l, list, link) { + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + + if (!layer_surface->initialized) + continue; + + if (exclusive != (layer_surface->current.exclusive_zone > 0)) + continue; + + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, l->scene->node.y); + } +} + +void +arrangelayers(Monitor *m) +{ + int i; + struct wlr_box usable_area = m->m; + LayerSurface *l; + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + if (!m->wlr_output->enabled) + return; + + if (m->scene_buffer->node.enabled) { + usable_area.height -= m->b.real_height; + usable_area.y += topbar ? m->b.real_height : 0; + } + + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); + + if (!wlr_box_equal(&usable_area, &m->w)) { + m->w = usable_area; + arrange(m); + } + + /* Arrange non-exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 0); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { + wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + if (locked || !l->layer_surface->current.keyboard_interactive || !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)); + return; + } + } +} + +void +autostartexec(void) { + const char *const *p; + size_t i = 0; + + /* count entries */ + for (p = autostart; *p; autostart_len++, p++) + while (*++p); + + autostart_pids = calloc(autostart_len, sizeof(pid_t)); + for (p = autostart; *p; i++, p++) { + if ((autostart_pids[i] = fork()) == 0) { + setsid(); + execvp(*p, (char *const *)p); + die("dwl: execvp %s:", *p); + } + /* skip arguments */ + while (*++p); + } +} + +void +axisnotify(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an axis event, + * for example when you move the scroll wheel. */ + struct wlr_pointer_axis_event *event = data; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); + /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented + * checking the event's orientation and the delta of the event */ + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis(seat, + event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source, event->relative_direction); +} + +bool +baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) +{ + return true; +} + +void +bufdestroy(struct wlr_buffer *wlr_buffer) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + if (buf->busy) + wl_list_remove(&buf->release.link); + drwl_image_destroy(buf->image); + free(buf); +} + +bool +bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; + + *data = buf->data; + *stride = wlr_buffer->width * 4; + *format = DRM_FORMAT_ARGB8888; + + return true; +} + +void +bufdataend(struct wlr_buffer *wlr_buffer) +{ +} + +Buffer * +bufmon(Monitor *m) +{ + size_t i; + Buffer *buf = NULL; + + for (i = 0; i < LENGTH(m->pool); i++) { + if (m->pool[i]) { + if (m->pool[i]->busy) + continue; + buf = m->pool[i]; + break; + } + + buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); + buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); + wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); + m->pool[i] = buf; + break; + } + if (!buf) + return NULL; + + buf->busy = true; + LISTEN(&buf->base.events.release, &buf->release, bufrelease); + wlr_buffer_lock(&buf->base); + drwl_setimage(m->drw, buf->image); + return buf; +} + +void +bufrelease(struct wl_listener *listener, void *data) +{ + Buffer *buf = wl_container_of(listener, buf, release); + buf->busy = false; + wl_list_remove(&buf->release.link); +} + +void +buttonpress(struct wl_listener *listener, void *data) +{ + unsigned int i = 0, x = 0; + double cx; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + struct wlr_scene_node *node; + struct wlr_scene_buffer *buffer; + uint32_t mods; + Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); + + click = ClkRoot; + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c) + click = ClkClient; + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); + if (locked) + break; + + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; + else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { + click = ClkStatus; + } else + click = ClkTitle; + } + + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { + b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } + break; + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO: should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); + grabc = NULL; + return; + } + cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); +} + +void +chvt(const Arg *arg) +{ + wlr_session_change_vt(session, arg->ui); +} + +void +checkidleinhibitor(struct wlr_surface *exclude) +{ + int inhibited = 0, unused_lx, unused_ly; + struct wlr_idle_inhibitor_v1 *inhibitor; + wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { + struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); + struct wlr_scene_tree *tree = surface->data; + if (exclude != surface && (bypass_surface_visibility || (!tree + || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { + inhibited = 1; + break; + } + } + + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); +} + +void +cleanup(void) +{ + size_t i; + + cleanuplisteners(); +#ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +#endif + wl_display_destroy_clients(dpy); + + /* kill child processes */ + for (i = 0; i < autostart_len; i++) { + if (0 < autostart_pids[i]) { + kill(autostart_pids[i], SIGTERM); + waitpid(autostart_pids[i], NULL, 0); + } + } + + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + } + wlr_xcursor_manager_destroy(cursor_mgr); + + destroykeyboardgroup(&kb_group->destroy, NULL); + + /* If it's not destroyed manually, it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed on the wlroots side */ + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); + + drwl_fini(); +} + +void +cleanupmon(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy); + LayerSurface *l, *tmp; + size_t i; + + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); + if (m->lock_surface) + destroylocksurface(&m->destroy_lock_surface, NULL); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); +} + +void +cleanuplisteners(void) +{ + wl_list_remove(&cursor_axis.link); + wl_list_remove(&cursor_button.link); + wl_list_remove(&cursor_frame.link); + wl_list_remove(&cursor_motion.link); + wl_list_remove(&cursor_motion_absolute.link); + wl_list_remove(&gpu_reset.link); + wl_list_remove(&new_idle_inhibitor.link); + wl_list_remove(&layout_change.link); + wl_list_remove(&new_input_device.link); + wl_list_remove(&new_virtual_keyboard.link); + wl_list_remove(&new_virtual_pointer.link); + wl_list_remove(&new_pointer_constraint.link); + wl_list_remove(&new_output.link); + wl_list_remove(&new_xdg_toplevel.link); + wl_list_remove(&new_xdg_decoration.link); + wl_list_remove(&new_xdg_popup.link); + wl_list_remove(&new_layer_surface.link); + wl_list_remove(&output_mgr_apply.link); + wl_list_remove(&output_mgr_test.link); + wl_list_remove(&output_power_mgr_set_mode.link); + wl_list_remove(&request_activate.link); + wl_list_remove(&request_cursor.link); + wl_list_remove(&request_set_psel.link); + wl_list_remove(&request_set_sel.link); + wl_list_remove(&request_set_cursor_shape.link); + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); + wl_list_remove(&new_session_lock.link); +#ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); + wl_list_remove(&xwayland_ready.link); +#endif +} + +void +closemon(Monitor *m) +{ + /* update selmon if needed and + * move closed monitor's clients to the focused one */ + Client *c; + int i = 0, nmons = wl_list_length(&mons); + if (!nmons) { + selmon = NULL; + } else if (m == selmon) { + do /* don't switch to disabled mons */ + selmon = wl_container_of(mons.next, selmon, link); + while (!selmon->wlr_output->enabled && i++ < nmons); + + if (!selmon->wlr_output->enabled) + selmon = NULL; + } + + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, + .width = c->geom.width, .height = c->geom.height}, 0); + if (c->mon == m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + drawbars(); +} + +void +commitlayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->current.layer]]; + struct wlr_layer_surface_v1_state old_state; + + if (l->layer_surface->initial_commit) { + client_set_scale(layer_surface->surface, l->mon->wlr_output->scale); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it */ + old_state = l->layer_surface->current; + l->layer_surface->current = l->layer_surface->pending; + arrangelayers(l->mon); + l->layer_surface->current = old_state; + return; + } + + if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) + return; + l->mapped = layer_surface->surface->mapped; + + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); + wlr_scene_node_reparent(&l->popups->node, (layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer)); + } + + arrangelayers(l->mon); +} + +void +commitnotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, commit); + + if (c->surface.xdg->initial_commit) { + /* + * Get the monitor this client will be rendered on + * Note that if the user set a rule in which the client is placed on + * a different monitor based on its title, this will likely select + * a wrong monitor. + */ + applyrules(c); + if (c->mon) { + client_set_scale(client_surface(c), c->mon->wlr_output->scale); + } + setmon(c, NULL, 0); /* Make sure to reapply rules in mapnotify() */ + + wlr_xdg_toplevel_set_wm_capabilities(c->surface.xdg->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + if (c->decoration) + requestdecorationmode(&c->set_decoration_mode, c->decoration); + wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0); + return; + } + + resize(c, c->geom, (c->isfloating)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +} + +void +commitpopup(struct wl_listener *listener, void *data) +{ + struct wlr_surface *surface = data; + struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); + LayerSurface *l = NULL; + Client *c = NULL; + struct wlr_box box; + int type = -1; + + if (!popup->base->initial_commit) + return; + + type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); + if (!popup->parent || type < 0) + return; + popup->base->surface->data = wlr_scene_xdg_surface_create( + popup->parent->data, popup->base); + if ((l && !l->mon) || (c && !c->mon)) { + wlr_xdg_popup_destroy(popup); + 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); + wlr_xdg_popup_unconstrain_from_box(popup, &box); + wl_list_remove(&listener->link); + free(listener); +} + +void +createdecoration(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + Client *c = deco->toplevel->base->data; + c->decoration = deco; + + LISTEN(&deco->events.request_mode, &c->set_decoration_mode, requestdecorationmode); + LISTEN(&deco->events.destroy, &c->destroy_decoration, destroydecoration); + + requestdecorationmode(&c->set_decoration_mode, deco); +} + +void +createidleinhibitor(struct wl_listener *listener, void *data) +{ + struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; + LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); + + checkidleinhibitor(NULL); +} + +void +createkeyboard(struct wlr_keyboard *keyboard) +{ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); +} + +KeyboardGroup * +createkeyboardgroup(void) +{ + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + + /* Prepare an XKB keymap and assign it to the keyboard group. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + XKB_KEYMAP_COMPILE_NO_FLAGS))) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + + /* Set up listeners for keyboard events */ + LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); + LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, keypressmod); + + group->key_repeat_source = wl_event_loop_add_timer(event_loop, keyrepeat, group); + + /* A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same wlr_keyboard_group, which provides a single wlr_keyboard interface for + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + return group; +} + +void +createlayersurface(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *layer_surface = data; + LayerSurface *l; + struct wlr_surface *surface = layer_surface->surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->pending.layer]]; + + if (!layer_surface->output + && !(layer_surface->output = selmon ? selmon->wlr_output : NULL)) { + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + l = layer_surface->data = ecalloc(1, sizeof(*l)); + l->type = LayerShell; + LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); + LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); + LISTEN(&layer_surface->events.destroy, &l->destroy, destroylayersurfacenotify); + + l->layer_surface = layer_surface; + l->mon = layer_surface->output->data; + l->scene_layer = wlr_scene_layer_surface_v1_create(scene_layer, layer_surface); + l->scene = l->scene_layer->tree; + l->popups = surface->data = wlr_scene_tree_create(layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer); + l->scene->node.data = l->popups->node.data = l; + + wl_list_insert(&l->mon->layers[layer_surface->pending.layer],&l->link); + wlr_surface_send_enter(surface, layer_surface->output); +} + +void +createlocksurface(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, new_surface); + struct wlr_session_lock_surface_v1 *lock_surface = data; + Monitor *m = lock_surface->output->data; + struct wlr_scene_tree *scene_tree = lock_surface->surface->data + = wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); + m->lock_surface = lock_surface; + + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height); + + LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface); + + if (m == selmon) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); +} + +void +createmon(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; + const MonitorRule *r; + size_t i; + struct wlr_output_state state; + Monitor *m; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->gaps = gaps; + + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; + } + } + + /* The mode is a tuple of (width, height, refresh rate), and each + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); + LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); + + wlr_output_state_set_enabled(&state, 1); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + if (!(m->drw = drwl_create())) + die("failed to create drwl context"); + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; + updatebar(m); + + wl_list_insert(&mons, &m->link); + drawbars(); + + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + * + */ + /* updatemons() will resize and set correct position */ + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); +} + +void +createnotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&toplevel->events.destroy, &c->destroy, destroynotify); + LISTEN(&toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&toplevel->events.set_title, &c->set_title, updatetitle); +} + +void +createpointer(struct wlr_pointer *pointer) +{ + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); + } + + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); + + if (libinput_device_config_left_handed_is_available(device)) + libinput_device_config_left_handed_set(device, left_handed); + + if (libinput_device_config_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled(device, middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method(device, scroll_method); + + if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method(device, click_method); + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); + } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +} + +void +createpointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); + pointer_constraint->constraint = data; + LISTEN(&pointer_constraint->constraint->events.destroy, + &pointer_constraint->destroy, destroypointerconstraint); +} + +void +createpopup(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client (either xdg-shell or layer-shell) + * creates a new popup. */ + struct wlr_xdg_popup *popup = data; + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); +} + +void +cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) +{ + if (active_constraint == constraint) + return; + + if (active_constraint) + wlr_pointer_constraint_v1_send_deactivated(active_constraint); + + active_constraint = constraint; + wlr_pointer_constraint_v1_send_activated(constraint); +} + +void +cursorframe(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(seat); +} + +void +cursorwarptohint(void) +{ + Client *c = NULL; + double sx = active_constraint->current.cursor_hint.x; + double sy = active_constraint->current.cursor_hint.y; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->current.cursor_hint.enabled) { + wlr_cursor_warp(cursor, NULL, sx + c->geom.x + c->bw, sy + c->geom.y + c->bw); + wlr_seat_pointer_warp(active_constraint->seat, sx, sy); + } +} + +void +destroydecoration(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, destroy_decoration); + + wl_list_remove(&c->destroy_decoration.link); + wl_list_remove(&c->set_decoration_mode.link); +} + +void +destroydragicon(struct wl_listener *listener, void *data) +{ + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + wl_list_remove(&listener->link); + free(listener); +} + +void +destroyidleinhibitor(struct wl_listener *listener, void *data) +{ + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); + wl_list_remove(&listener->link); + free(listener); +} + +void +destroylayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); + wl_list_remove(&l->destroy.link); + wl_list_remove(&l->unmap.link); + wl_list_remove(&l->surface_commit.link); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +} + +void +destroylock(SessionLock *lock, int unlock) +{ + wlr_seat_keyboard_notify_clear_focus(seat); + if ((locked = !unlock)) + goto destroy; + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + focusclient(focustop(selmon), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + +destroy: + wl_list_remove(&lock->new_surface.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->destroy.link); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; + free(lock); +} + +void +destroylocksurface(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy_lock_surface); + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; + wl_list_remove(&m->destroy_lock_surface.link); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; + + if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { + 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); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +} + +void +destroynotify(struct wl_listener *listener, void *data) +{ + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); +#ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +#endif + { + wl_list_remove(&c->commit.link); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + wl_list_remove(&c->maximize.link); + } + free(c); +} + +void +destroypointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); + + if (active_constraint == pointer_constraint->constraint) { + cursorwarptohint(); + active_constraint = NULL; + } + + wl_list_remove(&pointer_constraint->destroy.link); + free(pointer_constraint); +} + +void +destroysessionlock(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, destroy); + destroylock(lock, 0); +} + +void +destroykeyboardgroup(struct wl_listener *listener, void *data) +{ + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); + wlr_keyboard_group_destroy(group->wlr_group); + free(group); +} + +Monitor * +dirtomon(enum wlr_direction dir) +{ + struct wlr_output *next; + if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) + return selmon; + if ((next = wlr_output_layout_adjacent_output(output_layout, + dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + if ((next = wlr_output_layout_farthest_output(output_layout, + dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), + selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + return selmon; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; + Client *c; + Buffer *buf; + + if (!m->scene_buffer->node.enabled) + return; + if (!(buf = bufmon(m))) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ + drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); + } + } + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); +} + +void +drawbars(void) +{ + Monitor *m = NULL; + + wl_list_for_each(m, &mons, link) + drawbar(m); +} + +void +focusclient(Client *c, int lift) +{ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; + + if (locked) + return; + + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); + + if (c && client_surface(c) == old) + return; + + if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) + wlr_xdg_popup_destroy(popup); + } + + /* Put the new client atop the focus stack and select its monitor */ + if (c && !client_is_unmanaged(c)) { + wl_list_remove(&c->flink); + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) + client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, + * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (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) { + return; + } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { + return; + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } + drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); + return; + } + + /* Change cursor surface */ + motionnotify(0, NULL, 0, 0, 0, 0); + + /* 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); +} + +void +focusmon(const Arg *arg) +{ + int i = 0, nmons = wl_list_length(&mons); + if (nmons) { + do /* don't switch to disabled mons */ + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } + focusclient(focustop(selmon), 1); +} + +void +focusstack(const Arg *arg) +{ + /* Focus the next or previous client (in tiling order) on selmon */ + Client *c, *sel = focustop(selmon); + if (!sel) + return; + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } else { + wl_list_for_each_reverse(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } + /* If only one client is visible on selmon, then c == sel */ + focusclient(c, 1); +} + +/* We probably should change the name of this: it sounds like it + * will focus the topmost client of this mon, when actually will + * only return that client */ +Client * +focustop(Monitor *m) +{ + Client *c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m)) + return c; + } + return NULL; +} + +void +fullscreennotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, fullscreen); + setfullscreen(c, client_wants_fullscreen(c)); +} + +void +gpureset(struct wl_listener *listener, void *data) +{ + struct wlr_renderer *old_drw = drw; + struct wlr_allocator *old_alloc = alloc; + struct Monitor *m; + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't recreate renderer"); + + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + + wl_list_remove(&gpu_reset.link); + wl_signal_add(&drw->events.lost, &gpu_reset); + + wlr_compositor_set_renderer(compositor, drw); + + wl_list_for_each(m, &mons, link) { + wlr_output_init_render(m->wlr_output, alloc, drw); + } + + wlr_allocator_destroy(old_alloc); + wlr_renderer_destroy(old_drw); +} + +void +handlesig(int signo) +{ + if (signo == SIGCHLD) { + pid_t pid, *p, *lim; + while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { + if (pid == child_pid) + child_pid = -1; + if (!(p = autostart_pids)) + continue; + lim = &p[autostart_len]; + + for (; p < lim; p++) { + if (*p == pid) { + *p = -1; + break; + } + } + } + } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); + } +} + +void +handlecursoractivity(void) +{ + wl_event_source_timer_update(hide_source, cursor_timeout * 1000); + + if (!cursor_hidden) + return; + + cursor_hidden = false; + + if (last_cursor.shape) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(last_cursor.shape)); + else + wlr_cursor_set_surface(cursor, last_cursor.surface, + last_cursor.hotspot_x, last_cursor.hotspot_y); +} + +int +hidecursor(void *data) +{ + wlr_cursor_unset_image(cursor); + cursor_hidden = true; + return 1; +} + +void +incnmaster(const Arg *arg) +{ + if (!arg || !selmon) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +void +inputdevice(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + createkeyboard(wlr_keyboard_from_input_device(device)); + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; + default: + /* TODO handle other input device types */ + break; + } + + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In dwl we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(seat, caps); +} + +int +keybinding(uint32_t mods, xkb_keysym_t sym) +{ + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + */ + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && xkb_keysym_to_lower(sym) == xkb_keysym_to_lower(k->keysym) + && k->func) { + k->func(&k->arg); + return 1; + } + } + return 0; +} + +void +keypress(struct wl_listener *listener, void *data) +{ + int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; + group->nsyms = nsyms; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { + group->nsyms = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + + if (handled) + return; + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); +} + +void +keypressmod(struct wl_listener *listener, void *data) +{ + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + KeyboardGroup *group = wl_container_of(listener, group, modifiers); + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); +} + +int +keyrepeat(void *data) +{ + KeyboardGroup *group = data; + int i; + if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) + keybinding(group->mods, group->keysyms[i]); + + return 0; +} + +void +killclient(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + client_send_close(sel); +} + +void +locksession(struct wl_listener *listener, void *data) +{ + struct wlr_session_lock_v1 *session_lock = data; + SessionLock *lock; + wlr_scene_node_set_enabled(&locked_bg->node, 1); + if (cur_lock) { + wlr_session_lock_v1_destroy(session_lock); + return; + } + lock = session_lock->data = ecalloc(1, sizeof(*lock)); + focusclient(NULL, 0); + + lock->scene = wlr_scene_tree_create(layers[LyrBlock]); + cur_lock = lock->lock = session_lock; + locked = 1; + + 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); + + wlr_session_lock_v1_send_locked(session_lock); +} + +void +mapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + /* Enabled later by a call to arrange() */ + wlr_scene_node_set_enabled(&c->scene->node, client_is_unmanaged(c)); + c->scene_surface = c->type == XDGShell + ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) + : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); + c->scene->node.data = c->scene_surface->node.data = c; + + client_get_geometry(c, &c->geom); + + /* Handle unmanaged clients first so we can return prior create borders */ + if (client_is_unmanaged(c)) { + /* Unmanaged clients always are floating */ + wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + client_set_size(c, c->geom.width, c->geom.height); + if (client_wants_focus(c)) { + focusclient(c, 1); + exclusive_focus = c; + } + goto unset_fullscreen; + } + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, + (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + + /* 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; + + /* Insert this client into client lists. */ + wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: + * we always consider floating, clients that have parent and thus + * we set the same tags and monitor as its parent. + * If there is no parent, apply rules */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { + applyrules(c); + } + drawbars(); + +unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } +} + +void +maximizenotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. dwl doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, maximize); + if (c->surface.xdg->initialized + && wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); +} + +void +monocle(Monitor *m) +{ + Client *c; + int n = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating) + continue; + resize(c, m->w, 0); + n++; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); + if ((c = focustop(m))) + wlr_scene_node_raise_to_top(&c->scene->node); +} + +void +movestack(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + + if (!sel) { + return; + } + + if (wl_list_length(&clients) <= 1) { + return; + } + + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { + if (&c->link == &clients) { + c = wl_container_of(&clients, c, link); + break; /* wrap past the sentinel node */ + } + if (VISIBLEON(c, selmon) || &c->link == &clients) { + break; /* found it */ + } + } + } else { + wl_list_for_each_reverse(c, &sel->link, link) { + if (&c->link == &clients) { + c = wl_container_of(&clients, c, link); + break; /* wrap past the sentinel node */ + } + if (VISIBLEON(c, selmon) || &c->link == &clients) { + break; /* found it */ + } + } + /* backup one client */ + c = wl_container_of(c->link.prev, c, link); + } + + wl_list_remove(&sel->link); + wl_list_insert(&c->link, &sel->link); + arrange(selmon); +} + +void +motionabsolute(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. Also, some hardware emits these events. */ + struct wlr_pointer_motion_absolute_event *event = data; + double lx, ly, dx, dy; + + if (!event->time_msec) /* this is 0 with virtual pointers */ + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); + + wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); +} + +void +motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) +{ + double sx = 0, sy = 0, sx_confined, sy_confined; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; + struct wlr_pointer_constraint_v1 *constraint; + + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag + && surface != seat->pointer_state.focused_surface + && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->scene->node.x : w->geom.x); + sy = cursor->y - (l ? l->scene->node.y : w->geom.y); + } + + /* time is 0 in internal calls meant to restore pointer focus. */ + if (time) { + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + + wl_list_for_each(constraint, &pointer_constraints->constraints, link) + cursorconstrain(constraint); + + if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->surface == seat->pointer_state.focused_surface) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + } + } + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } + + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; + } + + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ + if (!surface && !seat->drag && !cursor_hidden) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +} + +void +motionrelative(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct wlr_pointer_motion_event *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy); +} + +void +moveresize(const Arg *arg) +{ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); + if (!grabc || client_is_unmanaged(grabc)) + return; + + /* Float the window and tell motionnotify to grab it */ + setfloating(grabc, 1); + switch (cursor_mode = arg->ui) { + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); + break; + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ + wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; + } +} + +void +outputmgrapply(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 0); +} + +void +outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) +{ + /* + * Called when a client such as wlr-randr requests a change in output + * configuration. This is only one way that the layout can be changed, + * so any Monitor information should be updated by updatemons() after an + * output_layout.change event, not here. + */ + struct wlr_output_configuration_head_v1 *config_head; + int ok = 1; + + wl_list_for_each(config_head, &config->heads, link) { + struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; + struct wlr_output_state state; + + /* Ensure displays previously disabled by wlr-output-power-management-v1 + * are properly handled*/ + m->asleep = 0; + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, config_head->state.enabled); + if (!config_head->state.enabled) + goto apply_or_test; + + if (config_head->state.mode) + wlr_output_state_set_mode(&state, config_head->state.mode); + else + wlr_output_state_set_custom_mode(&state, + config_head->state.custom_mode.width, + config_head->state.custom_mode.height, + config_head->state.custom_mode.refresh); + + wlr_output_state_set_transform(&state, config_head->state.transform); + wlr_output_state_set_scale(&state, config_head->state.scale); + wlr_output_state_set_adaptive_sync_enabled(&state, + config_head->state.adaptive_sync_enabled); + +apply_or_test: + ok &= test ? wlr_output_test_state(wlr_output, &state) + : wlr_output_commit_state(wlr_output, &state); + + /* Don't move monitors if position wouldn't change. This avoids + * wlroots marking the output as manually configured. + * wlr_output_layout_add does not like disabled outputs */ + if (!test && wlr_output->enabled && (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) + wlr_output_layout_add(output_layout, wlr_output, + config_head->state.x, config_head->state.y); + + wlr_output_state_finish(&state); + } + + if (ok) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); + + /* https://codeberg.org/dwl/dwl/issues/577 */ + updatemons(NULL, NULL); +} + +void +outputmgrtest(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 1); +} + +void +pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +{ + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && + sloppyfocus && time && c && !client_is_unmanaged(c)) + focusclient(c, 0); + + /* If surface is NULL, clear pointer focus */ + if (!surface) { + wlr_seat_pointer_notify_clear_focus(seat); + return; + } + + if (!time) { + clock_gettime(CLOCK_MONOTONIC, &now); + time = now.tv_sec * 1000 + now.tv_nsec / 1000000; + } + + /* Let the client know that the mouse cursor has entered one + * of its surfaces, and make keyboard focus follow if desired. + * wlroots makes this a no-op if surface is already focused */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); +} + +void +powermgrsetmode(struct wl_listener *listener, void *data) +{ + struct wlr_output_power_v1_set_mode_event *event = data; + struct wlr_output_state state = {0}; + Monitor *m = event->output->data; + + if (!m) + return; + + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the output */ + wlr_output_state_set_enabled(&state, event->mode); + wlr_output_commit_state(m->wlr_output, &state); + + m->asleep = !event->mode; + updatemons(NULL, NULL); +} + +void +quit(const Arg *arg) +{ + wl_display_terminate(dpy); +} + +void +rendermon(struct wl_listener *listener, void *data) +{ + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); + Client *c; + struct wlr_output_state pending = {0}; + struct timespec now; + + /* Render if no XDG clients have an outstanding resize and are visible on + * this monitor. */ + wl_list_for_each(c, &clients, link) { + if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) + goto skip; + } + + wlr_scene_output_commit(m->scene_output, NULL); + +skip: + /* Let clients know a frame has been rendered */ + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); +} + +void +requestdecorationmode(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_decoration_mode); + if (c->surface.xdg->initialized) + wlr_xdg_toplevel_decoration_v1_set_mode(c->decoration, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); +} + +void +requeststartdrag(struct wl_listener *listener, void *data) +{ + struct wlr_seat_request_start_drag_event *event = data; + + if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, + event->serial)) + wlr_seat_start_pointer_drag(seat, event->drag, event->serial); + else + wlr_data_source_destroy(event->drag->source); +} + +void +requestmonstate(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(event->output, event->state); + updatemons(NULL, NULL); +} + +void +resize(Client *c, struct wlr_box geo, int interact) +{ + struct wlr_box *bbox; + struct wlr_box clip; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); + 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); + + /* this is a no-op if size hasn't changed */ + c->resize = 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); +} + +void +run(char *startup_cmd) +{ + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ + autostartexec(); + if (startup_cmd) { + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { + close(STDIN_FILENO); + setsid(); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } + } + + /* Mark stdout as non-blocking to avoid the startup script + * causing dwl to freeze when a user neither closes stdin + * nor consumes standard input in his startup script */ + + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + + drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ + selmon = xytomon(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. Still may not be fully + * initialized, as the image/coordinates are not transformed for the + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + handlecursoractivity(); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + wl_display_run(dpy); +} + +void +setcursor(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + /* If we're "grabbing" the cursor, don't use the client's image, we will + * restore it after "grabbing" sending a leave event, followed by a enter + * event, which will result in the client requesting set the cursor surface */ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided surface as the cursor image. It will set the + * hardware cursor on the output that it's currently on and continue to + * do so as the cursor moves between outputs. */ + if (event->seat_client == seat->pointer_state.focused_client) { + last_cursor.shape = 0; + last_cursor.surface = event->surface; + last_cursor.hotspot_x = event->hotspot_x; + last_cursor.hotspot_y = event->hotspot_y; + + if (!cursor_hidden) + wlr_cursor_set_surface(cursor, event->surface, + event->hotspot_x, event->hotspot_y); + } +} + +void +setcursorshape(struct wl_listener *listener, void *data) +{ + struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided cursor shape. */ + if (event->seat_client == seat->pointer_state.focused_client) { + last_cursor.shape = event->shape; + last_cursor.surface = NULL; + + if (!cursor_hidden) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(event->shape)); + } +} + +void +setfloating(Client *c, int floating) +{ + 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->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + drawbars(); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; + c->bw = fullscreen ? 0 : borderpx; + client_set_fullscreen(c, fullscreen); + + wlr_scene_node_reparent(&c->scene->node, + layers[c->isfloating ? LyrFloat : LyrTile]); + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->w, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } + arrange(c->mon); + drawbars(); +} + +void +setlayout(const Arg *arg) +{ + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof(selmon->ltsymbol)); + arrange(selmon); + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setmon(Client *c, Monitor *m, uint32_t newtags) +{ + Monitor *oldmon = c->mon; + + if (oldmon == m) + return; + c->mon = m; + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ + if (oldmon) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); +} + +void +setpsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor them + */ + struct wlr_seat_request_set_primary_selection_event *event = data; + wlr_seat_set_primary_selection(seat, event->source, event->serial); +} + +void +setsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor them + */ + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(seat, event->source, event->serial); +} + +void +setup(void) +{ + int drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + + + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, managing Wayland globals, and so on. */ + dpy = wl_display_create(); + event_loop = wl_display_get_event_loop(dpy); + + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + if (!(backend = wlr_backend_autocreate(event_loop, &session))) + die("couldn't create backend"); + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); + wl_signal_add(&drw->events.lost, &gpu_reset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is to call: + * wlr_renderer_init_wl_display(drw); + * but we need to create the linux_dmabuf interface manually to integrate it + * with wlr_scene. */ + wlr_renderer_init_wl_shm(drw, dpy); + + if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { + wlr_drm_create(dpy, drw); + wlr_scene_set_linux_dmabuf_v1(scene, + wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + } + + if ((drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && drw->features.timeline + && backend->features.timeline) + wlr_linux_drm_syncobj_manager_v1_create(dpy, 1, drm_fd); + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't create allocator"); + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the setsel() function. */ + compositor = wlr_compositor_create(dpy, 6, drw); + wlr_subcompositor_create(dpy); + wlr_data_device_manager_create(dpy); + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); + wlr_ext_data_control_manager_v1_create(dpy, 1); + wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); + wlr_presentation_create(dpy, backend, 2); + wlr_alpha_modifier_v1_create(dpy); + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); + wl_signal_add(&activation->events.request_activate, &request_activate); + + wlr_scene_set_gamma_control_manager_v1(scene, wlr_gamma_control_manager_v1_create(dpy)); + + power_mgr = wlr_output_power_manager_v1_create(dpy); + wl_signal_add(&power_mgr->events.set_mode, &output_power_mgr_set_mode); + + /* Creates an output layout, which is a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); + wl_signal_add(&output_layout->events.change, &layout_change); + + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); + wl_signal_add(&backend->events.new_output, &new_output); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more + * detail on shells, refer to the article: + * + * https://drewdevault.com/2018/07/29/Wayland-shells.html + */ + wl_list_init(&clients); + wl_list_init(&fstack); + + 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); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); + wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); + wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); + wl_signal_add(&session_lock_mgr->events.new_lock, &new_session_lock); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); + wl_signal_add(&xdg_decoration_mgr->events.new_toplevel_decoration, &new_xdg_decoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); + wl_signal_add(&pointer_constraints->events.new_constraint, &new_pointer_constraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(cursor, output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ + cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + setenv("XCURSOR_SIZE", "24", 1); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * + * And more comments are sprinkled throughout the notify functions above. + */ + wl_signal_add(&cursor->events.motion, &cursor_motion); + wl_signal_add(&cursor->events.motion_absolute, &cursor_motion_absolute); + wl_signal_add(&cursor->events.button, &cursor_button); + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + + hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hidecursor, cursor); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + wl_signal_add(&backend->events.new_input, &new_input_device); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, + &new_virtual_keyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + wl_signal_add(&virtual_pointer_mgr->events.new_virtual_pointer, + &new_virtual_pointer); + + seat = wlr_seat_create(dpy, "seat0"); + wl_signal_add(&seat->events.request_set_cursor, &request_cursor); + wl_signal_add(&seat->events.request_set_selection, &request_set_sel); + wl_signal_add(&seat->events.request_set_primary_selection, &request_set_psel); + wl_signal_add(&seat->events.request_start_drag, &request_start_drag); + wl_signal_add(&seat->events.start_drag, &start_drag); + + kb_group = createkeyboardgroup(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + + drwl_init(); + + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ + unsetenv("DISPLAY"); +#ifdef XWAYLAND + /* + * Initialise the XWayland X server. + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { + wl_signal_add(&xwayland->events.ready, &xwayland_ready); + wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } +#endif +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + close(STDIN_FILENO); + open("/dev/null", O_RDWR); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwl: execvp %s failed:", ((char **)arg->v)[0]); + } +} + +void +startdrag(struct wl_listener *listener, void *data) +{ + struct wlr_drag *drag = data; + if (!drag->icon) + return; + + drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); +} + +int +statusin(int fd, unsigned int mask, void *data) +{ + char status[256]; + ssize_t n; + + if (mask & WL_EVENT_ERROR) + die("status in event error"); + if (mask & WL_EVENT_HANGUP) + wl_event_source_remove(status_event_source); + + n = read(fd, status, sizeof(status) - 1); + if (n < 0 && errno != EWOULDBLOCK) + die("read:"); + + status[n] = '\0'; + status[strcspn(status, "\n")] = '\0'; + + strncpy(stext, status, sizeof(stext)); + drawbars(); + + return 0; +} + +void +tag(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +tagmon(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setmon(sel, dirtomon(arg->i), 0); +} + +void +tile(Monitor *m) +{ + unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + + wl_list_for_each(c, &clients, link) + if (VISIBLEON(c, m) && !c->isfloating) + n++; + if (n == 0) + return; + if (smartgaps == n) + e = 0; + + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; + i = 0; + my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating) + continue; + if (i < m->nmaster) { + r = MIN(n, m->nmaster) - i; + h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, + .width = mw - 2*gappx*e, .height = h}, 0); + my += c->geom.height + gappx*e; + } else { + r = n - i; + h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, + .width = m->w.width - mw - gappx*e, .height = h}, 0); + ty += c->geom.height + gappx*e; + } + i++; + } +} + +void +togglebar(const Arg *arg) +{ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); + drawbars(); +} + +void +togglefloating(const Arg *arg) +{ + Client *sel = focustop(selmon); + /* return if fullscreen */ + if (sel) + setfloating(sel, !sel->isfloating); +} + +void +togglefullscreen(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setfullscreen(sel, !sel->isfullscreen); +} + +void +togglegaps(const Arg *arg) +{ + selmon->gaps = !selmon->gaps; + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +toggleview(const Arg *arg) +{ + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +unlocksession(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, unlock); + destroylock(lock, 1); +} + +void +unmaplayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, unmap); + + 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); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +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); + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; + } + + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; + focusclient(focustop(selmon), 1); + } + } else { + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } + + wlr_scene_node_destroy(&c->scene->node); + drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +updatemons(struct wl_listener *listener, void *data) +{ + /* + * Called whenever the output layout changes: adding or removing a + * monitor, changing an output's mode or position, etc. This is where + * the change officially happens and we update geometry, window + * positions, focus, and the stored configuration in wlroots' + * output-manager implementation. + */ + struct wlr_output_configuration_v1 *config + = wlr_output_configuration_v1_create(); + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head->state.enabled = 0; + /* Remove this output from the layout to avoid cursor enter inside it */ + wlr_output_layout_remove(output_layout, m->wlr_output); + closemon(m); + m->m = m->w = (struct wlr_box){0}; + } + /* Insert outputs that need to */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled + && !wlr_output_layout_get(output_layout, m->wlr_output)) + wlr_output_layout_add_auto(output_layout, m->wlr_output); + } + + /* Now that we update the output layout we can get its box */ + wlr_output_layout_get_box(output_layout, NULL, &sgeom); + + wlr_scene_node_set_position(&root_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(root_bg, sgeom.width, sgeom.height); + + /* Make sure the clients are hidden when dwl is locked */ + wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); + + wl_list_for_each(m, &mons, link) { + if (!m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; + wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ + m->gamma_lut_changed = 1; + + config_head->state.x = m->m.x; + config_head->state.y = m->m.y; + + if (!selmon) { + selmon = m; + } + } + + if (selmon && selmon->wlr_output->enabled) { + wl_list_for_each(c, &clients, link) { + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); + client_activate_surface(selmon->lock_surface->surface, 1); + } + } + + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { + updatebar(m); + drawbar(m); + } + + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a + * wl_pointer.motion event for the clients, it's only the image what it's + * at the wrong position after all. */ + wlr_cursor_move(cursor, NULL, 0, 0); + + wlr_output_manager_v1_set_configuration(output_mgr, config); +} + +void +updatebar(Monitor *m) +{ + size_t i; + int rw, rh; + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; + m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + + for (i = 0; i < LENGTH(m->pool); i++) + if (m->pool[i]) { + wlr_buffer_drop(&m->pool[i]->base); + m->pool[i] = NULL; + } + + if (m->b.scale == m->wlr_output->scale && m->drw) + return; + + drwl_font_destroy(m->drw->font); + snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); + if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) + die("Could not load font"); + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); +} + +void +updatetitle(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + drawbars(); +} + +void +urgent(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); + if (!c || c == focustop(selmon)) + return; + + c->isurgent = 1; + drawbars(); + + if (client_surface(c)->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +view(const Arg *arg) +{ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +virtualkeyboard(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ + KeyboardGroup *group = createkeyboardgroup(); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); + LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +} + +void +virtualpointer(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_pointer_v1_new_pointer_event *event = data; + struct wlr_input_device *device = &event->new_pointer->pointer.base; + + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); + handlecursoractivity(); +} + +Monitor * +xytomon(double x, double y) +{ + struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); + return o ? o->data : NULL; +} + +void +xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +{ + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; + struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; + + for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + scene_surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (!scene_surface) continue; + surface = scene_surface->surface; + } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; + if (c && c->type == LayerShell) { + c = NULL; + l = pnode->data; + } + } + + if (psurface) *psurface = surface; + if (pc) *pc = c; + if (pl) *pl = l; +} + +void +zoom(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; + + /* Search for the first tiled window that is not sel, marking sel as + * NULL if we pass it along the way */ + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && !c->isfloating) { + if (c != sel) + break; + sel = NULL; + } + } + + /* Return if no other tiled window was found */ + if (&c->link == &clients) + return; + + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) + sel = c; + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + + focusclient(sel, 1); + arrange(selmon); +} + +#ifdef XWAYLAND +void +activatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, activate); + + /* Only "managed" windows can be activated */ + if (!client_is_unmanaged(c)) + wlr_xwayland_surface_activate(c->surface.xwayland, 1); +} + +void +associatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, associate); + + LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); + LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); +} + +void +configurex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, configure); + struct wlr_xwayland_surface_configure_event *event = data; + if (!client_surface(c) || !client_surface(c)->mapped) { + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if (client_is_unmanaged(c)) { + wlr_scene_node_set_position(&c->scene->node, event->x, event->y); + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if ((c->isfloating && c != grabc) || !c->mon->lt[c->mon->sellt]->arrange) { + resize(c, (struct wlr_box){.x = event->x - c->bw, + .y = event->y - c->bw, .width = event->width + c->bw * 2, + .height = event->height + c->bw * 2}, 0); + } else { + arrange(c->mon); + } +} + +void +createnotifyx11(struct wl_listener *listener, void *data) +{ + struct wlr_xwayland_surface *xsurface = data; + Client *c; + + /* Allocate a Client for this surface */ + c = xsurface->data = ecalloc(1, sizeof(*c)); + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); + LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); +} + +void +dissociatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); +} + +void +sethints(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); + if (c == focustop(selmon) || !c->surface.xwayland->hints) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + drawbars(); + + if (c->isurgent && surface && surface->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +xwaylandready(struct wl_listener *listener, void *data) +{ + struct wlr_xcursor *xcursor; + + /* assign the one and only seat */ + wlr_xwayland_set_seat(xwayland, seat); + + /* Set the default XWayland cursor to match the rest of dwl. */ + if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 1))) + wlr_xwayland_set_cursor(xwayland, + xcursor->images[0]->buffer, xcursor->images[0]->width * 4, + xcursor->images[0]->width, xcursor->images[0]->height, + xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); +} +#endif + +int +main(int argc, char *argv[]) +{ + char *startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') + log_level = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else + goto usage; + } + if (optind < argc) + goto usage; + + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); + setup(); + run(startup_cmd); + cleanup(); + return EXIT_SUCCESS; + +usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); +} diff --git a/dwl.c.rej b/dwl.c.rej new file mode 100644 index 0000000..60eb71b --- /dev/null +++ b/dwl.c.rej @@ -0,0 +1,32 @@ +--- dwl.c ++++ dwl.c +@@ -340,6 +342,8 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); ++static void toggledimmingclient(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -1369,8 +1375,10 @@ 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 (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1388,7 +1396,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } diff --git a/patches/autostart.patch b/patches/autostart.patch new file mode 100644 index 0000000..e2f03e6 --- /dev/null +++ b/patches/autostart.patch @@ -0,0 +1,141 @@ +From e8932f159793012a54047c48b3710703a63c07fb Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Fri, 27 Feb 2026 12:29:02 -0600 +Subject: [PATCH] Applied autostart patch + +--- + config.def.h | 6 ++++++ + dwl.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 61 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..6eb0db4 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,12 @@ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Autostart */ ++static const char *const autostart[] = { ++ "wbg", "/path/to/your/image", NULL, ++ NULL /* terminate */ ++}; ++ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +diff --git a/dwl.c b/dwl.c +index 8a9715d..3450817 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -248,6 +248,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void autostartexec(void); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -451,6 +452,9 @@ static struct wl_listener xwayland_ready = {.notify = xwaylandready}; + static struct wlr_xwayland *xwayland; + #endif + ++static pid_t *autostart_pids; ++static size_t autostart_len; ++ + /* configuration, allows nested code to access above variables */ + #include "config.h" + +@@ -605,6 +609,27 @@ arrangelayers(Monitor *m) + } + } + ++void ++autostartexec(void) { ++ const char *const *p; ++ size_t i = 0; ++ ++ /* count entries */ ++ for (p = autostart; *p; autostart_len++, p++) ++ while (*++p); ++ ++ autostart_pids = calloc(autostart_len, sizeof(pid_t)); ++ for (p = autostart; *p; i++, p++) { ++ if ((autostart_pids[i] = fork()) == 0) { ++ setsid(); ++ execvp(*p, (char *const *)p); ++ die("dwl: execvp %s:", *p); ++ } ++ /* skip arguments */ ++ while (*++p); ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -701,12 +726,23 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ size_t i; ++ + cleanuplisteners(); + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif + wl_display_destroy_clients(dpy); ++ ++ /* kill child processes */ ++ for (i = 0; i < autostart_len; i++) { ++ if (0 < autostart_pids[i]) { ++ kill(autostart_pids[i], SIGTERM); ++ waitpid(autostart_pids[i], NULL, 0); ++ } ++ } ++ + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); +@@ -1562,10 +1598,25 @@ gpureset(struct wl_listener *listener, void *data) + void + handlesig(int signo) + { +- if (signo == SIGCHLD) +- while (waitpid(-1, NULL, WNOHANG) > 0); +- else if (signo == SIGINT || signo == SIGTERM) ++ if (signo == SIGCHLD) { ++ pid_t pid, *p, *lim; ++ while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { ++ if (pid == child_pid) ++ child_pid = -1; ++ if (!(p = autostart_pids)) ++ continue; ++ lim = &p[autostart_len]; ++ ++ for (; p < lim; p++) { ++ if (*p == pid) { ++ *p = -1; ++ break; ++ } ++ } ++ } ++ } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); ++ } + } + + void +@@ -2252,6 +2303,7 @@ run(char *startup_cmd) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ ++ autostartexec(); + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) +-- +2.52.0 + diff --git a/patches/bar.patch b/patches/bar.patch new file mode 100644 index 0000000..e098118 --- /dev/null +++ b/patches/bar.patch @@ -0,0 +1,1271 @@ +From d6a2c4e7f0d3108c7a8a1ad6e76a62e854b2f8e1 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Mon, 5 Jan 2026 16:51:14 +0300 +Subject: [PATCH] Implement dwm bar clone + +--- + Makefile | 2 +- + config.def.h | 33 ++-- + drwl.h | 310 +++++++++++++++++++++++++++++++++++ + dwl.c | 444 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 697 insertions(+), 92 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 578194f..279b1c0 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..7da50d2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ ++static const char *fonts[] = {"monospace:size=10"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* 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 uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + +-/* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++/* tagging */ ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -123,6 +129,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -166,7 +173,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..21afd21 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,310 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2025 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 44f3ad9..7b2abf5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,10 +1,12 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -59,6 +61,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -69,6 +72,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -77,14 +81,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #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 TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + + typedef union { + int i; +@@ -94,6 +101,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -183,10 +191,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -194,6 +211,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -205,6 +227,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -247,6 +272,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -282,6 +314,8 @@ 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 drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -310,7 +344,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -331,9 +364,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -342,6 +377,7 @@ 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); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -406,6 +442,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; + static struct wl_listener cursor_button = {.notify = buttonpress}; +@@ -521,7 +566,7 @@ arrange(Monitor *m) + 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, sizeof(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 */ +@@ -576,6 +621,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -618,17 +668,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -636,17 +771,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && ++ (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { ++ cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ arg.ui = 1 << i; ++ } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) ++ click = ClkLtSymbol; ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); ++ if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { ++ b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } +@@ -721,6 +873,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -736,6 +890,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -748,6 +908,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -814,7 +975,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -1066,7 +1227,7 @@ createmon(struct wl_listener *listener, void *data) + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; +- strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); ++ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; +@@ -1088,8 +1249,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1399,6 +1567,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* draw status first so it can be overdrawn by tags later */ ++ if (m == selmon) { /* status is only drawn on selected monitor */ ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ ++ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ ++ if ((w = m->b.width - tw - x) > m->b.height) { ++ if (c) { ++ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ wlr_scene_buffer_set_dest_size(m->scene_buffer, ++ m->b.real_width, m->b.real_height); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1433,13 +1675,13 @@ 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) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* 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 ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1450,12 +1692,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1769,7 +2010,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1792,7 +2033,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -2085,44 +2326,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); +- printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} +- + void + powermgrsetmode(struct wl_listener *listener, void *data) + { +@@ -2251,22 +2454,14 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + + /* Mark stdout as non-blocking to avoid the startup script +@@ -2276,7 +2471,7 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2342,7 +2537,7 @@ setfloating(Client *c, int floating) + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2365,7 +2560,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2377,9 +2572,9 @@ setlayout(const Arg *arg) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; +- strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2452,6 +2647,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2646,6 +2842,11 @@ setup(void) + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2670,6 +2871,8 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); ++ open("/dev/null", O_RDWR); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2688,6 +2891,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[256]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2698,7 +2925,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2743,6 +2970,15 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++ drawbars(); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2771,7 +3007,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2784,7 +3020,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2832,7 +3068,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2932,6 +3168,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2942,12 +3185,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ m->lrpad = m->drw->font->height; ++ m->b.height = m->drw->font->height + 2; ++ m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2960,10 +3236,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2976,7 +3252,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -3017,6 +3293,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -3025,9 +3302,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3160,10 +3440,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.52.0 + diff --git a/patches/controlled_fullscreen.patch b/patches/controlled_fullscreen.patch new file mode 100644 index 0000000..b3f8325 --- /dev/null +++ b/patches/controlled_fullscreen.patch @@ -0,0 +1,132 @@ +From e0cecc228d436425c0d921a1eec5e0370d24613d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= +Date: Thu, 13 Nov 2025 09:09:22 -0300 +Subject: [PATCH] controlled fullscreen fix + +--- + dwl.c | 32 ++++++++++++-------------------- + 1 file changed, 12 insertions(+), 20 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 12f441e..4f124eb 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -518,9 +518,6 @@ arrange(Monitor *m) + } + } + +- 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 +@@ -878,7 +875,7 @@ commitnotify(struct wl_listener *listener, void *data) + return; + } + +- resize(c, c->geom, (c->isfloating && !c->isfullscreen)); ++ resize(c, c->geom, (c->isfloating)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) +@@ -1490,7 +1487,7 @@ focusstack(const Arg *arg) + { + /* Focus the next or previous client (in tiling order) on selmon */ + Client *c, *sel = focustop(selmon); +- if (!sel || (sel->isfullscreen && !client_has_children(sel))) ++ if (!sel) + return; + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { +@@ -1826,7 +1823,7 @@ monocle(Monitor *m) + int n = 0; + + wl_list_for_each(c, &clients, link) { +- if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ if (!VISIBLEON(c, m) || c->isfloating) + continue; + resize(c, m->w, 0); + n++; +@@ -1957,7 +1954,7 @@ moveresize(const Arg *arg) + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); +- if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) ++ if (!grabc || client_is_unmanaged(grabc)) + return; + + /* Float the window and tell motionnotify to grab it */ +@@ -2332,14 +2329,12 @@ 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]); ++ wlr_scene_node_reparent(&c->scene->node, ++ layers[c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + printstatus(); + } +@@ -2352,12 +2347,12 @@ 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]); + ++ wlr_scene_node_reparent(&c->scene->node, ++ layers[c->isfloating ? LyrFloat : LyrTile]); + if (fullscreen) { + c->prev = c->geom; +- resize(c, c->mon->m, 0); ++ resize(c, c->mon->w, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ +@@ -2716,7 +2711,7 @@ tile(Monitor *m) + Client *c; + + wl_list_for_each(c, &clients, link) +- if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ if (VISIBLEON(c, m) && !c->isfloating) + n++; + if (n == 0) + return; +@@ -2727,7 +2722,7 @@ tile(Monitor *m) + mw = m->w.width; + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { +- if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ if (!VISIBLEON(c, m) || c->isfloating) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +@@ -2747,7 +2742,7 @@ togglefloating(const Arg *arg) + { + Client *sel = focustop(selmon); + /* return if fullscreen */ +- if (sel && !sel->isfullscreen) ++ if (sel) + setfloating(sel, !sel->isfloating); + } + +@@ -2902,9 +2897,6 @@ updatemons(struct wl_listener *listener, void *data) + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ + arrange(m); +- /* make sure fullscreen clients have the right size */ +- if ((c = focustop(m)) && c->isfullscreen) +- resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ +-- +2.51.0 + diff --git a/patches/dim_unfocused.patch b/patches/dim_unfocused.patch new file mode 100644 index 0000000..f73d886 --- /dev/null +++ b/patches/dim_unfocused.patch @@ -0,0 +1,216 @@ +diff --git a/client.h b/client.h +index dabea35..3a31c25 100644 +--- a/client.h ++++ b/client.h +@@ -319,6 +319,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..4ca21c9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + 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.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -140,8 +142,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +@@ -172,5 +175,6 @@ static const Key keys[] = { + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY|ShiftMask, BTN_MIDDLE, toggledimmingclient, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index dc0c861..dcc3ece 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -112,6 +112,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -141,7 +142,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -231,6 +232,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -338,6 +340,8 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); ++static void toggledimmingclient(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -410,6 +414,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -466,6 +471,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1365,8 +1371,10 @@ 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 (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1384,7 +1392,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1681,8 +1689,7 @@ void + mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ +- Client *p = NULL; +- Client *w, *c = wl_container_of(listener, c, map); ++ Client *p, *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1716,6 +1723,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* 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; +@@ -1734,6 +1745,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2160,7 +2175,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + 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); +@@ -2170,6 +2185,8 @@ resize(Client *c, struct wlr_box geo, int interact) + 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); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); ++ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2681,6 +2698,27 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ ++void ++toggledimmingclient(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (sel) ++ sel -> neverdim ^= 1; ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/patches/gaps.patch b/patches/gaps.patch new file mode 100644 index 0000000..c025baf --- /dev/null +++ b/patches/gaps.patch @@ -0,0 +1,127 @@ +From 50e3dd4746b6cb719efb9f8213b94ac52a5320d9 Mon Sep 17 00:00:00 2001 +From: peesock +Date: Mon, 24 Jun 2024 20:06:42 -0700 +Subject: [PATCH] gaps! + +Co-authored-by: sewn +Co-authored-by: serenevoid +--- + config.def.h | 4 ++++ + dwl.c | 34 ++++++++++++++++++++++++++-------- + 2 files changed, 30 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..b388b4e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,9 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static int gaps = 1; /* 1 means gaps between windows are added */ ++static const unsigned int gappx = 10; /* gap pixel between windows */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +@@ -135,6 +138,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, ++ { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +diff --git a/dwl.c b/dwl.c +index dc0437e..dc851df 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -199,6 +199,7 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -336,6 +337,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -949,6 +951,8 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ ++ m->gaps = gaps; ++ + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { +@@ -2638,7 +2642,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + +@@ -2647,23 +2651,30 @@ tile(Monitor *m) + n++; + if (n == 0) + return; ++ if (smartgaps == n) ++ e = 0; + + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; +- i = my = ty = 0; ++ i = 0; ++ my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, ++ .width = mw - 2*gappx*e, .height = h}, 0); ++ my += c->geom.height + gappx*e; + } else { ++ r = n - i; ++ h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ .width = m->w.width - mw - gappx*e, .height = h}, 0); ++ ty += c->geom.height + gappx*e; + } + i++; + } +@@ -2686,6 +2697,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ selmon->gaps = !selmon->gaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/patches/movestack.patch b/patches/movestack.patch new file mode 100644 index 0000000..2543c66 --- /dev/null +++ b/patches/movestack.patch @@ -0,0 +1,87 @@ +From 08230817bd3926e29d9897657eb1852cb27d461f Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 4 Feb 2025 23:21:19 +0100 +Subject: [PATCH] Allows you to move a window up and down the stack + +--- + config.def.h | 2 ++ + dwl.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 45 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..2c129f2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -129,6 +129,8 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, movestack, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, movestack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index def2562..045d6fa 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -303,6 +303,7 @@ static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); + static void maximizenotify(struct wl_listener *listener, void *data); + static void monocle(Monitor *m); ++static void movestack(const Arg *arg); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +@@ -1786,6 +1787,48 @@ monocle(Monitor *m) + wlr_scene_node_raise_to_top(&c->scene->node); + } + ++void ++movestack(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ ++ if (!sel) { ++ return; ++ } ++ ++ if (wl_list_length(&clients) <= 1) { ++ return; ++ } ++ ++ if (arg->i > 0) { ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ } else { ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ /* backup one client */ ++ c = wl_container_of(c->link.prev, c, link); ++ } ++ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ arrange(selmon); ++} ++ + void + motionabsolute(struct wl_listener *listener, void *data) + { +-- +2.48.1 + diff --git a/patches/simpleborders.patch b/patches/simpleborders.patch new file mode 100644 index 0000000..e782d84 --- /dev/null +++ b/patches/simpleborders.patch @@ -0,0 +1,73 @@ +From 09759c3ef75158c366e9fc63814485fbb31a3ccf Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 30 Dec 2023 13:39:31 +1000 +Subject: [PATCH] simpleborders + +--- + dwl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/dwl.c b/dwl.c +index a2711f67..415fe1a0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -259,6 +259,7 @@ static void closemon(Monitor *m); + static void commitlayersurfacenotify(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 int countclients(Monitor *m); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +@@ -308,6 +309,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int needsborder(Client *c); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -849,6 +851,17 @@ commitpopup(struct wl_listener *listener, void *data) + wl_list_remove(&listener->link); + } + ++int ++countclients(Monitor *m) ++{ ++ unsigned int n = 0; ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ return n; ++} ++ + void + createdecoration(struct wl_listener *listener, void *data) + { +@@ -1927,6 +1940,14 @@ moveresize(const Arg *arg) + } + } + ++int ++needsborder(Client *c) { ++ return ((countclients(c->mon) > 1 ++ && c->mon->lt[c->mon->sellt]->arrange != monocle) ++ || c->isfloating) ++ && !c->isfullscreen; ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2190,6 +2211,7 @@ resize(Client *c, struct wlr_box geo, int interact) + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = needsborder(c) ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +-- +2.45.2 + diff --git a/patches/unclutter.patch b/patches/unclutter.patch new file mode 100644 index 0000000..dd5a298 --- /dev/null +++ b/patches/unclutter.patch @@ -0,0 +1,192 @@ +From 68914f40359ebccc7b684a1f74d82419b1796cdf Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Thu, 25 Jul 2024 17:59:05 +0200 +Subject: [PATCH] =?UTF-8?q?hide=20the=20mouse=20cursor=20if=20it=20isn?= + =?UTF-8?q?=E2=80=99t=20being=20used?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + config.def.h | 2 ++ + dwl.c | 74 +++++++++++++++++++++++++++++++++++++++++++++------- + 2 files changed, 67 insertions(+), 9 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..790c73d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -106,6 +106,8 @@ LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right + */ + static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; + ++static const int cursor_timeout = 5; ++ + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ + #define MODKEY WLR_MODIFIER_ALT + +diff --git a/dwl.c b/dwl.c +index 775dadf..621779e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -288,6 +288,8 @@ static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); ++static void handlecursoractivity(void); ++static int hidecursor(void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -389,6 +391,14 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; ++static struct wl_event_source *hide_source; ++static bool cursor_hidden = false; ++static struct { ++ enum wp_cursor_shape_device_v1_shape shape; ++ struct wlr_surface *surface; ++ int hotspot_x; ++ int hotspot_y; ++} last_cursor; + + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; +@@ -609,8 +619,9 @@ axisnotify(struct wl_listener *listener, void *data) + * for example when you move the scroll wheel. */ + struct wlr_pointer_axis_event *event = data; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +- /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented +- * by checking the event's orientation and the delta of the event */ ++ handlecursoractivity(); ++ /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented ++ * checking the event's orientation and the delta of the event */ + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis(seat, + event->time_msec, event->orientation, event->delta, +@@ -627,6 +638,7 @@ buttonpress(struct wl_listener *listener, void *data) + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ handlecursoractivity(); + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: +@@ -1563,6 +1575,32 @@ handlesig(int signo) + quit(NULL); + } + ++void ++handlecursoractivity(void) ++{ ++ wl_event_source_timer_update(hide_source, cursor_timeout * 1000); ++ ++ if (!cursor_hidden) ++ return; ++ ++ cursor_hidden = false; ++ ++ if (last_cursor.shape) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(last_cursor.shape)); ++ else ++ wlr_cursor_set_surface(cursor, last_cursor.surface, ++ last_cursor.hotspot_x, last_cursor.hotspot_y); ++} ++ ++int ++hidecursor(void *data) ++{ ++ wlr_cursor_unset_image(cursor); ++ cursor_hidden = true; ++ return 1; ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1903,6 +1941,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ handlecursoractivity(); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) +@@ -1927,7 +1966,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ +- if (!surface && !seat->drag) ++ if (!surface && !seat->drag && !cursor_hidden) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +@@ -2284,6 +2323,7 @@ run(char *startup_cmd) + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ handlecursoractivity(); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event +@@ -2307,9 +2347,16 @@ setcursor(struct wl_listener *listener, void *data) + * use the provided surface as the cursor image. It will set the + * hardware cursor on the output that it's currently on and continue to + * do so as the cursor moves between outputs. */ +- if (event->seat_client == seat->pointer_state.focused_client) +- wlr_cursor_set_surface(cursor, event->surface, +- event->hotspot_x, event->hotspot_y); ++ if (event->seat_client == seat->pointer_state.focused_client) { ++ last_cursor.shape = 0; ++ last_cursor.surface = event->surface; ++ last_cursor.hotspot_x = event->hotspot_x; ++ last_cursor.hotspot_y = event->hotspot_y; ++ ++ if (!cursor_hidden) ++ wlr_cursor_set_surface(cursor, event->surface, ++ event->hotspot_x, event->hotspot_y); ++ } + } + + void +@@ -2321,9 +2368,14 @@ setcursorshape(struct wl_listener *listener, void *data) + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided cursor shape. */ +- if (event->seat_client == seat->pointer_state.focused_client) +- wlr_cursor_set_xcursor(cursor, cursor_mgr, +- wlr_cursor_shape_v1_name(event->shape)); ++ if (event->seat_client == seat->pointer_state.focused_client) { ++ last_cursor.shape = event->shape; ++ last_cursor.surface = NULL; ++ ++ if (!cursor_hidden) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(event->shape)); ++ } + } + + void +@@ -2614,6 +2666,9 @@ setup(void) + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + ++ hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), ++ hidecursor, cursor); ++ + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, +@@ -2998,6 +3053,7 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); ++ handlecursoractivity(); + } + + Monitor * +-- +2.49.0 + diff --git a/patches/warpcursor.patch b/patches/warpcursor.patch new file mode 100644 index 0000000..6ac0329 --- /dev/null +++ b/patches/warpcursor.patch @@ -0,0 +1,71 @@ +From 4951ccc89dac2b557994b2f6c3aacb2398e2d1b1 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 4 Jan 2024 20:30:01 +1000 +Subject: [PATCH] warpcursor + +--- + dwl.c | 27 +++++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 145fd018..f7ad6c13 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -347,6 +347,7 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static void warpcursor(const Client *c); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -514,6 +515,7 @@ arrange(Monitor *m) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); ++ warpcursor(focustop(selmon)); + } + + void +@@ -1323,6 +1325,10 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ /* Warp cursor to center of client if it is outside */ ++ if (lift) ++ warpcursor(c); ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); +@@ -2927,6 +2933,27 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, &pointer.base, event->suggested_output); + } + ++void ++warpcursor(const Client *c) { ++ if (cursor_mode != CurNormal) { ++ return; ++ } ++ if (!c && selmon) { ++ wlr_cursor_warp_closest(cursor, ++ NULL, ++ selmon->w.x + selmon->w.width / 2.0 , ++ selmon->w.y + selmon->w.height / 2.0); ++ } ++ else if ( c && (cursor->x < c->geom.x || ++ cursor->x > c->geom.x + c->geom.width || ++ cursor->y < c->geom.y || ++ cursor->y > c->geom.y + c->geom.height)) ++ wlr_cursor_warp_closest(cursor, ++ NULL, ++ c->geom.x + c->geom.width / 2.0, ++ c->geom.y + c->geom.height / 2.0); ++} ++ + Monitor * + xytomon(double x, double y) + { +-- +2.45.2 +