aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikita Langer <nikitalanger@icloud.com>2026-04-06 22:26:07 +0200
committerNikita Langer <nikitalanger@icloud.com>2026-04-06 22:26:07 +0200
commit3b4f2dd9894397c44ec0e10d4247e886c4f4c314 (patch)
tree43c0ca4a04abf25d89446095ca16d81a5d5f26cf
downloaddwl-3b4f2dd9894397c44ec0e10d4247e886c4f4c314.tar.gz
dwl-3b4f2dd9894397c44ec0e10d4247e886c4f4c314.tar.bz2
dwl-3b4f2dd9894397c44ec0e10d4247e886c4f4c314.tar.xz
dwl-3b4f2dd9894397c44ec0e10d4247e886c4f4c314.zip
Initial Komm mit :)
-rw-r--r--.cache/clangd/index/client.h.3F0FA93466CA83D6.idxbin0 -> 9544 bytes
-rw-r--r--.cache/clangd/index/config.h.520665537FAA5C12.idxbin0 -> 15800 bytes
-rw-r--r--.cache/clangd/index/cursor-shape-v1-protocol.h.FDCF5AF1199922A2.idxbin0 -> 3638 bytes
-rw-r--r--.cache/clangd/index/drwl.h.463C3C311BFA7BD6.idxbin0 -> 5200 bytes
-rw-r--r--.cache/clangd/index/dwl.c.3A2E70A89D407193.idxbin0 -> 130646 bytes
-rw-r--r--.cache/clangd/index/pointer-constraints-unstable-v1-protocol.h.EF0E9ACED9834C0D.idxbin0 -> 946 bytes
-rw-r--r--.cache/clangd/index/tablet-v2-protocol.h.96E888EA68B82651.idxbin0 -> 21912 bytes
-rw-r--r--.cache/clangd/index/util.h.5C5C037E64CF0436.idxbin0 -> 490 bytes
-rw-r--r--.cache/clangd/index/wlr-layer-shell-unstable-v1-protocol.h.D5A2E88FE0AE1E2A.idxbin0 -> 3198 bytes
-rw-r--r--.cache/clangd/index/wlr-output-power-management-unstable-v1-protocol.h.DAFB3D7BA4BDBA3A.idxbin0 -> 3020 bytes
-rw-r--r--.cache/clangd/index/xdg-shell-protocol.h.E7B6726B3F4096F4.idxbin0 -> 23630 bytes
-rw-r--r--.gitea/issue_template/bug_report.yml62
-rw-r--r--.gitea/issue_template/enhancement-idea.yml9
-rw-r--r--.gitignore6
-rw-r--r--.i.rej171
-rw-r--r--.mailmap3
-rw-r--r--CHANGELOG.md217
-rw-r--r--LICENSE692
-rw-r--r--LICENSE.dwm39
-rw-r--r--LICENSE.sway19
-rw-r--r--LICENSE.tinywl127
-rw-r--r--Makefile87
-rw-r--r--README.md216
-rw-r--r--client.h404
-rw-r--r--compile_commands.json45
-rw-r--r--config.def.h288
-rw-r--r--config.mk36
-rw-r--r--drwl.h310
-rw-r--r--dwl.1258
-rw-r--r--dwl.c4130
-rw-r--r--dwl.desktop5
-rw-r--r--patches/alwayscenter.patch25
-rw-r--r--patches/autostart-0.8.patch141
-rw-r--r--patches/bar-systray-0.7.patch3023
-rw-r--r--patches/bar.patch1271
-rw-r--r--patches/better-resize-0.7.patch110
-rw-r--r--patches/monitorconfig-0.8.patch90
-rw-r--r--patches/mouse-trackpad-split-0.7.patch96
-rw-r--r--patches/movecenter.patch82
-rw-r--r--patches/movestack-0.8.patch87
-rw-r--r--patches/numlock-capslock-0.8.patch79
-rw-r--r--patches/tablet-input-0.8.patch378
-rw-r--r--patches/touch-input-0.8.patch259
-rw-r--r--patches/unclutter.patch192
-rw-r--r--patches/vanitygaps-0.7.patch358
-rw-r--r--protocols/wlr-layer-shell-unstable-v1.xml390
-rw-r--r--protocols/wlr-output-power-management-unstable-v1.xml128
-rwxr-xr-xstartdwl.sh13
-rw-r--r--util.c51
-rw-r--r--util.h5
50 files changed, 13902 insertions, 0 deletions
diff --git a/.cache/clangd/index/client.h.3F0FA93466CA83D6.idx b/.cache/clangd/index/client.h.3F0FA93466CA83D6.idx
new file mode 100644
index 0000000..8816457
--- /dev/null
+++ b/.cache/clangd/index/client.h.3F0FA93466CA83D6.idx
Binary files differ
diff --git a/.cache/clangd/index/config.h.520665537FAA5C12.idx b/.cache/clangd/index/config.h.520665537FAA5C12.idx
new file mode 100644
index 0000000..937eda9
--- /dev/null
+++ b/.cache/clangd/index/config.h.520665537FAA5C12.idx
Binary files differ
diff --git a/.cache/clangd/index/cursor-shape-v1-protocol.h.FDCF5AF1199922A2.idx b/.cache/clangd/index/cursor-shape-v1-protocol.h.FDCF5AF1199922A2.idx
new file mode 100644
index 0000000..5a3ef72
--- /dev/null
+++ b/.cache/clangd/index/cursor-shape-v1-protocol.h.FDCF5AF1199922A2.idx
Binary files differ
diff --git a/.cache/clangd/index/drwl.h.463C3C311BFA7BD6.idx b/.cache/clangd/index/drwl.h.463C3C311BFA7BD6.idx
new file mode 100644
index 0000000..dfd972d
--- /dev/null
+++ b/.cache/clangd/index/drwl.h.463C3C311BFA7BD6.idx
Binary files differ
diff --git a/.cache/clangd/index/dwl.c.3A2E70A89D407193.idx b/.cache/clangd/index/dwl.c.3A2E70A89D407193.idx
new file mode 100644
index 0000000..170e017
--- /dev/null
+++ b/.cache/clangd/index/dwl.c.3A2E70A89D407193.idx
Binary files differ
diff --git a/.cache/clangd/index/pointer-constraints-unstable-v1-protocol.h.EF0E9ACED9834C0D.idx b/.cache/clangd/index/pointer-constraints-unstable-v1-protocol.h.EF0E9ACED9834C0D.idx
new file mode 100644
index 0000000..2bc6fbf
--- /dev/null
+++ b/.cache/clangd/index/pointer-constraints-unstable-v1-protocol.h.EF0E9ACED9834C0D.idx
Binary files differ
diff --git a/.cache/clangd/index/tablet-v2-protocol.h.96E888EA68B82651.idx b/.cache/clangd/index/tablet-v2-protocol.h.96E888EA68B82651.idx
new file mode 100644
index 0000000..6363607
--- /dev/null
+++ b/.cache/clangd/index/tablet-v2-protocol.h.96E888EA68B82651.idx
Binary files differ
diff --git a/.cache/clangd/index/util.h.5C5C037E64CF0436.idx b/.cache/clangd/index/util.h.5C5C037E64CF0436.idx
new file mode 100644
index 0000000..4a7eed3
--- /dev/null
+++ b/.cache/clangd/index/util.h.5C5C037E64CF0436.idx
Binary files differ
diff --git a/.cache/clangd/index/wlr-layer-shell-unstable-v1-protocol.h.D5A2E88FE0AE1E2A.idx b/.cache/clangd/index/wlr-layer-shell-unstable-v1-protocol.h.D5A2E88FE0AE1E2A.idx
new file mode 100644
index 0000000..7ec0d49
--- /dev/null
+++ b/.cache/clangd/index/wlr-layer-shell-unstable-v1-protocol.h.D5A2E88FE0AE1E2A.idx
Binary files differ
diff --git a/.cache/clangd/index/wlr-output-power-management-unstable-v1-protocol.h.DAFB3D7BA4BDBA3A.idx b/.cache/clangd/index/wlr-output-power-management-unstable-v1-protocol.h.DAFB3D7BA4BDBA3A.idx
new file mode 100644
index 0000000..e90fd8c
--- /dev/null
+++ b/.cache/clangd/index/wlr-output-power-management-unstable-v1-protocol.h.DAFB3D7BA4BDBA3A.idx
Binary files differ
diff --git a/.cache/clangd/index/xdg-shell-protocol.h.E7B6726B3F4096F4.idx b/.cache/clangd/index/xdg-shell-protocol.h.E7B6726B3F4096F4.idx
new file mode 100644
index 0000000..2c32985
--- /dev/null
+++ b/.cache/clangd/index/xdg-shell-protocol.h.E7B6726B3F4096F4.idx
Binary files differ
diff --git a/.gitea/issue_template/bug_report.yml b/.gitea/issue_template/bug_report.yml
new file mode 100644
index 0000000..77ce108
--- /dev/null
+++ b/.gitea/issue_template/bug_report.yml
@@ -0,0 +1,62 @@
+name: Bug Report
+about: Something in dwl isn't working correctly
+title:
+labels:
+ - 'Kind/Bug'
+body:
+ - type: markdown
+ attributes:
+ value: |
+ - Only report bugs that can be reproduced on the main (or wlroots-next) branch without patches.
+ - Proprietary graphics drivers, including nvidia, are not supported. Please use the open source equivalents, such as nouveau, if you would like to use dwl.
+ - Report patch issues to their respective authors.
+
+ - type: input
+ id: dwl_version
+ attributes:
+ label: 'dwl version:'
+ placeholder: '`dwl -v`'
+ validations:
+ required: true
+
+ - type: input
+ id: wlroots_version
+ attributes:
+ label: 'wlroots version:'
+ validations:
+ required: true
+
+ - type: input
+ id: distro
+ attributes:
+ label: What distro (and version) are you using?
+ validations:
+ required: false
+
+ - type: textarea
+ attributes:
+ label: Description
+ value: |
+ The steps you took to reproduce the problem.
+ validations:
+ required: false
+
+ - type: textarea
+ id: debug_log
+ attributes:
+ label: Debug Log
+ value: |
+ Run `dwl -d 2> ~/dwl.log` from a TTY and attach the **full** (do not truncate it) file here, or upload it to a pastebin.
+ Please try to keep the reproduction as brief as possible and exit dwl.
+ validations:
+ required: false
+
+ - type: textarea
+ id: backtrace
+ attributes:
+ label: Stack Trace
+ value: |
+ - Only required if dwl crashes.
+ - If the lines mentioning dwl or wlroots have `??`. Please compile both dwl and wlroots from source (enabling debug symbols) and try to reproduce.
+ validations:
+ required: false
diff --git a/.gitea/issue_template/enhancement-idea.yml b/.gitea/issue_template/enhancement-idea.yml
new file mode 100644
index 0000000..be1bbf2
--- /dev/null
+++ b/.gitea/issue_template/enhancement-idea.yml
@@ -0,0 +1,9 @@
+name: Enhancement idea
+about: Suggest a feature or improvement
+title:
+labels:
+ - 'Kind/Feature'
+body:
+ - type: textarea
+ attributes:
+ label: Description
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0dde90e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+dwl
+*.o
+*-protocol.c
+*-protocol.h
+.ccls-cache
+config.h
diff --git a/.i.rej b/.i.rej
new file mode 100644
index 0000000..e5ce4ef
--- /dev/null
+++ b/.i.rej
@@ -0,0 +1,171 @@
+--- config.def.h
++++ config.def.h
+@@ -106,8 +106,6 @@ 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
+
+--- dwl.c
++++ dwl.c
+@@ -288,8 +288,6 @@ 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);
+@@ -391,14 +389,6 @@ 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;
+@@ -619,9 +609,8 @@ 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
+- * checking the event's orientation and the delta of the event */
++ /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented
++ * by 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,
+@@ -638,7 +627,6 @@ 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:
+@@ -1575,32 +1563,6 @@ 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)
+ {
+@@ -1941,7 +1903,6 @@ 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)
+@@ -1966,7 +1927,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 && !cursor_hidden)
++ if (!surface && !seat->drag)
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
+
+ pointerfocus(c, surface, sx, sy, time);
+@@ -2323,7 +2284,6 @@ 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
+@@ -2347,16 +2307,9 @@ 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) {
+- 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);
+- }
++ if (event->seat_client == seat->pointer_state.focused_client)
++ wlr_cursor_set_surface(cursor, event->surface,
++ event->hotspot_x, event->hotspot_y);
+ }
+
+ void
+@@ -2368,14 +2321,9 @@ 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) {
+- 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));
+- }
++ if (event->seat_client == seat->pointer_state.focused_client)
++ wlr_cursor_set_xcursor(cursor, cursor_mgr,
++ wlr_cursor_shape_v1_name(event->shape));
+ }
+
+ void
+@@ -2666,9 +2614,6 @@ 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,
+@@ -3053,7 +2998,6 @@ 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 *
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..1778cb9
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,3 @@
+Lennart Jablonka <humm@ljabl.com> <hummsmith42@gmail.com>
+Leonardo Hernández Hernández <leohdz172@proton.me> <leohdz172@outlook.com>
+Leonardo Hernández Hernández <leohdz172@proton.me> <leohdz172@protonmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..07c9ee4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,217 @@
+# Changelog
+
+* [Unreleased](#unreleased)
+* [0.7](#0.7)
+* [0.6](#0.6)
+* [0.5](#0.5)
+
+
+## Unreleased
+### Added
+
+* Support for the linux-drm-syncobj-v1 protocol ([wlroots!4715][wlroots!4715], [#685][685])
+* Allow the use of non-system wlroots library ([#646][646])
+
+[wlroots!4715]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4715
+[685]: https://codeberg.org/dwl/dwl/pulls/685
+[646]: https://codeberg.org/dwl/dwl/pulls/646
+
+
+### Changed
+### Deprecated
+### Removed
+### Fixed
+
+* Crash when a client is created while all outputs are disabled.
+
+### Security
+### Contributors
+
+
+## 0.7
+
+This version is just 0.6 with wlroots 0.18 compatibility.
+
+### Added
+
+* Add support for the alpha-modifier-v1 protocol ([wlroots!4616][wlroots!4616]).
+* dwl now will survive GPU resets ([#601][601]).
+
+[wlroots!4616]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4616
+[601]: https://codeberg.org/dwl/dwl/issues/601
+
+
+### Contributors
+
+Guido Cella
+
+
+## 0.6
+
+### Added
+
+* Add `rootcolor` to change the default background color ([#544][544]).
+* Implement the wlr-virtual-pointer-unstable-v1 protocol ([#574][574]).
+* Implement the pointer-constraints and relative-pointer protocols ([#317][317])
+* Implement the wlr-output-power-management protocol ([#599][599])
+
+[544]: https://codeberg.org/dwl/dwl/pulls/544
+[574]: https://codeberg.org/dwl/dwl/pulls/574
+[317]: https://codeberg.org/dwl/dwl/issues/317
+[599]: https://codeberg.org/dwl/dwl/issues/559
+
+
+### Changed
+
+* Keyboards are now managed through keyboard groups ([#549][549]).
+* Only the first matched keybinding is executed.
+* Allow toggling the layout before selecting a different one ([#570][570]).
+* Fullscreen clients are now rendered above wlr_layer_surfaces in the top layer
+ ([#609][609]).
+* The default menu was changed from `bemenu-run` to `wmenu-run` ([#553][553]).
+* The option `sloppyfocus` now replicates the dwm behavior ([#599][599]).
+* Allow configure position of monitors with negative values. (-1, -1) is
+ used to auto-configure them ([#635][635]).
+* dwl now kills the entire process group of `startup_cmd`
+* The O_NONBLOCK flag is set for stdout.
+
+[549]: https://codeberg.org/dwl/dwl/pulls/549
+[570]: https://codeberg.org/dwl/dwl/pulls/570
+[609]: https://codeberg.org/dwl/dwl/pulls/609
+[553]: https://codeberg.org/dwl/dwl/issues/553
+[599]: https://codeberg.org/dwl/dwl/pulls/599
+[635]: https://codeberg.org/dwl/dwl/pulls/635
+
+
+### Removed
+
+* The SLOC limit is now removed ([#497][497]).
+
+[497]: https://codeberg.org/dwl/dwl/pulls/497
+
+
+### Fixed
+
+* Clients not having the correct border color when mapping.
+* Compliance with the xdg-decoration-unstable-v1 ([#546][546]).
+* dwl no longer sends negative values in xdg_toplevel.configure events.
+* Crashes with disabled monitors ([#472][472]).
+
+[546]: https://codeberg.org/dwl/dwl/pulls/546
+[472]: https://codeberg.org/dwl/dwl/issues/472
+
+
+### Contributors
+
+Ben Jargowsky
+Benjamin Chausse
+David Donahue
+Devin J. Pohly
+Dima Krasner
+Emil Miler
+Forrest Bushstone
+Guido Cella
+Peter Hofmann
+Rutherther
+Squibid
+choc
+fictitiousexistence
+korei999
+sewn
+thanatos
+
+
+## 0.5
+
+### Added
+
+* Allow configure x and y position of outputs ([#301][301])
+* Implement repeatable keybindings ([#368][368])
+* Print app id in printstatus() output ([#381][381])
+* Display client count in monocle symbol ([#387][387])
+* Export XCURSOR_SIZE to fix apps using an older version of Qt ([#425][425])
+* Support for wp-fractional-scale-v1 (through wlr_scene: [wlroots!3511][wlroots!3511])
+* dwl now sends `wl_surface.preferred_buffer_scale` (through wlr_scene: [wlroots!4269][wlroots!4269])
+* Add support for xdg-shell v6 ([#465][465])
+* Add support for wp-cursor-shape-v1 ([#444][444])
+* Add desktop file ([#484][484])
+* Add macro to easily configure colors ([#466][466])
+* Color of urgent clients are now red ([#494][494])
+* New flag `-d` and option `log_level` to change the wlroots debug level
+* Add CHANGELOG.md ([#501][501])
+
+[301]: https://github.com/djpohly/dwl/pull/301
+[368]: https://github.com/djpohly/dwl/pull/368
+[381]: https://github.com/djpohly/dwl/pull/381
+[387]: https://github.com/djpohly/dwl/issues/387
+[425]: https://github.com/djpohly/dwl/pull/425
+[wlroots!4269]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4269
+[wlroots!3511]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3511
+[465]: https://github.com/djpohly/dwl/pull/465
+[444]: https://github.com/djpohly/dwl/pull/444
+[484]: https://github.com/djpohly/dwl/pull/484
+[466]: https://github.com/djpohly/dwl/issues/466
+[494]: https://github.com/djpohly/dwl/pull/494
+[501]: https://github.com/djpohly/dwl/pull/501
+
+
+### Changed
+
+* Replace `tags` with `TAGCOUNT` in config.def.h ([#403][403])
+* Pop ups are now destroyed when focusing another client ([#408][408])
+* dwl does not longer respect size hints, instead clip windows if they are
+ larger than they should be ([#455][455])
+* The version of wlr-layer-shell-unstable-v1 was lowered to 3 (from 4)
+* Use the same border color as dwm ([#494][494])
+
+[403]: https://github.com/djpohly/dwl/pull/403
+[408]: https://github.com/djpohly/dwl/pull/409
+[455]: https://github.com/djpohly/dwl/pull/455
+[494]: https://github.com/djpohly/dwl/pull/494
+
+
+### Removed
+
+* Remove unused `rootcolor` option ([#401][401])
+* Remove support for wlr-input-inhibitor-unstable-v1 ([#430][430])
+* Remove support for KDE idle protocol ([#431][431])
+
+[401]: https://github.com/djpohly/dwl/pull/401
+[430]: https://github.com/djpohly/dwl/pull/430
+[431]: https://github.com/djpohly/dwl/pull/431
+
+
+### Fixed
+
+* Fix crash when creating a layer surface with all outputs disabled
+ ([#421][421])
+* Fix other clients being shown as focused if the focused client have pop ups
+ open ([#408][408])
+* Resize fullscreen clients when updating monitor mode
+* dwl no longer crash at exit like sometimes did
+* Fullscreen background appearing above clients ([#487][487])
+* Fix a segfault when user provides invalid xkb_rules ([#518][518])
+
+[421]: https://github.com/djpohly/dwl/pull/421
+[408]: https://github.com/djpohly/dwl/issues/408
+[487]: https://github.com/djpohly/dwl/issues/487
+[518]: https://github.com/djpohly/dwl/pull/518
+
+
+### Contributors
+
+* A Frederick Christensen
+* Angelo Antony
+* Ben Collerson
+* Devin J. Pohly
+* Forrest Bushstone
+* gan-of-culture
+* godalming123
+* Job79
+* link2xt
+* Micah Gorrell
+* Nikita Ivanov
+* Palanix
+* pino-desktop
+* Weiseguy
+* Yves Zoundi
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..658085a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,692 @@
+dwl - dwm for Wayland
+
+Copyright © 2020 dwl team
+
+See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+----
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/LICENSE.dwm b/LICENSE.dwm
new file mode 100644
index 0000000..507e4dc
--- /dev/null
+++ b/LICENSE.dwm
@@ -0,0 +1,39 @@
+Portions of dwl based on dwm code are used under the following license:
+
+MIT/X Consortium License
+
+© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+© 2006-2009 Jukka Salmi <jukka at salmi dot ch>
+© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
+© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com>
+© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com>
+© 2007-2009 Christof Musik <christof at sendfax dot de>
+© 2007-2009 Premysl Hruby <dfenze at gmail dot com>
+© 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
+© 2008 Martin Hurton <martin dot hurton at gmail dot com>
+© 2008 Neale Pickett <neale dot woozle dot org>
+© 2009 Mate Nagy <mnagy at port70 dot net>
+© 2010-2016 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2011 Christoph Lohmann <20h@r-36.net>
+© 2015-2016 Quentin Rameau <quinq@fifth.space>
+© 2015-2016 Eric Pruitt <eric.pruitt@gmail.com>
+© 2016-2017 Markus Teich <markus.teich@stusta.mhn.de>
+
+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.
diff --git a/LICENSE.sway b/LICENSE.sway
new file mode 100644
index 0000000..3e0cacc
--- /dev/null
+++ b/LICENSE.sway
@@ -0,0 +1,19 @@
+Copyright (c) 2016-2017 Drew DeVault
+
+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.
diff --git a/LICENSE.tinywl b/LICENSE.tinywl
new file mode 100644
index 0000000..7023690
--- /dev/null
+++ b/LICENSE.tinywl
@@ -0,0 +1,127 @@
+dwl is originally based on TinyWL, which is used under the following license:
+
+This work is licensed under CC0, which effectively puts it in the public domain.
+
+---
+
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6a7c0be
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,87 @@
+.POSIX:
+.SUFFIXES:
+
+include config.mk
+
+# flags for compiling
+DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \
+ -DVERSION=\"$(VERSION)\" $(XWAYLAND)
+DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \
+ -Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \
+ -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \
+ -Wfloat-conversion
+
+# CFLAGS / LDFLAGS
+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)
+
+all: dwl
+dwl: dwl.o util.o
+ $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@
+dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \
+ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \
+ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \
+ tablet-v2-protocol.h
+util.o: util.c util.h
+
+# wayland-scanner is a tool which generates C headers and rigging for Wayland
+# protocols, which are specified in XML. wlroots requires you to rig these up
+# to your build system yourself and provide them in the include path.
+WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner`
+WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols`
+
+cursor-shape-v1-protocol.h:
+ $(WAYLAND_SCANNER) enum-header \
+ $(WAYLAND_PROTOCOLS)/staging/cursor-shape/cursor-shape-v1.xml $@
+pointer-constraints-unstable-v1-protocol.h:
+ $(WAYLAND_SCANNER) enum-header \
+ $(WAYLAND_PROTOCOLS)/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml $@
+wlr-layer-shell-unstable-v1-protocol.h:
+ $(WAYLAND_SCANNER) enum-header \
+ protocols/wlr-layer-shell-unstable-v1.xml $@
+wlr-output-power-management-unstable-v1-protocol.h:
+ $(WAYLAND_SCANNER) server-header \
+ protocols/wlr-output-power-management-unstable-v1.xml $@
+xdg-shell-protocol.h:
+ $(WAYLAND_SCANNER) server-header \
+ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+tablet-v2-protocol.h:
+ $(WAYLAND_SCANNER) server-header \
+ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@
+
+config.h:
+ cp config.def.h $@
+clean:
+ rm -f dwl *.o *-protocol.h config.h
+
+patch:
+ rm -f *.rej *.orig
+
+dist: clean
+ mkdir -p dwl-$(VERSION)
+ cp -R LICENSE* Makefile CHANGELOG.md README.md client.h config.def.h \
+ config.mk protocols dwl.1 dwl.c util.c util.h dwl.desktop \
+ dwl-$(VERSION)
+ tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION)
+ rm -rf dwl-$(VERSION)
+
+install: dwl
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ rm -f $(DESTDIR)$(PREFIX)/bin/dwl
+ cp -f dwl $(DESTDIR)$(PREFIX)/bin
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl
+ mkdir -p $(DESTDIR)$(MANDIR)/man1
+ cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1
+ chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1
+ mkdir -p $(DESTDIR)$(DATADIR)/wayland-sessions
+ cp -f dwl.desktop /usr/share/wayland-sessions/dwl.desktop
+ chmod 644 /usr/share/wayland-sessions/dwl.desktop
+ cp -f startdwl.sh /usr/local/bin/startdwl.sh
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \
+ /usr/share/wayland-sessions/dwl.desktop
+
+.SUFFIXES: .c .o
+.c.o:
+ $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $<
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3a3b9bd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,216 @@
+# dwl - dwm for Wayland
+
+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,
+primarily in terms of functionality, and secondarily in terms of
+philosophy. Like [dwm], dwl is:
+
+- Easy to understand, hack on, and extend with patches
+- One C source file (or a very small number) configurable via `config.h`
+- Tied to as few external dependencies as possible
+
+## Getting Started:
+
+### Latest semi-stable [release]
+This is probably where you want to start. This builds against the [wlroots]
+versions currently shipping in major distributions. If your
+distribution's `wlroots` version is older, use an earlier dwl [release].
+The `wlroots` version against which a given `dwl` release builds is specified
+with each release on the [release] page
+
+### Development branch [main]
+Active development progresses on the `main` branch. The `main` branch is built
+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:
+- libinput
+- wayland
+- wlroots (compiled with the libinput backend)
+- xkbcommon
+- wayland-protocols (compile-time only)
+- pkg-config (compile-time only)
+
+dwl has the following additional dependencies if XWayland support is enabled:
+- libxcb
+- libxcb-wm
+- wlroots (compiled with X11 support)
+- Xwayland (runtime only)
+
+Install these (and their `-devel` versions if your distro has separate
+development packages) and run `make`. If you wish to build against a released
+version of wlroots (*you probably do*), use a [release] or a [0.x branch]. If
+you want to use the unstable development `main` branch, you need to use the git
+version of [wlroots].
+
+To enable XWayland, you should uncomment its flags in `config.mk`.
+
+## Configuration
+
+All configuration is done by editing `config.h` and recompiling, in the same
+manner as [dwm]. There is no way to separately restart the window manager in
+Wayland without restarting the entire display server, so any changes will take
+effect the next time dwl is executed.
+
+As in the [dwm] community, we encourage users to share patches they have
+created. Check out the [dwl-patches] repository!
+
+## Running dwl
+
+dwl can be run on any of the backends supported by wlroots. This means you can
+run it as a separate window inside either an X11 or Wayland session, as well as
+directly from a VT console. Depending on your distro's setup, you may need to
+add your user to the `video` and `input` groups before you can run dwl on a
+VT. If you are using `elogind` or `systemd-logind` you need to install polkit;
+otherwise you need to add yourself in the `seat` group and enable/start the
+seatd daemon.
+
+When dwl is run with no arguments, it will launch the server and begin handling
+any shortcuts configured in `config.h`. There is no status bar or other
+decoration initially; these are instead clients that can be run within the
+Wayland session. Do note that the default background color is grey. This can be
+modified in `config.h`.
+
+If you would like to run a script or command automatically at startup, you can
+specify the command using the `-s` option. This command will be executed as a
+shell command using `/bin/sh -c`. It serves a similar function to `.xinitrc`,
+but differs in that the display server will not shut down when this process
+terminates. Instead, dwl will send this process a SIGTERM at shutdown and wait
+for it to terminate (if it hasn't already). This makes it ideal for execing into
+a user service manager like [s6], [anopa], [runit], [dinit], or [`systemd
+--user`].
+
+Note: The `-s` command is run as a *child process* of dwl, which means that it
+does not have the ability to affect the environment of dwl or of any processes
+that it spawns. If you need to set environment variables that affect the entire
+dwl session, these must be set prior to running dwl. For example, Wayland
+requires a valid `XDG_RUNTIME_DIR`, which is usually set up by a session manager
+such as `elogind` or `systemd-logind`. If your system doesn't do this
+automatically, you will need to configure it prior to launching `dwl`, e.g.:
+
+ export XDG_RUNTIME_DIR=/tmp/xdg-runtime-$(id -u)
+ mkdir -p $XDG_RUNTIME_DIR
+ dwl
+
+### Status information
+
+Information about selected layouts, current window title, app-id, and
+selected/occupied/urgent tags is written to the stdin of the `-s` command (see
+the `STATUS INFORMATION` section in `_dwl_(1)`). This information can be used to
+populate an external status bar with a script that parses the
+information. Failing to read this information will cause dwl to block, so if you
+do want to run a startup command that does not consume the status information,
+you can close standard input with the `<&-` shell redirection, for example:
+
+ dwl -s 'foot --server <&-'
+
+If your startup command is a shell script, you can achieve the same inside the
+script with the line
+
+ exec <&-
+
+To get a list of status bars that work with dwl consult our [wiki].
+
+### (Known) Java nonreparenting WM issue
+Certain IDEs don't display correctly unless an environmental variable for Java AWT
+indicates that the WM is nonreparenting.
+
+For some Java AWT-based IDEs, such as Xilinx Vivado and Microchip MPLAB X, the
+following environment variable needs to be set before running the IDE or dwl:
+
+ export _JAVA_AWT_WM_NONREPARENTING=1
+
+## Replacements for X applications
+
+You can find a [list of useful resources on our wiki].
+
+## Background
+
+dwl is not meant to provide every feature under the sun. Instead, like [dwm], it
+sticks to features which are necessary, simple, and straightforward to implement
+given the base on which it is built. Implemented default features are:
+
+- Any features provided by [dwm]/Xlib: simple window borders, tags, keybindings,
+ client rules, mouse move/resize. Providing a built-in status bar is an
+ exception to this goal, to avoid dependencies on font rendering and/or drawing
+ libraries when an external bar could work well.
+- Configurable multi-monitor layout support, including position and rotation
+- Configurable HiDPI/multi-DPI support
+- Idle-inhibit protocol which lets applications such as mpv disable idle
+ monitoring
+- Provide information to external status bars via stdout/stdin
+- Urgency hints via xdg-activate protocol
+- Support screen lockers via ext-session-lock-v1 protocol
+- Various Wayland protocols
+- XWayland support as provided by wlroots (can be enabled in `config.mk`)
+- Zero flickering - Wayland users naturally expect that "every frame is perfect"
+- Layer shell popups (used by Waybar)
+- Damage tracking provided by scenegraph API
+
+Given the Wayland architecture, dwl has to implement features from [dwm] **and**
+the xorg-server. Because of this, it is impossible to maintain the original
+project goal of 2000 SLOC and have a reasonably complete compositor with
+features comparable to [dwm]. However, this does not mean that the code will grow
+indiscriminately. We will try to keep the code as small as possible.
+
+Features under consideration (possibly as patches) are:
+
+- Protocols made trivial by wlroots
+- Implement the text-input and input-method protocols to support IME once ibus
+ implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and
+ https://codeberg.org/dwl/dwl/pulls/235)
+
+Feature *non-goals* for the main codebase include:
+
+- Client-side decoration (any more than is necessary to tell the clients not to)
+- Client-initiated window management, such as move, resize, and close, which can
+ be done through the compositor
+- Animations and visual effects
+
+## Acknowledgements
+
+dwl began by extending the TinyWL example provided (CC0) by the sway/wlroots
+developers. This was made possible in many cases by looking at how sway
+accomplished something, then trying to do the same in as suckless a way as
+possible.
+
+Many thanks to suckless.org and the [dwm] developers and community for the
+inspiration, and to the various contributors to the project, including:
+
+- **Devin J. Pohly for creating and nurturing the fledgling project**
+- Alexander Courtis for the XWayland implementation
+- Guido Cella for the layer-shell protocol implementation, patch maintenance,
+ and for helping to keep the project running
+- Stivvo for output management and fullscreen support, and patch maintenance
+
+
+[wlroots]: https://gitlab.freedesktop.org/wlroots
+[dwm]: https://dwm.suckless.org/
+[`systemd --user`]: https://wiki.archlinux.org/title/Systemd/User
+[#dwl on Libera Chat]: https://web.libera.chat/?channels=#dwl
+[0.7-rc1]: https://codeberg.org/dwl/dwl/releases/tag/v0.7-rc1
+[0.x branch]: https://codeberg.org/dwl/dwl/branches
+[anopa]: https://jjacky.com/anopa/
+[dinit]: https://davmac.org/projects/dinit/
+[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/
+[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots/
+[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
diff --git a/client.h b/client.h
new file mode 100644
index 0000000..d9f90bb
--- /dev/null
+++ b/client.h
@@ -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/compile_commands.json b/compile_commands.json
new file mode 100644
index 0000000..49de6b3
--- /dev/null
+++ b/compile_commands.json
@@ -0,0 +1,45 @@
+[
+ {
+ "file": "dwl.c",
+ "arguments": [
+ "cc",
+ "-I/usr/include/harfbuzz",
+ "-I/usr/include/freetype2",
+ "-I/usr/include/libpng16",
+ "-I/usr/include/glib-2.0",
+ "-I/usr/lib/glib-2.0/include",
+ "-I/usr/include/sysprof-6",
+ "-pthread",
+ "-DUTF8PROC_EXPORTS",
+ "-I/usr/include/pixman-1",
+ "-I/usr/include/wlroots-0.19",
+ "-I/usr/include/pixman-1",
+ "-I/usr/include/libdrm",
+ "-I.",
+ "-DWLR_USE_UNSTABLE",
+ "-D_POSIX_C_SOURCE=200809L",
+ "-DVERSION=\"v0.8-7-ga2d03cf-dirty\"",
+ "-DXWAYLAND",
+ "-g",
+ "-Wpedantic",
+ "-Wall",
+ "-Wextra",
+ "-Wdeclaration-after-statement",
+ "-Wno-unused-parameter",
+ "-Wshadow",
+ "-Wunused-macros",
+ "-Werror=strict-prototypes",
+ "-Werror=implicit",
+ "-Werror=return-type",
+ "-Werror=incompatible-pointer-types",
+ "-Wfloat-conversion",
+ "-O1",
+ "-o",
+ "dwl.o",
+ "-c",
+ "dwl.c"
+ ],
+ "directory": "/home/nikita/.config/wayless/dwl",
+ "output": "dwl.o"
+ }
+] \ No newline at end of file
diff --git a/config.def.h b/config.def.h
new file mode 100644
index 0000000..0907336
--- /dev/null
+++ b/config.def.h
@@ -0,0 +1,288 @@
+/* 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 tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */
+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 const int monoclegaps = 1; /* 1 means outer gaps in monocle layout */
+static const unsigned int borderpx = 3; /* border pixel of windows */
+static const unsigned int gappih = 5; /* horiz inner gap between windows */
+static const unsigned int gappiv = 5; /* vert inner gap between windows */
+static const unsigned int gappoh = 5; /* horiz outer gap between windows and screen edge */
+static const unsigned int gappov = 5; /* vert outer gap between windows and screen edge */
+static const int showbar = 1; /* 0 means no bar */
+static const int topbar = 1; /* 0 means bottom bar */
+static const char *fonts[] = {"JetBrains Mono Nerd Font Propo:size=11"};
+static const char wmenufont[] = "JetBrains Mono Nerd Font Propo 11";
+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 const int respect_monitor_reserved_area = 0; /* 1 to monitor center while respecting the monitor's reserved area, 0 to monitor center */
+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;
+
+/* window resizing */
+/* resize_corner:
+ * 0: top-left
+ * 1: top-right
+ * 2: bottom-left
+ * 3: bottom-right
+ * 4: closest to the cursor
+ */
+static const int resize_corner = 3;
+static const int warp_cursor = 1; /* 1: warp to corner, 0: don’t warp */
+static const int lock_cursor = 0; /* 1: lock cursor, 0: don't lock */
+
+/* Autostart */
+static const char *const autostart[] = {
+ // "wbg", "/path/to/your/image", NULL,
+ "swaybg", "-i", "/usr/share/backgrounds/meine/wallhaven-k911kq.jpg", NULL,
+ // "kdeconnectd &", NULL,
+ // "dunst &", 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" */
+ { "steam", NULL, 1 << 6, 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 resx resy rate mode adaptive */
+ /*{"eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, 0, 0, 120.000f, 1, 1}, example of a HiDPI laptop monitor at 120Hz: */
+ /*
+ * mode lets the user decide how dwl should implement the modes:
+ * -1 sets a custom mode following the user's choice
+ * All other numbers set the mode at the index n; 0 is the standard mode; see wlr-randr
+ */
+{ "DP-1", 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, 0, 0, 0.0f, 1, 1 },
+ { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 0.0f, 0, 1},
+ // {"DP-1", 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 260.0f, 0, 1},
+ // { "DP-1", 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 0.0f, 1, 1 },
+ // { "DP-1", 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 0.0f, 2, 1 },
+ // {"DP-1", 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 260.002f, 0, 1},
+ // { "DP-1", 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 2560, 1440, 260.002014f, -1, 1 },
+ // {"DP-1", 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, 0, 0, 260.002014f, 0, 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,
+ .layout = "de",
+};
+
+/* numlock and capslock */
+static const int numlock = 1;
+static const int capslock = 0;
+
+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 disable_while_typing = 1;
+static const int left_handed = 0;
+static const int middle_button_emulation = 0;
+
+/* Natural scrolling */
+static const int trackpad_natural_scrolling = 0;
+static const int mouse_natural_scrolling = 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 trackpad_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
+static const double trackpad_accel_speed = 0.0;
+static const enum libinput_config_accel_profile mouse_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
+static const double mouse_accel_speed = 0.5;
+
+/* 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_LOGO
+
+#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", "-f", wmenufont, NULL };
+static const char *firefox[] = { "firefox", NULL };
+static const char *steam[] = { "steam", NULL };
+static const char *altmenu[] = { "wofi", "--show", "drun", NULL };
+static const char *screen[] = { "wlr-randr", "--output", "DP-1", "--mode", "2560x1440@260.002014", NULL };
+static const char *altscreen[] = { "wlr-randr", "--output", "DP-1", "--mode", "2560x1440@240.001007", NULL };
+static const char *dolphinfix[]= { "/usr/local/bin/dolphinfix.sh", NULL };
+static const char *dolphin[] = { "dolphin" ,NULL };
+static const char *downvol[] = { "pactl", "set-sink-volume", "@DEFAULT_SINK@", "-5%", NULL };
+static const char *upvol[] = { "pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%", NULL };
+static const char *mutevol[] = { "pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle", NULL };
+static const char *minecraft[] = { "prismlauncher", NULL };
+static const char *lockcmd[] = { "swaylock", NULL };
+static const char *brighter[] = { "/usr/local/bin/dwm-scripts/bright.sh", "5", NULL };
+static const char *dimmer[] = { "/usr/local/bin/dwm-scripts/bright.sh", "-5", NULL };
+
+static const Key keys[] = {
+ /* Note that Shift changes certain key codes: 2 -> at, etc. */
+ /* modifier key function argument */
+ { MODKEY, XKB_KEY_d, spawn, {.v = menucmd} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_d, spawn, {.v = altmenu} },
+ { MODKEY, XKB_KEY_n, spawn, {.v = screen} },
+ { MODKEY, XKB_KEY_n, spawn, {.v = minecraft} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_n, spawn, {.v = altscreen} },
+ { MODKEY|WLR_MODIFIER_ALT, XKB_KEY_n, spawn, {.v = lockcmd} },
+ { MODKEY, XKB_KEY_e, spawn, {.v = dolphin} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_e, spawn, {.v = dolphinfix} },
+ { MODKEY, XKB_KEY_s, spawn, {.v = steam} },
+ { MODKEY, XKB_KEY_Return, spawn, {.v = termcmd} },
+ { MODKEY, XKB_KEY_f, spawn, {.v = firefox} },
+ { 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_o, incnmaster, {.i = -1} },
+ { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} },
+ { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, zoom, {0} },
+ { MODKEY, XKB_KEY_Tab, view, {0} },
+ { MODKEY, XKB_KEY_x, movecenter, {0} },
+ /* Vanity gaps */
+
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_h, incgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_l, incgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_ALT|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_ALT|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_ALT|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_ALT|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_ALT, XKB_KEY_0, togglegaps, {0} },
+ { MODKEY|WLR_MODIFIER_ALT|WLR_MODIFIER_SHIFT, XKB_KEY_equal,defaultgaps, {0} },
+ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } },
+ { MODKEY, XKB_KEY_z, incihgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_z, incivgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_ALT, XKB_KEY_y, incohgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_ALT, XKB_KEY_z, incohgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Z, incovgaps, {.i = -1 } },
+
+ /* Vanity gaps end */
+ { MODKEY, XKB_KEY_q, killclient, {0} },
+ { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
+ { MODKEY, XKB_KEY_v, 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|WLR_MODIFIER_SHIFT, XKB_KEY_f, togglefullscreen, {0} },
+ { MODKEY, XKB_KEY_0, view, {.ui = ~0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_equal, 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_semicolon, tagmon, {.i = WLR_DIRECTION_LEFT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_colon, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
+ TAGKEYS( XKB_KEY_2, XKB_KEY_quotedbl, 1),
+ TAGKEYS( XKB_KEY_3, XKB_KEY_section, 2),
+ TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3),
+ TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4),
+ TAGKEYS( XKB_KEY_6, XKB_KEY_ampersand, 5),
+ TAGKEYS( XKB_KEY_7, XKB_KEY_slash, 6),
+ TAGKEYS( XKB_KEY_8, XKB_KEY_parenleft, 7),
+ TAGKEYS( XKB_KEY_9, XKB_KEY_parenright, 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.mk b/config.mk
new file mode 100644
index 0000000..9ccea74
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,36 @@
+_VERSION = 0.8-dev
+VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)`
+
+PKG_CONFIG = pkg-config
+
+# paths
+PREFIX = /usr/local
+MANDIR = $(PREFIX)/share/man
+DATADIR = $(PREFIX)/share
+
+WLR_INCS = `$(PKG_CONFIG) --cflags wlroots-0.19`
+WLR_LIBS = `$(PKG_CONFIG) --libs wlroots-0.19`
+
+# Allow using an alternative wlroots installation
+# This has to have all the includes required by wlroots, e.g:
+# Assuming wlroots git repo is "${PWD}/wlroots" and you only ran "meson setup build && ninja -C build"
+#WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \
+# -I$(PWD)/wlroots/include
+# Set -rpath to avoid using the wrong library.
+#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/build -L$(PWD)/wlroots/build -lwlroots-0.19
+
+# Assuming you ran "meson setup --prefix ${PWD}/0.19 build && ninja -C build install"
+#WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \
+# -I$(PWD)/wlroots/0.19/include/wlroots-0.19
+#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/0.19/lib64 -L$(PWD)/wlroots/0.19/lib64 -lwlroots-0.19
+
+XWAYLAND =
+XLIBS =
+# Uncomment to build XWayland support
+XWAYLAND = -DXWAYLAND
+XLIBS = xcb xcb-icccm
+
+# dwl itself only uses C99 features, but wlroots' headers use anonymous unions (C11).
+# To avoid warnings about them, we do not use -std=c99 and instead of using the
+# gmake default 'CC=c99', we use cc.
+CC = cc
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 <sewn@disroot.org>
+ * Copyright (c) 2024 notchoc <notchoc@disroot.org>
+ *
+ * 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 <bjoern@hoehrmann.de>
+ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
+ */
+#pragma once
+
+#include <stdlib.h>
+#include <fcft/fcft.h>
+#include <pixman-1/pixman.h>
+
+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.1 b/dwl.1
new file mode 100644
index 0000000..7fee870
--- /dev/null
+++ b/dwl.1
@@ -0,0 +1,258 @@
+.Dd January 8, 2021
+.Dt DWL 1
+.Os
+.Sh NAME
+.Nm dwl
+.Nd dwm for Wayland
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl d
+.Op Fl s Ar startup command
+.Sh DESCRIPTION
+.Nm
+is a Wayland compositor based on wlroots.
+It is intended to fill the same space in the Wayland world that
+.Nm dwm
+does for X11.
+.Pp
+When given the
+.Fl v
+option,
+.Nm
+writes its name and version to standard error and exits unsuccessfully.
+.Pp
+When given the
+.Fl d
+option,
+.Nm
+enables full wlroots logging, including debug information.
+.Pp
+When given the
+.Fl s
+option,
+.Nm
+starts a shell process running
+.Ar command
+when starting.
+When stopping, it sends
+.Dv SIGTERM
+to the child process group and waits for it to exit.
+.Pp
+Users are encouraged to customize
+.Nm
+by editing the sources, in particular
+.Pa config.h .
+The default key bindings are as follows:
+.Bl -tag -width 20n -offset indent -compact
+.It Mod-[1-9]
+Show only all windows with a tag.
+.It Mod-Ctrl-[1-9]
+Show all windows with a tag.
+.It Mod-Shift-[1-9]
+Move window to a single tag.
+.It Mod-Ctrl-Shift-[1-9]
+Toggle tag for window.
+.It Mod-p
+Spawn
+.Xr wmenu-run 1 .
+.It Mod-Shift-Return
+Spawn
+.Xr foot 1 .
+.It Mod-[jk]
+Move focus down/up the stack.
+.It Mod-[id]
+Increase/decrease number of windows in master area.
+.It Mod-[hl]
+Decrease/increase master area.
+.It Mod-Return
+Move window on top of stack or switch top of stack with second window.
+.It Mod-Tab
+Show only all windows with previous tag.
+.It Mod-Shift-c
+Close window.
+.It Mod-t
+Switch to tabbed layout.
+.It Mod-f
+Switch to floating layout.
+.It Mod-m
+Switch to monocle layout.
+.It Mod-Space
+Switch to previous layout.
+.It Mod-Shift-Space
+Toggle floating state of window.
+.It Mod-e
+Toggle fullscreen state of window.
+.It Mod-0
+Show all windows.
+.It Mod-Shift-0
+Set all tags for window.
+.It Mod-,
+Move focus to previous monitor.
+.It Mod-.
+Move focus to next monitor.
+.It Mod-Shift-,
+Move window to previous monitor.
+.It Mod-Shift-.
+Move window to next monitor.
+.It Mod-Shift-q
+Quit
+.Nm .
+.El
+These might differ depending on your keyboard layout.
+.Ss Mouse commands
+.Bl -tag -width 20n -offset indent -compact
+.It Mod-Button1
+Move focused window while dragging.
+Tiled windows will be toggled to the floating state.
+.It Mod-Button2
+Toggle focused window between floating and tiled state.
+.It Mod-Button3
+Resize focused window while dragging.
+Tiled windows will be toggled to the floating state.
+.El
+.Sh STATUS INFORMATION
+.Nm
+writes its status information to standard output.
+If the
+.Fl s
+option is given, the status information is written to the standard input of the
+child process instead.
+.Pp
+Said information has the following format:
+.Bd -ragged -offset indent
+.Ar <monitor>
+.Ar <component>
+.Ar <data>
+.Ed
+.Pp
+.Bl -tag -width 11n -offset 0 -compact
+.It Ar <monitor>
+is the name given to the output.
+.It Ar <component>
+is one of (in order)
+.Em title ,
+.Em appid ,
+.Em fullscreen ,
+.Em floating ,
+.Em selmon ,
+.Em tags ,
+.Em layout .
+.It Ar <data>
+changes depending on
+.Ar <component> .
+.Bl -tag -width 10n -compact
+.It Em title
+The title of the focused window on
+.Ar <monitor>
+or nothing if there is no focused window.
+.It Em appid
+The app_id of the focused window on
+.Ar <monitor>
+or nothing if there is no focused window.
+.It Em fullscreen
+Prints 1 if the focused window on
+.Ar <monitor>
+is in fullscreen state, otherwise prints 0. If there is no focused
+window it prints nothing.
+.It Em floating
+Prints 1 if the focused window on
+.Ar <monitor>
+is in floating state, otherwise prints 0. If there is no focused
+window it prints nothing.
+.It Em selmon
+Prints 1 if
+.Ar <monitor>
+is the selected monitor, otherwise prints 0.
+.It Em tags
+Prints four bitmasks in the following order:
+.Bl -bullet -width 2n -compact
+.It
+Occupied tags of
+.Ar <monitor> .
+.It
+Selected tags of
+.Ar <monitor> .
+.It
+Tags of the focused window on
+.Ar <monitor> .
+.It
+Tags where a window on
+.Ar <monitor>
+requested activation or has urgency hints.
+.El
+The bitmasks are 32-bit unsigned decimal integers.
+.It Em layout
+Prints the symbol of the current layout.
+.El
+.El
+.Ss Examples
+When there is a selected window:
+.Bd -literal -offset indent
+HDMI\-A\-1 title \(ti/source/repos/dwl > man \-l dwl.1
+HDMI\-A\-1 appid footclient
+HDMI\-A\-1 fullscreen 0
+HDMI\-A\-1 floating 0
+HDMI\-A\-1 selmon 1
+HDMI\-A\-1 tags 271 4 4 0
+HDMI\-A\-1 layout [T]
+.Ed
+.Pp
+When there is no selected window:
+.Bd -literal -offset indent
+HDMI\-A\-1 title
+HDMI\-A\-1 appid
+HDMI\-A\-1 fullscreen
+HDMI\-A\-1 floating
+HDMI\-A\-1 selmon 1
+HDMI\-A\-1 tags 271 512 0 0
+HDMI\-A\-1 layout [T]
+.Ed
+.Sh ENVIRONMENT
+These environment variables are used by
+.Nm :
+.Bl -tag -width XDG_RUNTIME_DIR
+.It Ev XDG_RUNTIME_DIR
+A directory where temporary user files, such as the Wayland socket,
+are stored.
+.It Ev XDG_CONFIG_DIR
+A directory containing configuration of various programs and
+libraries, including libxkbcommon.
+.It Ev DISPLAY , WAYLAND_DISPLAY , WAYLAND_SOCKET
+Tell how to connect to an underlying X11 or Wayland server.
+.It Ev WLR_*
+Various variables specific to wlroots.
+.It Ev XKB_* , XLOCALEDIR , XCOMPOSEFILE
+Various variables specific to libxkbcommon.
+.It Ev XCURSOR_PATH
+List of directories to search for XCursor themes in.
+.It Ev HOME
+A directory where there are always dear files there for you.
+Waiting for you to clean them up.
+.El
+.Pp
+These are set by
+.Nm :
+.Bl -tag -width WAYLAND_DISPLAY
+.It Ev WAYLAND_DISPLAY
+Tell how to connect to
+.Nm .
+.It Ev DISPLAY
+If using
+.Nm Xwayland ,
+tell how to connect to the
+.Nm Xwayland
+server.
+.El
+.Sh EXAMPLES
+Start
+.Nm
+with s6 in the background:
+.Dl dwl \-s \(aqs6\-svscan <&\-\(aq
+.Sh SEE ALSO
+.Xr dwm 1 ,
+.Xr foot 1 ,
+.Xr wmenu 1 ,
+.Xr xkeyboard-config 7
+.Sh BUGS
+All of them.
diff --git a/dwl.c b/dwl.c
new file mode 100644
index 0000000..5dd1510
--- /dev/null
+++ b/dwl.c
@@ -0,0 +1,4130 @@
+/*
+ * See LICENSE file for copyright and license details.
+ */
+#include <fcntl.h>
+#include <getopt.h>
+#include <libinput.h>
+#include <linux/input-event-codes.h>
+#include <math.h>
+#include <libdrm/drm_fourcc.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-core.h>
+#include <wlr/backend.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_alpha_modifier_v1.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_cursor_shape_v1.h>
+#include <wlr/types/wlr_data_control_v1.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_drm.h>
+#include <wlr/types/wlr_export_dmabuf_v1.h>
+#include <wlr/types/wlr_ext_data_control_v1.h>
+#include <wlr/types/wlr_fractional_scale_v1.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_idle_inhibit_v1.h>
+#include <wlr/types/wlr_idle_notify_v1.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_keyboard_group.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_linux_dmabuf_v1.h>
+#include <wlr/types/wlr_linux_drm_syncobj_v1.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output_management_v1.h>
+#include <wlr/types/wlr_output_power_management_v1.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/types/wlr_primary_selection_v1.h>
+#include <wlr/types/wlr_relative_pointer_v1.h>
+#include <wlr/types/wlr_scene.h>
+#include <wlr/types/wlr_screencopy_v1.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_session_lock_v1.h>
+#include <wlr/types/wlr_single_pixel_buffer_v1.h>
+#include <wlr/types/wlr_subcompositor.h>
+#include <wlr/types/wlr_tablet_pad.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/types/wlr_viewporter.h>
+#include <wlr/types/wlr_virtual_keyboard_v1.h>
+#include <wlr/types/wlr_virtual_pointer_v1.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_activation_v1.h>
+#include <wlr/types/wlr_xdg_decoration_v1.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/interfaces/wlr_buffer.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include <xkbcommon/xkbcommon.h>
+#ifdef XWAYLAND
+#include <wlr/xwayland.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+#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 gappih; /* horizontal gap between windows */
+ int gappiv; /* vertical gap between windows */
+ int gappoh; /* horizontal outer gaps */
+ int gappov; /* vertical outer 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;
+ int resx;
+ int resy;
+ float rate;
+ int mode;
+ int adaptive;
+} 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 createtablet(struct wlr_input_device *device);
+static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint);
+static void cursorframe(struct wl_listener *listener, void *data);
+static void cursorwarptohint(void);
+static void defaultgaps(const Arg *arg);
+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 void destroytablet(struct wl_listener *listener, void *data);
+static void destroytabletsurfacenotify(struct wl_listener *listener, void *data);
+static void destroytablettool(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 incgaps(const Arg *arg);
+static void incigaps(const Arg *arg);
+static void incihgaps(const Arg *arg);
+static void incivgaps(const Arg *arg);
+static void incogaps(const Arg *arg);
+static void incohgaps(const Arg *arg);
+static void incovgaps(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 setgaps(int oh, int ov, int ih, int iv);
+static void tag(const Arg *arg);
+static void tagmon(const Arg *arg);
+static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy);
+static void tablettoolproximity(struct wl_listener *listener, void *data);
+static void tablettoolaxis(struct wl_listener *listener, void *data);
+static void tablettoolbutton(struct wl_listener *listener, void *data);
+static void tablettooltip(struct wl_listener *listener, void *data);
+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 _movecenter(Client *c, int interact);
+static void movecenter(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 uint32_t locked_mods = 0;
+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_tablet_manager_v2 *tablet_mgr;
+static struct wlr_tablet_v2_tablet *tablet = NULL;
+static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL;
+static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL;
+static struct wlr_surface *tablet_curr_surface = NULL;
+static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify};
+
+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 int rzcorner;
+
+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 tablet_device_destroy = {.notify = destroytablet};
+static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis};
+static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton};
+static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool};
+static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity};
+static struct wl_listener tablet_tool_tip = {.notify = tablettooltip};
+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};
+
+static int enablegaps = 1; /* enables gaps, used by togglegaps */
+
+#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));
+ }
+ }
+
+ wlr_scene_node_set_enabled(&m->fullscreen_bg->node,
+ (c = focustop(m)) && c->isfullscreen);
+
+ 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 && !c->isfullscreen));
+
+ /* 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);
+
+ wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0);
+
+ /* 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);
+ if (numlock) {
+ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
+ if (mod_index != XKB_MOD_INVALID)
+ locked_mods |= (uint32_t)1 << mod_index;
+ }
+
+ if (capslock) {
+ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
+ if (mod_index != XKB_MOD_INVALID)
+ locked_mods |= (uint32_t)1 << mod_index;
+ }
+
+ if (locked_mods)
+ wlr_keyboard_notify_modifiers(&group->wlr_group->keyboard, 0, 0, locked_mods, 0);
+
+ 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;
+ struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.next, mode, link);
+ 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]);
+
+ m->gappih = gappih;
+ m->gappiv = gappiv;
+ m->gappoh = gappoh;
+ m->gappov = gappov;
+
+ wlr_output_state_init(&state);
+ /* Initialize monitor state using configured rules */
+ 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);
+
+ wlr_output_state_set_adaptive_sync_enabled(&state, r->adaptive);
+
+ if(r->mode == -1)
+ wlr_output_state_set_custom_mode(&state, r->resx, r->resy,
+ (int) (r->rate > 0 ? r->rate * 1000 : 0));
+ else if (!wl_list_empty(&wlr_output->modes)) {
+ for (int j = 0; j < r->mode; j++) {
+ mode = wl_container_of(mode->link.next, mode, link);
+ }
+ wlr_output_state_set_mode(&state, mode);
+ }
+
+ break;
+ }
+ }
+
+ /* 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)) {
+ /* Trackpad */
+ 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, trackpad_natural_scrolling);
+
+ if (libinput_device_config_accel_is_available(device)) {
+ libinput_device_config_accel_set_profile(device, trackpad_accel_profile);
+ libinput_device_config_accel_set_speed(device, trackpad_accel_speed);
+ }
+ } else {
+ /* Mouse */
+ if (libinput_device_config_scroll_has_natural_scroll(device))
+ libinput_device_config_scroll_set_natural_scroll_enabled(device, mouse_natural_scrolling);
+
+ if (libinput_device_config_accel_is_available(device)) {
+ libinput_device_config_accel_set_profile(device, mouse_accel_profile);
+ libinput_device_config_accel_set_speed(device, mouse_accel_speed);
+ }
+ }
+
+ 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);
+ }
+
+ 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
+createtablet(struct wlr_input_device *device)
+{
+ if (!tablet) {
+ struct libinput_device *device_handle = NULL;
+ if (!wlr_input_device_is_libinput(device) ||
+ !(device_handle = wlr_libinput_get_device_handle(device)))
+ return;
+
+ tablet = wlr_tablet_create(tablet_mgr, seat, device);
+ wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy);
+ if (libinput_device_config_send_events_get_modes(device_handle)) {
+ libinput_device_config_send_events_set_mode(device_handle, send_events_mode);
+ wlr_cursor_attach_input_device(cursor, device);
+ }
+ } else if (device == tablet->wlr_device) {
+ wlr_log(WLR_ERROR, "createtablet: duplicate device");
+ } else {
+ wlr_log(WLR_ERROR, "createtablet: already have one tablet");
+ }
+}
+
+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
+defaultgaps(const Arg *arg)
+{
+ setgaps(gappoh, gappov, gappih, gappiv);
+}
+
+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);
+}
+
+void
+destroytablet(struct wl_listener *listener, void *data)
+{
+ wl_list_remove(&tablet_device_destroy.link);
+ wlr_cursor_detach_input_device(cursor, tablet->wlr_device);
+ tablet = NULL;
+}
+
+void
+destroytabletsurfacenotify(struct wl_listener *listener, void *data)
+{
+ if (tablet_curr_surface)
+ wl_list_remove(&destroy_tablet_surface_listener.link);
+ tablet_curr_surface = NULL;
+}
+
+void
+destroytablettool(struct wl_listener *listener, void *data)
+{
+ destroytabletsurfacenotify(NULL, NULL);
+ tablet_tool = NULL;
+}
+
+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 || (sel->isfullscreen && !client_has_children(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
+incgaps(const Arg *arg)
+{
+ setgaps(
+ selmon->gappoh + arg->i,
+ selmon->gappov + arg->i,
+ selmon->gappih + arg->i,
+ selmon->gappiv + arg->i
+ );
+}
+
+void
+incigaps(const Arg *arg)
+{
+ setgaps(
+ selmon->gappoh,
+ selmon->gappov,
+ selmon->gappih + arg->i,
+ selmon->gappiv + arg->i
+ );
+}
+
+void
+incihgaps(const Arg *arg)
+{
+ setgaps(
+ selmon->gappoh,
+ selmon->gappov,
+ selmon->gappih + arg->i,
+ selmon->gappiv
+ );
+}
+
+void
+incivgaps(const Arg *arg)
+{
+ setgaps(
+ selmon->gappoh,
+ selmon->gappov,
+ selmon->gappih,
+ selmon->gappiv + arg->i
+ );
+}
+
+void
+incogaps(const Arg *arg)
+{
+ setgaps(
+ selmon->gappoh + arg->i,
+ selmon->gappov + arg->i,
+ selmon->gappih,
+ selmon->gappiv
+ );
+}
+
+void
+incohgaps(const Arg *arg)
+{
+ setgaps(
+ selmon->gappoh + arg->i,
+ selmon->gappov,
+ selmon->gappih,
+ selmon->gappiv
+ );
+}
+
+void
+incovgaps(const Arg *arg)
+{
+ setgaps(
+ selmon->gappoh,
+ selmon->gappov + arg->i,
+ selmon->gappih,
+ selmon->gappiv
+ );
+}
+
+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;
+ case WLR_INPUT_DEVICE_TABLET:
+ createtablet(device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, 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 || c->isfullscreen)
+ continue;
+ n++;
+ if (!monoclegaps)
+ resize(c, m->w, 0);
+ else
+ resize(c, (struct wlr_box){.x = m->w.x + gappoh, .y = m->w.y + gappov,
+ .width = m->w.width - 2 * gappoh, .height = m->w.height - 2 * gappov}, 0);
+ }
+ 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) {
+ int cdx = (int)round(cursor->x) - grabcx;
+ int cdy = (int)round(cursor->y) - grabcy;
+
+ cdx = !(rzcorner & 1) && grabc->geom.width - 2 * (int)grabc->bw - cdx < 1 ? 0 : cdx;
+ cdy = !(rzcorner & 2) && grabc->geom.height - 2 * (int)grabc->bw - cdy < 1 ? 0 : cdy;
+
+ const struct wlr_box box = {
+ .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx),
+ .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy),
+ .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx),
+ .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy)
+ };
+ resize(grabc, box, 1);
+
+ if (!lock_cursor) {
+ grabcx += cdx;
+ grabcy += cdy;
+ } else {
+ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy);
+ }
+
+ 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) || grabc->isfullscreen)
+ 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:
+ const char *cursors[] = { "nw-resize", "ne-resize", "sw-resize", "se-resize" };
+
+ rzcorner = resize_corner;
+ grabcx = (int)round(cursor->x);
+ grabcy = (int)round(cursor->y);
+
+ if (rzcorner == 4)
+ /* identify the closest corner index */
+ rzcorner = (grabcx - grabc->geom.x < grabc->geom.x + grabc->geom.width - grabcx ? 0 : 1)
+ + (grabcy - grabc->geom.y < grabc->geom.y + grabc->geom.height - grabcy ? 0 : 2);
+
+ if (warp_cursor) {
+ grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x;
+ grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height : grabc->geom.y;
+ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy);
+ }
+
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, cursors[rzcorner]);
+ 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)
+{
+ Client *p = client_get_parent(c);
+ c->isfloating = floating;
+ /* If in floating layout do not change the client's layer */
+ if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange)
+ return;
+ wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ||
+ (p && p->isfullscreen) ? LyrFS
+ : c->isfloating ? LyrFloat : LyrTile]);
+ arrange(c->mon);
+ 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->isfullscreen
+ ? LyrFS : c->isfloating ? LyrFloat : LyrTile]);
+
+ if (fullscreen) {
+ c->prev = c->geom;
+ resize(c, c->mon->m, 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
+setgaps(int oh, int ov, int ih, int iv)
+{
+ selmon->gappoh = MAX(oh, 0);
+ selmon->gappov = MAX(ov, 0);
+ selmon->gappih = MAX(ih, 0);
+ selmon->gappiv = MAX(iv, 0);
+ arrange(selmon);
+}
+
+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 */
+ c->prev.x = (m->w.width - c->prev.width) / 2 + m->m.x;
+ c->prev.y = (m->w.height - c->prev.height) / 2 + m->m.y;
+ 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);
+
+ tablet_mgr = wlr_tablet_v2_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);
+ wl_signal_add(&cursor->events.tablet_tool_proximity, &tablet_tool_proximity);
+ wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis);
+ wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button);
+ wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip);
+
+
+ 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
+tabletapplymap(double x, double y, struct wlr_input_device *dev)
+{
+ Client *p;
+ struct wlr_box geom = {0};
+ if (tabletmaptosurface && tablet_curr_surface) {
+ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL);
+ if (p) {
+ for (; client_get_parent(p); p = client_get_parent(p));
+ geom.x = p->geom.x + p->bw;
+ geom.y = p->geom.y + p->bw;
+ geom.width = p->geom.width - 2 * p->bw;
+ geom.height = p->geom.height - 2 * p->bw;
+ }
+ }
+ wlr_cursor_map_input_to_region(cursor, dev, &geom);
+ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output);
+ handlecursoractivity(); // add_cursor
+}
+
+void
+tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y,
+ double x, double y, double dx, double dy)
+{
+ struct wlr_surface *surface = NULL;
+ double sx, sy;
+
+ if (!change_x && !change_y)
+ return;
+
+ tabletapplymap(x, y, tablet->wlr_device);
+
+ // TODO: apply constraints
+ switch (tablet_tool->wlr_tool->type) {
+ case WLR_TABLET_TOOL_TYPE_LENS:
+ case WLR_TABLET_TOOL_TYPE_MOUSE:
+ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy);
+ break;
+ default:
+ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN);
+ break;
+ }
+
+ motionnotify(0, NULL, 0, 0, 0, 0);
+
+ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy);
+ if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet))
+ surface = NULL;
+
+ if (surface != tablet_curr_surface) {
+ if (tablet_curr_surface) {
+ // TODO: wait until all buttons released before leaving
+ if (tablet_tool)
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
+ if (tablet_pad)
+ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface);
+ wl_list_remove(&destroy_tablet_surface_listener.link);
+ }
+ if (surface) {
+ if (tablet_pad)
+ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface);
+ if (tablet_tool)
+ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface);
+ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener);
+ }
+ tablet_curr_surface = surface;
+ }
+
+ if (surface)
+ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy);
+}
+
+void
+tablettoolproximity(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_proximity_event *event = data;
+ struct wlr_tablet_tool *tool = event->tool;
+
+ if (!tablet_tool) {
+ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool);
+ wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &tablet_tool_destroy);
+ wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor);
+ }
+
+ switch (event->state) {
+ case WLR_TABLET_TOOL_PROXIMITY_OUT:
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
+ destroytabletsurfacenotify(NULL, NULL);
+ break;
+ case WLR_TABLET_TOOL_PROXIMITY_IN:
+ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0);
+ break;
+ }
+}
+
+double tilt_x = 0;
+double tilt_y = 0;
+
+void
+tablettoolaxis(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_axis_event *event = data;
+
+ tablettoolmotion(tablet_tool,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_X,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y,
+ event->x, event->y, event->dx, event->dy);
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)
+ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)
+ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)
+ tilt_x = event->tilt_x;
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)
+ tilt_y = event->tilt_y;
+ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y))
+ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, tilt_x, tilt_y);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION)
+ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER)
+ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL)
+ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0);
+}
+
+void
+tablettoolbutton(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_button_event *event = data;
+ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+void
+tablettooltip(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_tip_event *event = data;
+
+ if (!tablet_curr_surface) {
+ struct wlr_pointer_button_event fakeptrbtnevent = {
+ .button = BTN_LEFT,
+ .state = event->state == WLR_TABLET_TOOL_TIP_UP ?
+ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED,
+ .time_msec = event->time_msec,
+ };
+ buttonpress(NULL, (void *)&fakeptrbtnevent);
+ }
+
+ if (event->state == WLR_TABLET_TOOL_TIP_UP) {
+ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool);
+ return;
+ }
+
+ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool);
+ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool);
+}
+
+void
+tile(Monitor *m)
+{
+ unsigned int mw, my, ty, h, r, oe = enablegaps, ie = enablegaps;
+ int i, n = 0;
+ Client *c;
+
+ wl_list_for_each(c, &clients, link)
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
+ n++;
+ if (n == 0)
+ return;
+
+ if (smartgaps == n) {
+ oe = 0; // outer gaps disabled
+ }
+
+ if (n > m->nmaster)
+ mw = m->nmaster ? (int)roundf((m->w.width + m->gappiv*ie) * m->mfact) : 0;
+ else
+ mw = m->w.width - 2*m->gappov*oe + m->gappiv*ie;
+ i = 0;
+ my = ty = m->gappoh*oe;
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+ if (i < m->nmaster) {
+ r = MIN(n, m->nmaster) - i;
+ h = (m->w.height - my - m->gappoh*oe - m->gappih*ie * (r - 1)) / r;
+ resize(c, (struct wlr_box){.x = m->w.x + m->gappov*oe, .y = m->w.y + my,
+ .width = mw - m->gappiv*ie, .height = h}, 0);
+ my += c->geom.height + m->gappih*ie;
+ } else {
+ r = n - i;
+ h = (m->w.height - ty - m->gappoh*oe - m->gappih*ie * (r - 1)) / r;
+ resize(c, (struct wlr_box){.x = m->w.x + mw + m->gappov*oe, .y = m->w.y + ty,
+ .width = m->w.width - mw - 2*m->gappov*oe, .height = h}, 0);
+ ty += c->geom.height + m->gappih*ie;
+ }
+ 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 && !sel->isfullscreen)
+ setfloating(sel, !sel->isfloating);
+}
+
+void
+togglefullscreen(const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ if (sel)
+ setfullscreen(sel, !sel->isfullscreen);
+}
+
+void
+togglegaps(const Arg *arg)
+{
+ enablegaps = !enablegaps;
+ arrange(selmon);
+}
+
+void
+_movecenter(Client *c, int interact)
+{
+ struct wlr_box b;
+
+ if (!c || !c->mon) {
+ return;
+ }
+
+ if (!c->isfloating) {
+ return;
+ }
+
+ b = respect_monitor_reserved_area ? c->mon->w : c->mon->m;
+ resize(c, (struct wlr_box){
+ .x = (b.width - c->geom.width) / 2 + b.x,
+ .y = (b.height - c->geom.height) / 2 + b.y,
+ .width = c->geom.width,
+ .height = c->geom.height,
+ }, interact);
+}
+
+void
+movecenter(const Arg *arg)
+{
+ Client *c = focustop(selmon);
+ _movecenter(c, 1);
+}
+
+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);
+ /* 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. */
+ 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.desktop b/dwl.desktop
new file mode 100644
index 0000000..09b146b
--- /dev/null
+++ b/dwl.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=DWL
+Comment=DWM for Wayland
+Exec=startdwl.sh
+Type=Application
diff --git a/patches/alwayscenter.patch b/patches/alwayscenter.patch
new file mode 100644
index 0000000..962745d
--- /dev/null
+++ b/patches/alwayscenter.patch
@@ -0,0 +1,25 @@
+From 48110f0443c8e1ddcd56b6fed5da46535024919c Mon Sep 17 00:00:00 2001
+From: Guido Cella <guido@guidocella.xyz>
+Date: Tue, 13 Jan 2026 21:17:22 +0100
+Subject: [PATCH] center floating windows
+
+---
+ dwl.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/dwl.c b/dwl.c
+index 44f3ad9..72714f8 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -2414,6 +2414,8 @@ setmon(Client *c, Monitor *m, uint32_t newtags)
+ /* 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 */
++ c->prev.x = (m->w.width - c->prev.width) / 2 + m->m.x;
++ c->prev.y = (m->w.height - c->prev.height) / 2 + m->m.y;
+ setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */
+ setfloating(c, c->isfloating);
+ }
+--
+2.52.0
+
diff --git a/patches/autostart-0.8.patch b/patches/autostart-0.8.patch
new file mode 100644
index 0000000..a1ce9ac
--- /dev/null
+++ b/patches/autostart-0.8.patch
@@ -0,0 +1,141 @@
+From d2e2e61aeb25ad71c2c559994968ba64e0974503 Mon Sep 17 00:00:00 2001
+From: A Frederick Christensen <dwl@ivories.org>
+Date: Fri, 27 Feb 2026 12:23:04 -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 44f3ad9..e7b6199 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -246,6 +246,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);
+@@ -449,6 +450,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"
+
+@@ -603,6 +607,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)
+ {
+@@ -699,12 +724,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);
+@@ -1560,10 +1596,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
+@@ -2250,6 +2301,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-systray-0.7.patch b/patches/bar-systray-0.7.patch
new file mode 100644
index 0000000..4cf63e3
--- /dev/null
+++ b/patches/bar-systray-0.7.patch
@@ -0,0 +1,3023 @@
+From cf228147250f4616d150fbe5276088c5f9969bba Mon Sep 17 00:00:00 2001
+From: vetu104 <vetu104@proton.me>
+Date: Sat, 29 Mar 2025 19:22:37 +0200
+Subject: [PATCH] Add a system tray next to sewn's bar
+
+---
+ Makefile | 23 +-
+ config.def.h | 5 +
+ dbus.c | 242 +++++++++++++++
+ dbus.h | 10 +
+ dwl.c | 107 ++++++-
+ systray/helpers.c | 43 +++
+ systray/helpers.h | 12 +
+ systray/icon.c | 149 +++++++++
+ systray/icon.h | 26 ++
+ systray/item.c | 403 ++++++++++++++++++++++++
+ systray/item.h | 46 +++
+ systray/menu.c | 757 ++++++++++++++++++++++++++++++++++++++++++++++
+ systray/menu.h | 11 +
+ systray/tray.c | 237 +++++++++++++++
+ systray/tray.h | 37 +++
+ systray/watcher.c | 551 +++++++++++++++++++++++++++++++++
+ systray/watcher.h | 35 +++
+ 17 files changed, 2681 insertions(+), 13 deletions(-)
+ create mode 100644 dbus.c
+ create mode 100644 dbus.h
+ create mode 100644 systray/helpers.c
+ create mode 100644 systray/helpers.h
+ create mode 100644 systray/icon.c
+ create mode 100644 systray/icon.h
+ create mode 100644 systray/item.c
+ create mode 100644 systray/item.h
+ create mode 100644 systray/menu.c
+ create mode 100644 systray/menu.h
+ create mode 100644 systray/tray.c
+ create mode 100644 systray/tray.h
+ create mode 100644 systray/watcher.c
+ create mode 100644 systray/watcher.h
+
+diff --git a/Makefile b/Makefile
+index 9bc67db..9d50189 100644
+--- a/Makefile
++++ b/Makefile
+@@ -12,17 +12,28 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \
+ -Wfloat-conversion
+
+ # CFLAGS / LDFLAGS
+-PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS)
++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) dbus-1
+ DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS)
+ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS)
+
++TRAYOBJS = systray/watcher.o systray/tray.o systray/item.o systray/icon.o systray/menu.o systray/helpers.o
++TRAYDEPS = systray/watcher.h systray/tray.h systray/item.h systray/icon.h systray/menu.h systray/helpers.h
++
+ all: dwl
+-dwl: dwl.o util.o
+- $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@
+-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \
++dwl: dwl.o util.o dbus.o $(TRAYOBJS) $(TRAYDEPS)
++ $(CC) dwl.o util.o dbus.o $(TRAYOBJS) $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@
++dwl.o: dwl.c client.h dbus.h config.h config.mk cursor-shape-v1-protocol.h \
+ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \
+- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h
++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \
++ $(TRAYDEPS)
+ util.o: util.c util.h
++dbus.o: dbus.c dbus.h
++systray/watcher.o: systray/watcher.c $(TRAYDEPS)
++systray/tray.o: systray/tray.c $(TRAYDEPS)
++systray/item.o: systray/item.c $(TRAYDEPS)
++systray/icon.o: systray/icon.c $(TRAYDEPS)
++systray/menu.o: systray/menu.c $(TRAYDEPS)
++systray/helpers.o: systray/helpers.c $(TRAYDEPS)
+
+ # wayland-scanner is a tool which generates C headers and rigging for Wayland
+ # protocols, which are specified in XML. wlroots requires you to rig these up
+@@ -49,7 +60,7 @@ xdg-shell-protocol.h:
+ config.h:
+ cp config.def.h $@
+ clean:
+- rm -f dwl *.o *-protocol.h
++ rm -f dwl *.o *-protocol.h systray/*.o
+
+ dist: clean
+ mkdir -p dwl-$(VERSION)
+diff --git a/config.def.h b/config.def.h
+index 5d1dc2b..451643e 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -7,6 +7,8 @@
+ 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 unsigned int systrayspacing = 2; /* systray spacing */
++static const int showsystray = 1; /* 0 means no systray */
+ 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"};
+@@ -127,6 +129,7 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA
+ /* commands */
+ static const char *termcmd[] = { "foot", NULL };
+ static const char *menucmd[] = { "wmenu-run", NULL };
++static const char *dmenucmd[] = { "wmenu", NULL };
+
+ static const Key keys[] = {
+ /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */
+@@ -188,4 +191,6 @@ static const Button buttons[] = {
+ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} },
+ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} },
+ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} },
++ { ClkTray, 0, BTN_LEFT, trayactivate, {0} },
++ { ClkTray, 0, BTN_RIGHT, traymenu, {0} },
+ };
+diff --git a/dbus.c b/dbus.c
+new file mode 100644
+index 0000000..125312c
+--- /dev/null
++++ b/dbus.c
+@@ -0,0 +1,242 @@
++#include "dbus.h"
++
++#include "util.h"
++
++#include <dbus/dbus.h>
++#include <stdlib.h>
++#include <wayland-server-core.h>
++
++#include <fcntl.h>
++#include <stddef.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <unistd.h>
++
++static void
++close_pipe(void *data)
++{
++ int *pipefd = data;
++
++ close(pipefd[0]);
++ close(pipefd[1]);
++ free(pipefd);
++}
++
++static int
++dwl_dbus_dispatch(int fd, unsigned int mask, void *data)
++{
++ DBusConnection *conn = data;
++
++ int pending;
++ DBusDispatchStatus oldstatus, newstatus;
++
++ oldstatus = dbus_connection_get_dispatch_status(conn);
++ newstatus = dbus_connection_dispatch(conn);
++
++ /* Don't clear pending flag if status didn't change */
++ if (oldstatus == newstatus)
++ return 0;
++
++ if (read(fd, &pending, sizeof(int)) < 0) {
++ perror("read");
++ die("Error in dbus dispatch");
++ }
++
++ return 0;
++}
++
++static int
++dwl_dbus_watch_handle(int fd, uint32_t mask, void *data)
++{
++ DBusWatch *watch = data;
++
++ uint32_t flags = 0;
++
++ if (!dbus_watch_get_enabled(watch))
++ return 0;
++
++ if (mask & WL_EVENT_READABLE)
++ flags |= DBUS_WATCH_READABLE;
++ if (mask & WL_EVENT_WRITABLE)
++ flags |= DBUS_WATCH_WRITABLE;
++ if (mask & WL_EVENT_HANGUP)
++ flags |= DBUS_WATCH_HANGUP;
++ if (mask & WL_EVENT_ERROR)
++ flags |= DBUS_WATCH_ERROR;
++
++ dbus_watch_handle(watch, flags);
++
++ return 0;
++}
++
++static dbus_bool_t
++dwl_dbus_add_watch(DBusWatch *watch, void *data)
++{
++ struct wl_event_loop *loop = data;
++
++ int fd;
++ struct wl_event_source *watch_source;
++ uint32_t mask = 0, flags;
++
++ if (!dbus_watch_get_enabled(watch))
++ return TRUE;
++
++ flags = dbus_watch_get_flags(watch);
++ if (flags & DBUS_WATCH_READABLE)
++ mask |= WL_EVENT_READABLE;
++ if (flags & DBUS_WATCH_WRITABLE)
++ mask |= WL_EVENT_WRITABLE;
++
++ fd = dbus_watch_get_unix_fd(watch);
++ watch_source = wl_event_loop_add_fd(loop, fd, mask,
++ dwl_dbus_watch_handle, watch);
++
++ dbus_watch_set_data(watch, watch_source, NULL);
++
++ return TRUE;
++}
++
++static void
++dwl_dbus_remove_watch(DBusWatch *watch, void *data)
++{
++ struct wl_event_source *watch_source = dbus_watch_get_data(watch);
++
++ if (watch_source)
++ wl_event_source_remove(watch_source);
++}
++
++static int
++dwl_dbus_timeout_handle(void *data)
++{
++ DBusTimeout *timeout = data;
++
++ if (dbus_timeout_get_enabled(timeout))
++ dbus_timeout_handle(timeout);
++
++ return 0;
++}
++
++static dbus_bool_t
++dwl_dbus_add_timeout(DBusTimeout *timeout, void *data)
++{
++ struct wl_event_loop *loop = data;
++
++ int r, interval;
++ struct wl_event_source *timeout_source;
++
++ if (!dbus_timeout_get_enabled(timeout))
++ return TRUE;
++
++ interval = dbus_timeout_get_interval(timeout);
++
++ timeout_source =
++ wl_event_loop_add_timer(loop, dwl_dbus_timeout_handle, timeout);
++
++ r = wl_event_source_timer_update(timeout_source, interval);
++ if (r < 0) {
++ wl_event_source_remove(timeout_source);
++ return FALSE;
++ }
++
++ dbus_timeout_set_data(timeout, timeout_source, NULL);
++
++ return TRUE;
++}
++
++static void
++dwl_dbus_remove_timeout(DBusTimeout *timeout, void *data)
++{
++ struct wl_event_source *timeout_source;
++
++ timeout_source = dbus_timeout_get_data(timeout);
++
++ if (timeout_source) {
++ wl_event_source_timer_update(timeout_source, 0);
++ wl_event_source_remove(timeout_source);
++ }
++}
++
++static void
++dwl_dbus_dispatch_status(DBusConnection *conn, DBusDispatchStatus status,
++ void *data)
++{
++ int *pipefd = data;
++
++ if (status != DBUS_DISPATCH_COMPLETE) {
++ int pending = 1;
++ if (write(pipefd[1], &pending, sizeof(int)) < 0) {
++ perror("write");
++ die("Error in dispatch status");
++ }
++ }
++}
++
++struct wl_event_source *
++startbus(DBusConnection *conn, struct wl_event_loop *loop)
++{
++ int *pipefd;
++ int pending = 1, flags;
++ struct wl_event_source *bus_source = NULL;
++
++ pipefd = ecalloc(2, sizeof(int));
++
++ /*
++ * Libdbus forbids calling dbus_connection_dispatch from the
++ * DBusDispatchStatusFunction directly. Notify the event loop of
++ * updates via a self-pipe.
++ */
++ if (pipe(pipefd) < 0)
++ goto fail;
++ if (((flags = fcntl(pipefd[0], F_GETFD)) < 0) ||
++ fcntl(pipefd[0], F_SETFD, flags | FD_CLOEXEC) < 0 ||
++ ((flags = fcntl(pipefd[1], F_GETFD)) < 0) ||
++ fcntl(pipefd[1], F_SETFD, flags | FD_CLOEXEC) < 0) {
++ goto fail;
++ }
++
++ dbus_connection_set_exit_on_disconnect(conn, FALSE);
++
++ bus_source = wl_event_loop_add_fd(loop, pipefd[0], WL_EVENT_READABLE,
++ dwl_dbus_dispatch, conn);
++ if (!bus_source)
++ goto fail;
++
++ dbus_connection_set_dispatch_status_function(conn,
++ dwl_dbus_dispatch_status,
++ pipefd, close_pipe);
++ if (!dbus_connection_set_watch_functions(conn, dwl_dbus_add_watch,
++ dwl_dbus_remove_watch, NULL,
++ loop, NULL)) {
++ goto fail;
++ }
++ if (!dbus_connection_set_timeout_functions(conn, dwl_dbus_add_timeout,
++ dwl_dbus_remove_timeout,
++ NULL, loop, NULL)) {
++ goto fail;
++ }
++ if (dbus_connection_get_dispatch_status(conn) != DBUS_DISPATCH_COMPLETE)
++ if (write(pipefd[1], &pending, sizeof(int)) < 0)
++ goto fail;
++
++ return bus_source;
++
++fail:
++ if (bus_source)
++ wl_event_source_remove(bus_source);
++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL,
++ NULL);
++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL);
++
++ return NULL;
++}
++
++void
++stopbus(DBusConnection *conn, struct wl_event_source *bus_source)
++{
++ wl_event_source_remove(bus_source);
++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL,
++ NULL);
++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL);
++}
+diff --git a/dbus.h b/dbus.h
+new file mode 100644
+index 0000000..b374b98
+--- /dev/null
++++ b/dbus.h
+@@ -0,0 +1,10 @@
++#ifndef DWLDBUS_H
++#define DWLDBUS_H
++
++#include <dbus/dbus.h>
++#include <wayland-server-core.h>
++
++struct wl_event_source* startbus (DBusConnection *conn, struct wl_event_loop *loop);
++void stopbus (DBusConnection *conn, struct wl_event_source *bus_source);
++
++#endif /* DWLDBUS_H */
+diff --git a/dwl.c b/dwl.c
+index ece537a..7753ef6 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -1,6 +1,7 @@
+ /*
+ * See LICENSE file for copyright and license details.
+ */
++#include <dbus/dbus.h>
+ #include <getopt.h>
+ #include <libinput.h>
+ #include <linux/input-event-codes.h>
+@@ -71,6 +72,9 @@
+
+ #include "util.h"
+ #include "drwl.h"
++#include "dbus.h"
++#include "systray/tray.h"
++#include "systray/watcher.h"
+
+ /* macros */
+ #define MAX(A, B) ((A) > (B) ? (A) : (B))
+@@ -89,7 +93,7 @@ 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 */
++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot, ClkTray }; /* clicks */
+ #ifdef XWAYLAND
+ enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar,
+ NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */
+@@ -218,6 +222,7 @@ struct Monitor {
+ int real_width, real_height; /* non-scaled */
+ float scale;
+ } b; /* bar area */
++ Tray *tray;
+ struct wlr_box w; /* window area, layout-relative */
+ struct wl_list layers[4]; /* LayerSurface.link */
+ const Layout *lt[2];
+@@ -376,6 +381,9 @@ static void togglefloating(const Arg *arg);
+ static void togglefullscreen(const Arg *arg);
+ static void toggletag(const Arg *arg);
+ static void toggleview(const Arg *arg);
++static void trayactivate(const Arg *arg);
++static void traymenu(const Arg *arg);
++static void traynotify(void *data);
+ static void unlocksession(struct wl_listener *listener, void *data);
+ static void unmaplayersurfacenotify(struct wl_listener *listener, void *data);
+ static void unmapnotify(struct wl_listener *listener, void *data);
+@@ -451,6 +459,10 @@ static Monitor *selmon;
+ static char stext[256];
+ static struct wl_event_source *status_event_source;
+
++static DBusConnection *bus_conn;
++static struct wl_event_source *bus_source;
++static Watcher watcher = {.running = 0};
++
+ static const struct wlr_buffer_impl buffer_impl = {
+ .destroy = bufdestroy,
+ .begin_data_ptr_access = bufdatabegin,
+@@ -721,8 +733,8 @@ bufrelease(struct wl_listener *listener, void *data)
+ void
+ buttonpress(struct wl_listener *listener, void *data)
+ {
+- unsigned int i = 0, x = 0;
+- double cx;
++ unsigned int i = 0, x = 0, ti = 0;
++ double cx, tx = 0;
+ unsigned int click;
+ struct wlr_pointer_button_event *event = data;
+ struct wlr_keyboard *keyboard;
+@@ -732,6 +744,7 @@ buttonpress(struct wl_listener *listener, void *data)
+ Arg arg = {0};
+ Client *c;
+ const Button *b;
++ int traywidth;
+
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+
+@@ -751,6 +764,8 @@ buttonpress(struct wl_listener *listener, void *data)
+ (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;
++ traywidth = tray_get_width(selmon->tray);
++
+ do
+ x += TEXTW(selmon, tags[i]);
+ while (cx >= x && ++i < LENGTH(tags));
+@@ -759,8 +774,16 @@ buttonpress(struct wl_listener *listener, void *data)
+ 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)) {
++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2) && cx < selmon->b.width - traywidth) {
+ click = ClkStatus;
++ } else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) {
++ unsigned int tray_n_items = watcher_get_n_items(&watcher);
++ tx = selmon->b.width - traywidth;
++ do
++ tx += tray_n_items ? (int)(traywidth / tray_n_items) : 0;
++ while (cx >= tx && ++ti < tray_n_items);
++ click = ClkTray;
++ arg.ui = ti;
+ } else
+ click = ClkTitle;
+ }
+@@ -774,7 +797,12 @@ buttonpress(struct wl_listener *listener, void *data)
+ 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);
++ if (click == ClkTagBar && b->arg.i == 0)
++ b->func(&arg);
++ else if (click == ClkTray && b->arg.i == 0)
++ b->func(&arg);
++ else
++ b->func(&b->arg);
+ return;
+ }
+ }
+@@ -840,6 +868,14 @@ cleanup(void)
+
+ destroykeyboardgroup(&kb_group->destroy, NULL);
+
++ if (watcher.running)
++ watcher_stop(&watcher);
++
++ if (showbar && showsystray) {
++ stopbus(bus_conn, bus_source);
++ dbus_connection_unref(bus_conn);
++ }
++
+ /* If it's not destroyed manually it will cause a use-after-free of wlr_seat.
+ * Destroy it until it's fixed in the wlroots side */
+ wlr_backend_destroy(backend);
+@@ -868,6 +904,9 @@ cleanupmon(struct wl_listener *listener, void *data)
+ for (i = 0; i < LENGTH(m->pool); i++)
+ wlr_buffer_drop(&m->pool[i]->base);
+
++ if (showsystray)
++ destroytray(m->tray);
++
+ drwl_setimage(m->drw, NULL);
+ drwl_destroy(m->drw);
+
+@@ -1506,6 +1545,7 @@ dirtomon(enum wlr_direction dir)
+ void
+ drawbar(Monitor *m)
+ {
++ int traywidth = 0;
+ int x, w, tw = 0;
+ int boxs = m->drw->font->height / 9;
+ int boxw = m->drw->font->height / 6 + 2;
+@@ -1518,11 +1558,13 @@ drawbar(Monitor *m)
+ if (!(buf = bufmon(m)))
+ return;
+
++ traywidth = tray_get_width(m->tray);
++
+ /* 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);
++ drwl_text(m->drw, m->b.width - (tw + traywidth), 0, tw, m->b.height, 0, stext, 0);
+ }
+
+ wl_list_for_each(c, &clients, link) {
+@@ -1548,7 +1590,7 @@ drawbar(Monitor *m)
+ 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 ((w = m->b.width - (tw + x + traywidth)) > 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);
+@@ -1560,6 +1602,15 @@ drawbar(Monitor *m)
+ }
+ }
+
++ if (traywidth > 0) {
++ pixman_image_composite32(PIXMAN_OP_SRC,
++ m->tray->image, NULL, m->drw->image,
++ 0, 0,
++ 0, 0,
++ m->b.width - traywidth, 0,
++ traywidth, m->b.height);
++ }
++
+ 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,
+@@ -1568,6 +1619,26 @@ drawbar(Monitor *m)
+ wlr_buffer_unlock(&buf->base);
+ }
+
++void
++traynotify(void *data)
++{
++ Monitor *m = data;
++
++ drawbar(m);
++}
++
++void
++trayactivate(const Arg *arg)
++{
++ tray_leftclicked(selmon->tray, arg->ui);
++}
++
++void
++traymenu(const Arg *arg)
++{
++ tray_rightclicked(selmon->tray, arg->ui, dmenucmd);
++}
++
+ void
+ drawbars(void)
+ {
+@@ -2818,6 +2889,15 @@ setup(void)
+ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy),
+ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL);
+
++ bus_conn = dbus_bus_get(DBUS_BUS_SESSION, NULL);
++ if (!bus_conn)
++ die("Failed to connect to bus");
++ bus_source = startbus(bus_conn, event_loop);
++ if (!bus_source)
++ die("Failed to start listening to bus events");
++ if (showbar && showsystray)
++ watcher_start(&watcher, bus_conn, event_loop);
++
+ /* 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 */
+@@ -3160,6 +3240,7 @@ updatebar(Monitor *m)
+ size_t i;
+ int rw, rh;
+ char fontattrs[12];
++ Tray *tray;
+
+ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh);
+ m->b.width = rw;
+@@ -3185,6 +3266,18 @@ updatebar(Monitor *m)
+ 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);
++
++ if (showsystray) {
++ if (m->tray)
++ destroytray(m->tray);
++ tray = createtray(m,
++ m->b.height, systrayspacing, colors[SchemeNorm], fonts, fontattrs,
++ &traynotify, &watcher);
++ if (!tray)
++ die("Couldn't create tray for monitor");
++ m->tray = tray;
++ wl_list_insert(&watcher.trays, &tray->link);
++ }
+ }
+
+ void
+diff --git a/systray/helpers.c b/systray/helpers.c
+new file mode 100644
+index 0000000..d1af9f8
+--- /dev/null
++++ b/systray/helpers.c
+@@ -0,0 +1,43 @@
++#include "helpers.h"
++
++#include <dbus/dbus.h>
++
++#include <errno.h>
++#include <stddef.h>
++
++// IWYU pragma: no_include "dbus/dbus-protocol.h"
++// IWYU pragma: no_include "dbus/dbus-shared.h"
++
++int
++request_property(DBusConnection *conn, const char *busname, const char *busobj,
++ const char *prop, const char *iface, PropHandler handler,
++ void *data)
++{
++ DBusMessage *msg = NULL;
++ DBusPendingCall *pending = NULL;
++ int r;
++
++ if (!(msg = dbus_message_new_method_call(busname, busobj,
++ DBUS_INTERFACE_PROPERTIES,
++ "Get")) ||
++ !dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface,
++ DBUS_TYPE_STRING, &prop,
++ DBUS_TYPE_INVALID) ||
++ !dbus_connection_send_with_reply(conn, msg, &pending, -1) ||
++ !dbus_pending_call_set_notify(pending, handler, data, NULL)) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ dbus_message_unref(msg);
++ return 0;
++
++fail:
++ if (pending) {
++ dbus_pending_call_cancel(pending);
++ dbus_pending_call_unref(pending);
++ }
++ if (msg)
++ dbus_message_unref(msg);
++ return r;
++}
+diff --git a/systray/helpers.h b/systray/helpers.h
+new file mode 100644
+index 0000000..2c592e0
+--- /dev/null
++++ b/systray/helpers.h
+@@ -0,0 +1,12 @@
++#ifndef HELPERS_H
++#define HELPERS_H
++
++#include <dbus/dbus.h>
++
++typedef void (*PropHandler)(DBusPendingCall *pcall, void *data);
++
++int request_property (DBusConnection *conn, const char *busname,
++ const char *busobj, const char *prop, const char *iface,
++ PropHandler handler, void *data);
++
++#endif /* HELPERS_H */
+diff --git a/systray/icon.c b/systray/icon.c
+new file mode 100644
+index 0000000..1b97866
+--- /dev/null
++++ b/systray/icon.c
+@@ -0,0 +1,149 @@
++#include "icon.h"
++
++#include <fcft/fcft.h>
++#include <pixman.h>
++
++#include <ctype.h>
++#include <stdint.h>
++#include <stdlib.h>
++#include <string.h>
++
++#define PREMUL_ALPHA(chan, alpha) (chan * alpha + 127) / 255
++
++/*
++ * Converts pixels from uint8_t[4] to uint32_t and
++ * straight alpha to premultiplied alpha.
++ */
++static uint32_t *
++to_pixman(const uint8_t *src, int n_pixels, size_t *pix_size)
++{
++ uint32_t *dest = NULL;
++
++ *pix_size = n_pixels * sizeof(uint32_t);
++ dest = malloc(*pix_size);
++ if (!dest)
++ return NULL;
++
++ for (int i = 0; i < n_pixels; i++) {
++ uint8_t a = src[i * 4 + 0];
++ uint8_t r = src[i * 4 + 1];
++ uint8_t g = src[i * 4 + 2];
++ uint8_t b = src[i * 4 + 3];
++
++ /*
++ * Skip premultiplying fully opaque and fully transparent
++ * pixels.
++ */
++ if (a == 0) {
++ dest[i] = 0;
++
++ } else if (a == 255) {
++ dest[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) |
++ ((uint32_t)g << 8) | ((uint32_t)b);
++
++ } else {
++ dest[i] = ((uint32_t)a << 24) |
++ ((uint32_t)PREMUL_ALPHA(r, a) << 16) |
++ ((uint32_t)PREMUL_ALPHA(g, a) << 8) |
++ ((uint32_t)PREMUL_ALPHA(b, a));
++ }
++ }
++
++ return dest;
++}
++
++Icon *
++createicon(const uint8_t *buf, int width, int height, int size)
++{
++ Icon *icon = NULL;
++
++ int n_pixels;
++ pixman_image_t *img = NULL;
++ size_t pixbuf_size;
++ uint32_t *buf_pixman = NULL;
++ uint8_t *buf_orig = NULL;
++
++ n_pixels = size / 4;
++
++ icon = calloc(1, sizeof(Icon));
++ buf_orig = malloc(size);
++ buf_pixman = to_pixman(buf, n_pixels, &pixbuf_size);
++ if (!icon || !buf_orig || !buf_pixman)
++ goto fail;
++
++ img = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height,
++ buf_pixman, width * 4);
++ if (!img)
++ goto fail;
++
++ memcpy(buf_orig, buf, size);
++
++ icon->buf_orig = buf_orig;
++ icon->buf_pixman = buf_pixman;
++ icon->img = img;
++ icon->size_orig = size;
++ icon->size_pixman = pixbuf_size;
++
++ return icon;
++
++fail:
++ free(buf_orig);
++ if (img)
++ pixman_image_unref(img);
++ free(buf_pixman);
++ free(icon);
++ return NULL;
++}
++
++void
++destroyicon(Icon *icon)
++{
++ if (icon->img)
++ pixman_image_unref(icon->img);
++ free(icon->buf_orig);
++ free(icon->buf_pixman);
++ free(icon);
++}
++
++FallbackIcon *
++createfallbackicon(const char *appname, int fgcolor, struct fcft_font *font)
++{
++ const struct fcft_glyph *glyph;
++ char initial;
++
++ if ((unsigned char)appname[0] > 127) {
++ /* first character is not ascii */
++ initial = '?';
++ } else {
++ initial = toupper(*appname);
++ }
++
++ glyph = fcft_rasterize_char_utf32(font, initial, FCFT_SUBPIXEL_DEFAULT);
++ if (!glyph)
++ return NULL;
++
++ return glyph;
++}
++
++int
++resize_image(pixman_image_t *image, int new_width, int new_height)
++{
++ int src_width = pixman_image_get_width(image);
++ int src_height = pixman_image_get_height(image);
++ pixman_transform_t transform;
++ pixman_fixed_t scale_x, scale_y;
++
++ if (src_width == new_width && src_height == new_height)
++ return 0;
++
++ scale_x = pixman_double_to_fixed((double)src_width / new_width);
++ scale_y = pixman_double_to_fixed((double)src_height / new_height);
++
++ pixman_transform_init_scale(&transform, scale_x, scale_y);
++ if (!pixman_image_set_filter(image, PIXMAN_FILTER_BEST, NULL, 0) ||
++ !pixman_image_set_transform(image, &transform)) {
++ return -1;
++ }
++
++ return 0;
++}
+diff --git a/systray/icon.h b/systray/icon.h
+new file mode 100644
+index 0000000..20f281b
+--- /dev/null
++++ b/systray/icon.h
+@@ -0,0 +1,26 @@
++#ifndef ICON_H
++#define ICON_H
++
++#include <fcft/fcft.h>
++#include <pixman.h>
++
++#include <stddef.h>
++#include <stdint.h>
++
++typedef const struct fcft_glyph FallbackIcon;
++
++typedef struct {
++ pixman_image_t *img;
++ uint32_t *buf_pixman;
++ uint8_t *buf_orig;
++ size_t size_orig;
++ size_t size_pixman;
++} Icon;
++
++Icon *createicon (const uint8_t *buf, int width, int height, int size);
++FallbackIcon *createfallbackicon (const char *appname, int fgcolor,
++ struct fcft_font *font);
++void destroyicon (Icon *icon);
++int resize_image (pixman_image_t *orig, int new_width, int new_height);
++
++#endif /* ICON_H */
+diff --git a/systray/item.c b/systray/item.c
+new file mode 100644
+index 0000000..4359a28
+--- /dev/null
++++ b/systray/item.c
+@@ -0,0 +1,403 @@
++#include "item.h"
++
++#include "helpers.h"
++#include "icon.h"
++#include "watcher.h"
++
++#include <dbus/dbus.h>
++
++#include <stdint.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++// IWYU pragma: no_include "dbus/dbus-protocol.h"
++// IWYU pragma: no_include "dbus/dbus-shared.h"
++
++#define RULEBSIZE 256
++#define MIN(A, B) ((A) < (B) ? (A) : (B))
++
++static const char *match_string =
++ "type='signal',"
++ "sender='%s',"
++ "interface='" SNI_NAME
++ "',"
++ "member='NewIcon'";
++
++static Watcher *
++item_get_watcher(const Item *item)
++{
++ if (!item)
++ return NULL;
++
++ return item->watcher;
++}
++
++static DBusConnection *
++item_get_connection(const Item *item)
++{
++ if (!item || !item->watcher)
++ return NULL;
++
++ return item->watcher->conn;
++}
++
++static const uint8_t *
++extract_image(DBusMessageIter *iter, dbus_int32_t *width, dbus_int32_t *height,
++ int *size)
++{
++ DBusMessageIter vals, bytes;
++ const uint8_t *buf;
++
++ dbus_message_iter_recurse(iter, &vals);
++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32)
++ goto fail;
++ dbus_message_iter_get_basic(&vals, width);
++
++ dbus_message_iter_next(&vals);
++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32)
++ goto fail;
++ dbus_message_iter_get_basic(&vals, height);
++
++ dbus_message_iter_next(&vals);
++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_ARRAY)
++ goto fail;
++ dbus_message_iter_recurse(&vals, &bytes);
++ if (dbus_message_iter_get_arg_type(&bytes) != DBUS_TYPE_BYTE)
++ goto fail;
++ dbus_message_iter_get_fixed_array(&bytes, &buf, size);
++ if (size == 0)
++ goto fail;
++
++ return buf;
++
++fail:
++ return NULL;
++}
++
++static int
++select_image(DBusMessageIter *iter, int target_width)
++{
++ DBusMessageIter vals;
++ dbus_int32_t cur_width;
++ int i = 0;
++
++ do {
++ dbus_message_iter_recurse(iter, &vals);
++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32)
++ return -1;
++ dbus_message_iter_get_basic(&vals, &cur_width);
++ if (cur_width >= target_width)
++ return i;
++
++ i++;
++ } while (dbus_message_iter_next(iter));
++
++ /* return last index if desired not found */
++ return --i;
++}
++
++static void
++menupath_ready_handler(DBusPendingCall *pending, void *data)
++{
++ Item *item = data;
++
++ DBusError err = DBUS_ERROR_INIT;
++ DBusMessage *reply = NULL;
++ DBusMessageIter iter, opath;
++ char *path_dup = NULL;
++ const char *path;
++
++ reply = dbus_pending_call_steal_reply(pending);
++ if (!reply)
++ goto fail;
++
++ if (dbus_set_error_from_message(&err, reply)) {
++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get menupath\n",
++ err.name, err.message);
++ goto fail;
++ }
++
++ dbus_message_iter_init(reply, &iter);
++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
++ goto fail;
++ dbus_message_iter_recurse(&iter, &opath);
++ if (dbus_message_iter_get_arg_type(&opath) != DBUS_TYPE_OBJECT_PATH)
++ goto fail;
++ dbus_message_iter_get_basic(&opath, &path);
++
++ path_dup = strdup(path);
++ if (!path_dup)
++ goto fail;
++
++ item->menu_busobj = path_dup;
++
++ dbus_message_unref(reply);
++ dbus_pending_call_unref(pending);
++ return;
++
++fail:
++ free(path_dup);
++ dbus_error_free(&err);
++ if (reply)
++ dbus_message_unref(reply);
++ if (pending)
++ dbus_pending_call_unref(pending);
++}
++
++/*
++ * Gets the Id dbus property, which is the name of the application,
++ * most of the time...
++ * The initial letter will be used as a fallback icon
++ */
++static void
++id_ready_handler(DBusPendingCall *pending, void *data)
++{
++ Item *item = data;
++
++ DBusError err = DBUS_ERROR_INIT;
++ DBusMessage *reply = NULL;
++ DBusMessageIter iter, string;
++ Watcher *watcher;
++ char *id_dup = NULL;
++ const char *id;
++
++ watcher = item_get_watcher(item);
++
++ reply = dbus_pending_call_steal_reply(pending);
++ if (!reply)
++ goto fail;
++
++ if (dbus_set_error_from_message(&err, reply)) {
++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get appid\n",
++ err.name, err.message);
++ goto fail;
++ }
++
++ dbus_message_iter_init(reply, &iter);
++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
++ goto fail;
++ dbus_message_iter_recurse(&iter, &string);
++ if (dbus_message_iter_get_arg_type(&string) != DBUS_TYPE_STRING)
++ goto fail;
++ dbus_message_iter_get_basic(&string, &id);
++
++ id_dup = strdup(id);
++ if (!id_dup)
++ goto fail;
++ item->appid = id_dup;
++
++ /* Don't trigger update if this item already has a real icon */
++ if (!item->icon)
++ watcher_update_trays(watcher);
++
++ dbus_message_unref(reply);
++ dbus_pending_call_unref(pending);
++ return;
++
++fail:
++ dbus_error_free(&err);
++ if (id_dup)
++ free(id_dup);
++ if (reply)
++ dbus_message_unref(reply);
++ if (pending)
++ dbus_pending_call_unref(pending);
++}
++
++static void
++pixmap_ready_handler(DBusPendingCall *pending, void *data)
++{
++ Item *item = data;
++
++ DBusMessage *reply = NULL;
++ DBusMessageIter iter, array, select, strct;
++ Icon *icon = NULL;
++ Watcher *watcher;
++ dbus_int32_t width, height;
++ int selected_index, size;
++ const uint8_t *buf;
++
++ watcher = item_get_watcher(item);
++
++ reply = dbus_pending_call_steal_reply(pending);
++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
++ goto fail;
++ dbus_message_iter_init(reply, &iter);
++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
++ goto fail;
++ dbus_message_iter_recurse(&iter, &array);
++ if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY)
++ goto fail;
++ dbus_message_iter_recurse(&array, &select);
++ if (dbus_message_iter_get_arg_type(&select) != DBUS_TYPE_STRUCT)
++ goto fail;
++ selected_index = select_image(&select, 22); // Get the 22*22 image
++ if (selected_index < 0)
++ goto fail;
++
++ dbus_message_iter_recurse(&array, &strct);
++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_STRUCT)
++ goto fail;
++ for (int i = 0; i < selected_index; i++)
++ dbus_message_iter_next(&strct);
++ buf = extract_image(&strct, &width, &height, &size);
++ if (!buf)
++ goto fail;
++
++ if (!item->icon) {
++ /* First icon */
++ icon = createicon(buf, width, height, size);
++ if (!icon)
++ goto fail;
++ item->icon = icon;
++ watcher_update_trays(watcher);
++
++ } else if (memcmp(item->icon->buf_orig, buf,
++ MIN(item->icon->size_orig, (size_t)size)) != 0) {
++ /* New icon */
++ destroyicon(item->icon);
++ item->icon = NULL;
++ icon = createicon(buf, width, height, size);
++ if (!icon)
++ goto fail;
++ item->icon = icon;
++ watcher_update_trays(watcher);
++
++ } else {
++ /* Icon didn't change */
++ }
++
++ dbus_message_unref(reply);
++ dbus_pending_call_unref(pending);
++ return;
++
++fail:
++ if (icon)
++ destroyicon(icon);
++ if (reply)
++ dbus_message_unref(reply);
++ if (pending)
++ dbus_pending_call_unref(pending);
++}
++
++static DBusHandlerResult
++handle_newicon(Item *item, DBusConnection *conn, DBusMessage *msg)
++{
++ const char *sender = dbus_message_get_sender(msg);
++
++ if (sender && strcmp(sender, item->busname) == 0) {
++ request_property(conn, item->busname, item->busobj,
++ "IconPixmap", SNI_IFACE, pixmap_ready_handler,
++ item);
++
++ return DBUS_HANDLER_RESULT_HANDLED;
++
++ } else {
++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++ }
++}
++
++static DBusHandlerResult
++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data)
++{
++ Item *item = data;
++
++ if (dbus_message_is_signal(msg, SNI_IFACE, "NewIcon"))
++ return handle_newicon(item, conn, msg);
++ else
++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++}
++
++Item *
++createitem(const char *busname, const char *busobj, Watcher *watcher)
++{
++ DBusConnection *conn;
++ Item *item;
++ char *busname_dup = NULL;
++ char *busobj_dup = NULL;
++ char match_rule[RULEBSIZE];
++
++ item = calloc(1, sizeof(Item));
++ busname_dup = strdup(busname);
++ busobj_dup = strdup(busobj);
++ if (!item || !busname_dup || !busobj_dup)
++ goto fail;
++
++ conn = watcher->conn;
++ item->busname = busname_dup;
++ item->busobj = busobj_dup;
++ item->watcher = watcher;
++
++ request_property(conn, busname, busobj, "IconPixmap", SNI_IFACE,
++ pixmap_ready_handler, item);
++
++ request_property(conn, busname, busobj, "Id", SNI_IFACE,
++ id_ready_handler, item);
++
++ request_property(conn, busname, busobj, "Menu", SNI_IFACE,
++ menupath_ready_handler, item);
++
++ if (snprintf(match_rule, sizeof(match_rule), match_string, busname) >=
++ RULEBSIZE) {
++ goto fail;
++ }
++
++ if (!dbus_connection_add_filter(conn, filter_bus, item, NULL))
++ goto fail;
++ dbus_bus_add_match(conn, match_rule, NULL);
++
++ return item;
++
++fail:
++ free(busname_dup);
++ free(busobj_dup);
++ return NULL;
++}
++
++void
++destroyitem(Item *item)
++{
++ DBusConnection *conn;
++ char match_rule[RULEBSIZE];
++
++ conn = item_get_connection(item);
++
++ if (snprintf(match_rule, sizeof(match_rule), match_string,
++ item->busname) < RULEBSIZE) {
++ dbus_bus_remove_match(conn, match_rule, NULL);
++ dbus_connection_remove_filter(conn, filter_bus, item);
++ }
++ if (item->icon)
++ destroyicon(item->icon);
++ free(item->menu_busobj);
++ free(item->busname);
++ free(item->busobj);
++ free(item->appid);
++ free(item);
++}
++
++void
++item_activate(Item *item)
++{
++ DBusConnection *conn;
++ DBusMessage *msg = NULL;
++ dbus_int32_t x = 0, y = 0;
++
++ conn = item_get_connection(item);
++
++ if (!(msg = dbus_message_new_method_call(item->busname, item->busobj,
++ SNI_IFACE, "Activate")) ||
++ !dbus_message_append_args(msg, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32,
++ &y, DBUS_TYPE_INVALID) ||
++ !dbus_connection_send_with_reply(conn, msg, NULL, -1)) {
++ goto fail;
++ }
++
++ dbus_message_unref(msg);
++ return;
++
++fail:
++ if (msg)
++ dbus_message_unref(msg);
++}
+diff --git a/systray/item.h b/systray/item.h
+new file mode 100644
+index 0000000..dc22e25
+--- /dev/null
++++ b/systray/item.h
+@@ -0,0 +1,46 @@
++#ifndef ITEM_H
++#define ITEM_H
++
++#include "icon.h"
++#include "watcher.h"
++
++#include <wayland-util.h>
++
++/*
++ * The FDO spec says "org.freedesktop.StatusNotifierItem"[1],
++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierItem"
++ *
++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/
++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib
++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem
++ *
++ */
++#define SNI_NAME "org.kde.StatusNotifierItem"
++#define SNI_OPATH "/StatusNotifierItem"
++#define SNI_IFACE "org.kde.StatusNotifierItem"
++
++typedef struct Item {
++ struct wl_list icons;
++ char *busname;
++ char *busobj;
++ char *menu_busobj;
++ char *appid;
++ Icon *icon;
++ FallbackIcon *fallback_icon;
++
++ Watcher *watcher;
++
++ int fgcolor;
++
++ int ready;
++
++ struct wl_list link;
++} Item;
++
++Item *createitem (const char *busname, const char *busobj, Watcher *watcher);
++void destroyitem (Item *item);
++
++void item_activate (Item *item);
++void item_show_menu (Item *item);
++
++#endif /* ITEM_H */
+diff --git a/systray/menu.c b/systray/menu.c
+new file mode 100644
+index 0000000..ff3bfb5
+--- /dev/null
++++ b/systray/menu.c
+@@ -0,0 +1,757 @@
++#include "menu.h"
++
++#include <dbus/dbus.h>
++#include <wayland-server-core.h>
++#include <wayland-util.h>
++
++#include <errno.h>
++#include <signal.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <sys/types.h>
++#include <sys/wait.h>
++#include <time.h>
++#include <unistd.h>
++
++// IWYU pragma: no_include "dbus/dbus-protocol.h"
++// IWYU pragma: no_include "dbus/dbus-shared.h"
++
++#define DBUSMENU_IFACE "com.canonical.dbusmenu"
++#define BUFSIZE 512
++#define LABEL_MAX 64
++
++typedef struct {
++ struct wl_array layout;
++ DBusConnection *conn;
++ struct wl_event_loop *loop;
++ char *busname;
++ char *busobj;
++ const char **menucmd;
++} Menu;
++
++typedef struct {
++ char label[LABEL_MAX];
++ dbus_int32_t id;
++ struct wl_array submenu;
++ int has_submenu;
++} MenuItem;
++
++typedef struct {
++ struct wl_event_loop *loop;
++ struct wl_event_source *fd_source;
++ struct wl_array *layout_node;
++ Menu *menu;
++ pid_t menu_pid;
++ int fd;
++} MenuShowContext;
++
++static int extract_menu (DBusMessageIter *av, struct wl_array *menu);
++static int real_show_menu (Menu *menu, struct wl_array *m);
++static void submenus_destroy_recursive (struct wl_array *m);
++
++static void
++menuitem_init(MenuItem *mi)
++{
++ wl_array_init(&mi->submenu);
++ mi->id = -1;
++ *mi->label = '\0';
++ mi->has_submenu = 0;
++}
++
++static void
++submenus_destroy_recursive(struct wl_array *layout_node)
++{
++ MenuItem *mi;
++
++ wl_array_for_each(mi, layout_node) {
++ if (mi->has_submenu) {
++ submenus_destroy_recursive(&mi->submenu);
++ wl_array_release(&mi->submenu);
++ }
++ }
++}
++
++static void
++menu_destroy(Menu *menu)
++{
++ submenus_destroy_recursive(&menu->layout);
++ wl_array_release(&menu->layout);
++ free(menu->busname);
++ free(menu->busobj);
++ free(menu);
++}
++
++static void
++menu_show_ctx_finalize(MenuShowContext *ctx, int error)
++{
++ if (ctx->fd_source)
++ wl_event_source_remove(ctx->fd_source);
++
++ if (ctx->fd >= 0)
++ close(ctx->fd);
++
++ if (ctx->menu_pid >= 0) {
++ if (waitpid(ctx->menu_pid, NULL, WNOHANG) == 0)
++ kill(ctx->menu_pid, SIGTERM);
++ }
++
++ if (error)
++ menu_destroy(ctx->menu);
++
++ free(ctx);
++}
++
++static void
++remove_newline(char *buf)
++{
++ size_t len;
++
++ len = strlen(buf);
++ if (len > 0 && buf[len - 1] == '\n')
++ buf[len - 1] = '\0';
++}
++
++static void
++send_clicked(const char *busname, const char *busobj, int itemid,
++ DBusConnection *conn)
++{
++ DBusMessage *msg = NULL;
++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ DBusMessageIter sub = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ const char *data = "";
++ const char *eventid = "clicked";
++ time_t timestamp;
++
++ timestamp = time(NULL);
++
++ msg = dbus_message_new_method_call(busname, busobj, DBUSMENU_IFACE,
++ "Event");
++ if (!msg)
++ goto fail;
++
++ dbus_message_iter_init_append(msg, &iter);
++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &itemid) ||
++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
++ &eventid) ||
++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
++ DBUS_TYPE_STRING_AS_STRING,
++ &sub) ||
++ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &data) ||
++ !dbus_message_iter_close_container(&iter, &sub) ||
++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32,
++ &timestamp)) {
++ goto fail;
++ }
++
++ if (!dbus_connection_send_with_reply(conn, msg, NULL, -1))
++ goto fail;
++
++ dbus_message_unref(msg);
++ return;
++
++fail:
++ dbus_message_iter_abandon_container_if_open(&iter, &sub);
++ if (msg)
++ dbus_message_unref(msg);
++}
++
++static void
++menuitem_selected(const char *label, struct wl_array *m, Menu *menu)
++{
++ MenuItem *mi;
++
++ wl_array_for_each(mi, m) {
++ if (strcmp(mi->label, label) == 0) {
++ if (mi->has_submenu) {
++ real_show_menu(menu, &mi->submenu);
++
++ } else {
++ send_clicked(menu->busname, menu->busobj,
++ mi->id, menu->conn);
++ menu_destroy(menu);
++ }
++
++ return;
++ }
++ }
++}
++
++static int
++read_pipe(int fd, uint32_t mask, void *data)
++{
++ MenuShowContext *ctx = data;
++
++ char buf[BUFSIZE];
++ ssize_t bytes_read;
++
++ bytes_read = read(fd, buf, BUFSIZE);
++ /* 0 == Got EOF, menu program closed without writing to stdout */
++ if (bytes_read <= 0)
++ goto fail;
++
++ buf[bytes_read] = '\0';
++ remove_newline(buf);
++
++ menuitem_selected(buf, ctx->layout_node, ctx->menu);
++ menu_show_ctx_finalize(ctx, 0);
++ return 0;
++
++fail:
++ menu_show_ctx_finalize(ctx, 1);
++ return 0;
++}
++
++static MenuShowContext *
++prepare_show_ctx(struct wl_event_loop *loop, int monitor_fd, int dmenu_pid,
++ struct wl_array *layout_node, Menu *menu)
++{
++ MenuShowContext *ctx = NULL;
++ struct wl_event_source *fd_src = NULL;
++
++ ctx = calloc(1, sizeof(MenuShowContext));
++ if (!ctx)
++ goto fail;
++
++ fd_src = wl_event_loop_add_fd(menu->loop, monitor_fd, WL_EVENT_READABLE,
++ read_pipe, ctx);
++ if (!fd_src)
++ goto fail;
++
++ ctx->fd_source = fd_src;
++ ctx->fd = monitor_fd;
++ ctx->menu_pid = dmenu_pid;
++ ctx->layout_node = layout_node;
++ ctx->menu = menu;
++
++ return ctx;
++
++fail:
++ if (fd_src)
++ wl_event_source_remove(fd_src);
++ free(ctx);
++ return NULL;
++}
++
++static int
++write_dmenu_buf(char *buf, struct wl_array *layout_node)
++{
++ MenuItem *mi;
++ int r;
++ size_t curlen = 0;
++
++ *buf = '\0';
++
++ wl_array_for_each(mi, layout_node) {
++ curlen += strlen(mi->label) +
++ 2; /* +2 is newline + nul terminator */
++ if (curlen + 1 > BUFSIZE) {
++ r = -1;
++ goto fail;
++ }
++
++ strcat(buf, mi->label);
++ strcat(buf, "\n");
++ }
++ remove_newline(buf);
++
++ return 0;
++
++fail:
++ fprintf(stderr, "Failed to construct dmenu input\n");
++ return r;
++}
++
++static int
++real_show_menu(Menu *menu, struct wl_array *layout_node)
++{
++ MenuShowContext *ctx = NULL;
++ char buf[BUFSIZE];
++ int to_pipe[2], from_pipe[2];
++ pid_t pid;
++
++ if (pipe(to_pipe) < 0 || pipe(from_pipe) < 0)
++ goto fail;
++
++ pid = fork();
++ if (pid < 0) {
++ goto fail;
++ } else if (pid == 0) {
++ dup2(to_pipe[0], STDIN_FILENO);
++ dup2(from_pipe[1], STDOUT_FILENO);
++
++ close(to_pipe[0]);
++ close(to_pipe[1]);
++ close(from_pipe[1]);
++ close(from_pipe[0]);
++
++ if (execvp(menu->menucmd[0], (char *const *)menu->menucmd)) {
++ perror("Error spawning menu program");
++ exit(EXIT_FAILURE);
++ }
++ }
++
++ ctx = prepare_show_ctx(menu->loop, from_pipe[0], pid, layout_node,
++ menu);
++ if (!ctx)
++ goto fail;
++
++ if (write_dmenu_buf(buf, layout_node) < 0 ||
++ write(to_pipe[1], buf, strlen(buf)) < 0) {
++ goto fail;
++ }
++
++ close(to_pipe[0]);
++ close(to_pipe[1]);
++ close(from_pipe[1]);
++ return 0;
++
++fail:
++ close(to_pipe[0]);
++ close(to_pipe[1]);
++ close(from_pipe[1]);
++ menu_show_ctx_finalize(ctx, 1);
++ return -1;
++}
++
++static void
++createmenuitem(MenuItem *mi, dbus_int32_t id, const char *label,
++ int toggle_state, int has_submenu)
++{
++ char *tok;
++ char temp[LABEL_MAX];
++
++ if (toggle_state == 0)
++ strcpy(mi->label, "☐ ");
++ else if (toggle_state == 1)
++ strcpy(mi->label, "✓ ");
++ else
++ strcpy(mi->label, " ");
++
++ /* Remove "mnemonics" (underscores which mark keyboard shortcuts) */
++ strcpy(temp, label);
++ tok = strtok(temp, "_");
++ do {
++ strcat(mi->label, tok);
++ } while ((tok = strtok(NULL, "_")));
++
++ if (has_submenu) {
++ mi->has_submenu = 1;
++ strcat(mi->label, " →");
++ }
++
++ mi->id = id;
++}
++
++/**
++ * Populates the passed in menuitem based on the dictionary contents.
++ *
++ * @param[in] dict
++ * @param[in] itemid
++ * @param[in] mi
++ * @param[out] has_submenu
++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped
++ */
++static int
++read_dict(DBusMessageIter *dict, dbus_int32_t itemid, MenuItem *mi,
++ int *has_submenu)
++{
++ DBusMessageIter member, val;
++ const char *children_display = NULL, *label = NULL, *toggle_type = NULL;
++ const char *key;
++ dbus_bool_t visible = TRUE, enabled = TRUE;
++ dbus_int32_t toggle_state = 1;
++ int r;
++
++ do {
++ dbus_message_iter_recurse(dict, &member);
++ if (dbus_message_iter_get_arg_type(&member) !=
++ DBUS_TYPE_STRING) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&member, &key);
++
++ dbus_message_iter_next(&member);
++ if (dbus_message_iter_get_arg_type(&member) !=
++ DBUS_TYPE_VARIANT) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_recurse(&member, &val);
++
++ if (strcmp(key, "visible") == 0) {
++ if (dbus_message_iter_get_arg_type(&val) !=
++ DBUS_TYPE_BOOLEAN) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&val, &visible);
++
++ } else if (strcmp(key, "enabled") == 0) {
++ if (dbus_message_iter_get_arg_type(&val) !=
++ DBUS_TYPE_BOOLEAN) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&val, &enabled);
++
++ } else if (strcmp(key, "toggle-type") == 0) {
++ if (dbus_message_iter_get_arg_type(&val) !=
++ DBUS_TYPE_STRING) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&val, &toggle_type);
++
++ } else if (strcmp(key, "toggle-state") == 0) {
++ if (dbus_message_iter_get_arg_type(&val) !=
++ DBUS_TYPE_INT32) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&val, &toggle_state);
++
++ } else if (strcmp(key, "children-display") == 0) {
++ if (dbus_message_iter_get_arg_type(&val) !=
++ DBUS_TYPE_STRING) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&val, &children_display);
++
++ if (strcmp(children_display, "submenu") == 0)
++ *has_submenu = 1;
++
++ } else if (strcmp(key, "label") == 0) {
++ if (dbus_message_iter_get_arg_type(&val) !=
++ DBUS_TYPE_STRING) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&val, &label);
++ }
++ } while (dbus_message_iter_next(dict));
++
++ /* Skip hidden etc items */
++ if (!label || !visible || !enabled)
++ return 1;
++
++ /*
++ * 4 characters for checkmark and submenu indicator,
++ * 1 for nul terminator
++ */
++ if (strlen(label) + 5 > LABEL_MAX) {
++ fprintf(stderr, "Too long menu entry label: %s! Skipping...\n",
++ label);
++ return 1;
++ }
++
++ if (toggle_type && strcmp(toggle_type, "checkmark") == 0)
++ createmenuitem(mi, itemid, label, toggle_state, *has_submenu);
++ else
++ createmenuitem(mi, itemid, label, -1, *has_submenu);
++
++ return 0;
++
++fail:
++ fprintf(stderr, "Error parsing menu data\n");
++ return r;
++}
++
++/**
++ * Extracts a menuitem from a DBusMessage
++ *
++ * @param[in] strct
++ * @param[in] mi
++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped
++ */
++static int
++extract_menuitem(DBusMessageIter *strct, MenuItem *mi)
++{
++ DBusMessageIter val, dict;
++ dbus_int32_t itemid;
++ int has_submenu = 0;
++ int r;
++
++ dbus_message_iter_recurse(strct, &val);
++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_INT32) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&val, &itemid);
++
++ if (!dbus_message_iter_next(&val) ||
++ dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_recurse(&val, &dict);
++ if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY) {
++ r = -1;
++ goto fail;
++ }
++
++ r = read_dict(&dict, itemid, mi, &has_submenu);
++ if (r < 0) {
++ goto fail;
++
++ } else if (r == 0 && has_submenu) {
++ dbus_message_iter_next(&val);
++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY)
++ goto fail;
++ r = extract_menu(&val, &mi->submenu);
++ if (r < 0)
++ goto fail;
++ }
++
++ return r;
++
++fail:
++ return r;
++}
++
++static int
++extract_menu(DBusMessageIter *av, struct wl_array *layout_node)
++{
++ DBusMessageIter variant, menuitem;
++ MenuItem *mi;
++ int r;
++
++ dbus_message_iter_recurse(av, &variant);
++ if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_VARIANT) {
++ r = -1;
++ goto fail;
++ }
++
++ mi = wl_array_add(layout_node, sizeof(MenuItem));
++ if (!mi) {
++ r = -ENOMEM;
++ goto fail;
++ }
++ menuitem_init(mi);
++
++ do {
++ dbus_message_iter_recurse(&variant, &menuitem);
++ if (dbus_message_iter_get_arg_type(&menuitem) !=
++ DBUS_TYPE_STRUCT) {
++ r = -1;
++ goto fail;
++ }
++
++ r = extract_menuitem(&menuitem, mi);
++ if (r < 0)
++ goto fail;
++ else if (r == 0) {
++ mi = wl_array_add(layout_node, sizeof(MenuItem));
++ if (!mi) {
++ r = -ENOMEM;
++ goto fail;
++ }
++ menuitem_init(mi);
++ }
++ /* r > 0: no action was performed on mi */
++ } while (dbus_message_iter_next(&variant));
++
++ return 0;
++
++fail:
++ return r;
++}
++
++static void
++layout_ready(DBusPendingCall *pending, void *data)
++{
++ Menu *menu = data;
++
++ DBusMessage *reply = NULL;
++ DBusMessageIter iter, strct;
++ dbus_uint32_t revision;
++ int r;
++
++ reply = dbus_pending_call_steal_reply(pending);
++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
++ r = -1;
++ goto fail;
++ }
++
++ dbus_message_iter_init(reply, &iter);
++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_get_basic(&iter, &revision);
++
++ if (!dbus_message_iter_next(&iter) ||
++ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT) {
++ r = -1;
++ goto fail;
++ }
++ dbus_message_iter_recurse(&iter, &strct);
++
++ /*
++ * id 0 is the root, which contains nothing of interest.
++ * Traverse past it.
++ */
++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_INT32 ||
++ !dbus_message_iter_next(&strct) ||
++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY ||
++ !dbus_message_iter_next(&strct) ||
++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY) {
++ r = -1;
++ goto fail;
++ }
++
++ /* Root traversed over, extract the menu */
++ wl_array_init(&menu->layout);
++ r = extract_menu(&strct, &menu->layout);
++ if (r < 0)
++ goto fail;
++
++ r = real_show_menu(menu, &menu->layout);
++ if (r < 0)
++ goto fail;
++
++ dbus_message_unref(reply);
++ dbus_pending_call_unref(pending);
++ return;
++
++fail:
++ menu_destroy(menu);
++ if (reply)
++ dbus_message_unref(reply);
++ if (pending)
++ dbus_pending_call_unref(pending);
++}
++
++static int
++request_layout(Menu *menu)
++{
++ DBusMessage *msg = NULL;
++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ DBusMessageIter strings = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ DBusPendingCall *pending = NULL;
++ dbus_int32_t parentid, depth;
++ int r;
++
++ parentid = 0;
++ depth = -1;
++
++ /* menu busobj request answer didn't arrive yet. */
++ if (!menu->busobj) {
++ r = -1;
++ goto fail;
++ }
++
++ msg = dbus_message_new_method_call(menu->busname, menu->busobj,
++ DBUSMENU_IFACE, "GetLayout");
++ if (!msg) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ dbus_message_iter_init_append(msg, &iter);
++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32,
++ &parentid) ||
++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &depth) ||
++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
++ DBUS_TYPE_STRING_AS_STRING,
++ &strings) ||
++ !dbus_message_iter_close_container(&iter, &strings)) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ if (!dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) ||
++ !dbus_pending_call_set_notify(pending, layout_ready, menu, NULL)) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ dbus_message_unref(msg);
++ return 0;
++
++fail:
++ if (pending) {
++ dbus_pending_call_cancel(pending);
++ dbus_pending_call_unref(pending);
++ }
++ dbus_message_iter_abandon_container_if_open(&iter, &strings);
++ if (msg)
++ dbus_message_unref(msg);
++ menu_destroy(menu);
++ return r;
++}
++
++static void
++about_to_show_handle(DBusPendingCall *pending, void *data)
++{
++ Menu *menu = data;
++
++ DBusMessage *reply = NULL;
++
++ reply = dbus_pending_call_steal_reply(pending);
++ if (!reply)
++ goto fail;
++
++ if (request_layout(menu) < 0)
++ goto fail;
++
++ dbus_message_unref(reply);
++ dbus_pending_call_unref(pending);
++ return;
++
++fail:
++ if (reply)
++ dbus_message_unref(reply);
++ if (pending)
++ dbus_pending_call_unref(pending);
++ menu_destroy(menu);
++}
++
++void
++menu_show(DBusConnection *conn, struct wl_event_loop *loop, const char *busname,
++ const char *busobj, const char **menucmd)
++{
++ DBusMessage *msg = NULL;
++ DBusPendingCall *pending = NULL;
++ Menu *menu = NULL;
++ char *busname_dup = NULL, *busobj_dup = NULL;
++ dbus_int32_t parentid = 0;
++
++ menu = calloc(1, sizeof(Menu));
++ busname_dup = strdup(busname);
++ busobj_dup = strdup(busobj);
++ if (!menu || !busname_dup || !busobj_dup)
++ goto fail;
++
++ menu->conn = conn;
++ menu->loop = loop;
++ menu->busname = busname_dup;
++ menu->busobj = busobj_dup;
++ menu->menucmd = menucmd;
++
++ msg = dbus_message_new_method_call(menu->busname, menu->busobj,
++ DBUSMENU_IFACE, "AboutToShow");
++ if (!msg)
++ goto fail;
++
++ if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, &parentid,
++ DBUS_TYPE_INVALID) ||
++ !dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) ||
++ !dbus_pending_call_set_notify(pending, about_to_show_handle, menu,
++ NULL)) {
++ goto fail;
++ }
++
++ dbus_message_unref(msg);
++ return;
++
++fail:
++ if (pending)
++ dbus_pending_call_unref(pending);
++ if (msg)
++ dbus_message_unref(msg);
++ free(menu);
++}
+diff --git a/systray/menu.h b/systray/menu.h
+new file mode 100644
+index 0000000..7f48ada
+--- /dev/null
++++ b/systray/menu.h
+@@ -0,0 +1,11 @@
++#ifndef MENU_H
++#define MENU_H
++
++#include <dbus/dbus.h>
++#include <wayland-server-core.h>
++
++/* The menu is built on demand and not kept around */
++void menu_show (DBusConnection *conn, struct wl_event_loop *loop,
++ const char *busname, const char *busobj, const char **menucmd);
++
++#endif /* MENU_H */
+diff --git a/systray/tray.c b/systray/tray.c
+new file mode 100644
+index 0000000..7f9b1b0
+--- /dev/null
++++ b/systray/tray.c
+@@ -0,0 +1,237 @@
++#include "tray.h"
++
++#include "icon.h"
++#include "item.h"
++#include "menu.h"
++#include "watcher.h"
++
++#include <fcft/fcft.h>
++#include <pixman.h>
++#include <wayland-util.h>
++
++#include <stddef.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#define PIXMAN_COLOR(hex) \
++ { .red = ((hex >> 24) & 0xff) * 0x101, \
++ .green = ((hex >> 16) & 0xff) * 0x101, \
++ .blue = ((hex >> 8) & 0xff) * 0x101, \
++ .alpha = (hex & 0xff) * 0x101 }
++
++static Watcher *
++tray_get_watcher(const Tray *tray)
++{
++ if (!tray)
++ return NULL;
++
++ return tray->watcher;
++}
++
++static pixman_image_t *
++createcanvas(int width, int height, int bgcolor)
++{
++ pixman_image_t *src, *dest;
++ pixman_color_t bgcolor_pix = PIXMAN_COLOR(bgcolor);
++
++ dest = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL,
++ 0);
++ src = pixman_image_create_solid_fill(&bgcolor_pix);
++
++ pixman_image_composite32(PIXMAN_OP_SRC, src, NULL, dest, 0, 0, 0, 0, 0,
++ 0, width, height);
++
++ pixman_image_unref(src);
++ return dest;
++}
++
++void
++tray_update(Tray *tray)
++{
++ Item *item;
++ Watcher *watcher;
++ int icon_size, i = 0, canvas_width, canvas_height, n_items, spacing;
++ pixman_image_t *canvas = NULL, *img;
++
++ watcher = tray_get_watcher(tray);
++ n_items = watcher_get_n_items(watcher);
++
++ if (!n_items) {
++ if (tray->image) {
++ pixman_image_unref(tray->image);
++ tray->image = NULL;
++ }
++ tray->cb(tray->monitor);
++ return;
++ }
++
++ icon_size = tray->height;
++ spacing = tray->spacing;
++ canvas_width = n_items * (icon_size + spacing) + spacing;
++ canvas_height = tray->height;
++
++ canvas = createcanvas(canvas_width, canvas_height, tray->scheme[1]);
++ if (!canvas)
++ goto fail;
++
++ wl_list_for_each(item, &watcher->items, link) {
++ int slot_x_start = spacing + i * (icon_size + spacing);
++ int slot_x_end = slot_x_start + icon_size + spacing;
++ int slot_x_width = slot_x_end - slot_x_start;
++
++ int slot_y_start = 0;
++ int slot_y_end = canvas_height;
++ int slot_y_width = slot_y_end - slot_y_start;
++
++ if (item->icon) {
++ /* Real icon */
++ img = item->icon->img;
++ if (resize_image(img, icon_size, icon_size) < 0)
++ goto fail;
++ pixman_image_composite32(PIXMAN_OP_OVER, img, NULL,
++ canvas, 0, 0, 0, 0,
++ slot_x_start, 0, canvas_width,
++ canvas_height);
++
++ } else if (item->appid) {
++ /* Font glyph alpha mask */
++ const struct fcft_glyph *g;
++ int pen_y, pen_x;
++ pixman_color_t fg_color = PIXMAN_COLOR(tray->scheme[0]);
++ pixman_image_t *fg;
++
++ if (item->fallback_icon) {
++ g = item->fallback_icon;
++ } else {
++ g = createfallbackicon(item->appid,
++ item->fgcolor,
++ tray->font);
++ if (!g)
++ goto fail;
++ item->fallback_icon = g;
++ }
++
++ pen_x = slot_x_start + (slot_x_width - g->width) / 2;
++ pen_y = slot_y_start + (slot_y_width - g->height) / 2;
++
++ fg = pixman_image_create_solid_fill(&fg_color);
++ pixman_image_composite32(PIXMAN_OP_OVER, fg, g->pix,
++ canvas, 0, 0, 0, 0, pen_x,
++ pen_y, canvas_width,
++ canvas_height);
++ pixman_image_unref(fg);
++ }
++ i++;
++ }
++
++ if (tray->image)
++ pixman_image_unref(tray->image);
++ tray->image = canvas;
++ tray->cb(tray->monitor);
++
++ return;
++
++fail:
++ if (canvas)
++ pixman_image_unref(canvas);
++ return;
++}
++
++void
++destroytray(Tray *tray)
++{
++ if (tray->image)
++ pixman_image_unref(tray->image);
++ if (tray->font)
++ fcft_destroy(tray->font);
++ free(tray);
++}
++
++Tray *
++createtray(void *monitor, int height, int spacing, uint32_t *colorscheme,
++ const char **fonts, const char *fontattrs, TrayNotifyCb cb,
++ Watcher *watcher)
++{
++ Tray *tray = NULL;
++ char fontattrs_my[128];
++ struct fcft_font *font = NULL;
++
++ sprintf(fontattrs_my, "%s:%s", fontattrs, "weight:bold");
++
++ tray = calloc(1, sizeof(Tray));
++ font = fcft_from_name(1, fonts, fontattrs_my);
++ if (!tray || !font)
++ goto fail;
++
++ tray->monitor = monitor;
++ tray->height = height;
++ tray->spacing = spacing;
++ tray->scheme = colorscheme;
++ tray->cb = cb;
++ tray->watcher = watcher;
++ tray->font = font;
++
++ return tray;
++
++fail:
++ if (font)
++ fcft_destroy(font);
++ free(tray);
++ return NULL;
++}
++
++int
++tray_get_width(const Tray *tray)
++{
++ if (tray && tray->image)
++ return pixman_image_get_width(tray->image);
++ else
++ return 0;
++}
++
++int
++tray_get_icon_width(const Tray *tray)
++{
++ if (!tray)
++ return 0;
++
++ return tray->height;
++}
++
++void
++tray_rightclicked(Tray *tray, unsigned int index, const char **menucmd)
++{
++ Item *item;
++ Watcher *watcher;
++ unsigned int count = 0;
++
++ watcher = tray_get_watcher(tray);
++
++ wl_list_for_each(item, &watcher->items, link) {
++ if (count == index) {
++ menu_show(watcher->conn, watcher->loop, item->busname,
++ item->menu_busobj, menucmd);
++ return;
++ }
++ count++;
++ }
++}
++
++void
++tray_leftclicked(Tray *tray, unsigned int index)
++{
++ Item *item;
++ Watcher *watcher;
++ unsigned int count = 0;
++
++ watcher = tray_get_watcher(tray);
++
++ wl_list_for_each(item, &watcher->items, link) {
++ if (count == index) {
++ item_activate(item);
++ return;
++ }
++ count++;
++ }
++}
+diff --git a/systray/tray.h b/systray/tray.h
+new file mode 100644
+index 0000000..af4e5e3
+--- /dev/null
++++ b/systray/tray.h
+@@ -0,0 +1,37 @@
++#ifndef TRAY_H
++#define TRAY_H
++
++#include "watcher.h"
++
++#include <pixman.h>
++#include <wayland-util.h>
++
++#include <stdint.h>
++
++typedef void (*TrayNotifyCb)(void *data);
++
++typedef struct {
++ pixman_image_t *image;
++ struct fcft_font *font;
++ uint32_t *scheme;
++ TrayNotifyCb cb;
++ Watcher *watcher;
++ void *monitor;
++ int height;
++ int spacing;
++
++ struct wl_list link;
++} Tray;
++
++Tray *createtray (void *monitor, int height, int spacing, uint32_t *colorscheme,
++ const char **fonts, const char *fontattrs, TrayNotifyCb cb,
++ Watcher *watcher);
++void destroytray (Tray *tray);
++
++int tray_get_width (const Tray *tray);
++int tray_get_icon_width (const Tray *tray);
++void tray_update (Tray *tray);
++void tray_leftclicked (Tray *tray, unsigned int index);
++void tray_rightclicked (Tray *tray, unsigned int index, const char **menucmd);
++
++#endif /* TRAY_H */
+diff --git a/systray/watcher.c b/systray/watcher.c
+new file mode 100644
+index 0000000..8dd84b9
+--- /dev/null
++++ b/systray/watcher.c
+@@ -0,0 +1,551 @@
++#include "watcher.h"
++
++#include "item.h"
++#include "tray.h"
++
++#include <dbus/dbus.h>
++#include <wayland-util.h>
++
++#include <errno.h>
++#include <stdio.h>
++#include <string.h>
++
++// IWYU pragma: no_include "dbus/dbus-protocol.h"
++// IWYU pragma: no_include "dbus/dbus-shared.h"
++
++static const char *const match_rule =
++ "type='signal',"
++ "interface='" DBUS_INTERFACE_DBUS
++ "',"
++ "member='NameOwnerChanged'";
++
++static const char *const snw_xml =
++ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
++ " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
++ "<node>\n"
++ " <interface name=\"" DBUS_INTERFACE_PROPERTIES
++ "\">\n"
++ " <method name=\"Get\">\n"
++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
++ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
++ " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n"
++ " </method>\n"
++ " <method name=\"GetAll\">\n"
++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
++ " <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n"
++ " </method>\n"
++ " <method name=\"Set\">\n"
++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
++ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
++ " <arg type=\"v\" name=\"value\" direction=\"in\"/>\n"
++ " </method>\n"
++ " <signal name=\"PropertiesChanged\">\n"
++ " <arg type=\"s\" name=\"interface_name\"/>\n"
++ " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n"
++ " <arg type=\"as\" name=\"invalidated_properties\"/>\n"
++ " </signal>\n"
++ " </interface>\n"
++ " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE
++ "\">\n"
++ " <method name=\"Introspect\">\n"
++ " <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n"
++ " </method>\n"
++ " </interface>\n"
++ " <interface name=\"" DBUS_INTERFACE_PEER
++ "\">\n"
++ " <method name=\"Ping\"/>\n"
++ " <method name=\"GetMachineId\">\n"
++ " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n"
++ " </method>\n"
++ " </interface>\n"
++ " <interface name=\"" SNW_IFACE
++ "\">\n"
++ " <!-- methods -->\n"
++ " <method name=\"RegisterStatusNotifierItem\">\n"
++ " <arg name=\"service\" type=\"s\" direction=\"in\" />\n"
++ " </method>\n"
++ " <!-- properties -->\n"
++ " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n"
++ " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n"
++ " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n"
++ " <!-- signals -->\n"
++ " <signal name=\"StatusNotifierHostRegistered\">\n"
++ " </signal>\n"
++ " </interface>\n"
++ "</node>\n";
++
++static void
++unregister_item(Watcher *watcher, Item *item)
++{
++ wl_list_remove(&item->link);
++ destroyitem(item);
++
++ watcher_update_trays(watcher);
++}
++
++static Item *
++item_name_to_ptr(const Watcher *watcher, const char *busname)
++{
++ Item *item;
++
++ wl_list_for_each(item, &watcher->items, link) {
++ if (!item || !item->busname)
++ return NULL;
++ if (strcmp(item->busname, busname) == 0)
++ return item;
++ }
++
++ return NULL;
++}
++
++static DBusHandlerResult
++handle_nameowner_changed(Watcher *watcher, DBusConnection *conn,
++ DBusMessage *msg)
++{
++ char *name, *old_owner, *new_owner;
++ Item *item;
++
++ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name,
++ DBUS_TYPE_STRING, &old_owner,
++ DBUS_TYPE_STRING, &new_owner,
++ DBUS_TYPE_INVALID)) {
++ return DBUS_HANDLER_RESULT_HANDLED;
++ }
++
++ if (*new_owner != '\0' || *name == '\0')
++ return DBUS_HANDLER_RESULT_HANDLED;
++
++ item = item_name_to_ptr(watcher, name);
++ if (!item)
++ return DBUS_HANDLER_RESULT_HANDLED;
++
++ unregister_item(watcher, item);
++
++ return DBUS_HANDLER_RESULT_HANDLED;
++}
++
++static DBusHandlerResult
++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data)
++{
++ Watcher *watcher = data;
++
++ if (dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS,
++ "NameOwnerChanged"))
++ return handle_nameowner_changed(watcher, conn, msg);
++
++ else
++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++}
++
++static DBusHandlerResult
++respond_register_item(Watcher *watcher, DBusConnection *conn, DBusMessage *msg)
++{
++ DBusHandlerResult res = DBUS_HANDLER_RESULT_HANDLED;
++
++ DBusMessage *reply = NULL;
++ Item *item;
++ const char *sender, *param, *busobj, *registree_name;
++
++ if (!(sender = dbus_message_get_sender(msg)) ||
++ !dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &param,
++ DBUS_TYPE_INVALID)) {
++ reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS,
++ "Malformed message");
++ goto send;
++ }
++
++ switch (*param) {
++ case '/':
++ registree_name = sender;
++ busobj = param;
++ break;
++ case ':':
++ registree_name = param;
++ busobj = SNI_OPATH;
++ break;
++ default:
++ reply = dbus_message_new_error_printf(msg,
++ DBUS_ERROR_INVALID_ARGS,
++ "Bad argument: \"%s\"",
++ param);
++ goto send;
++ }
++
++ if (*registree_name != ':' ||
++ !dbus_validate_bus_name(registree_name, NULL)) {
++ reply = dbus_message_new_error_printf(msg,
++ DBUS_ERROR_INVALID_ARGS,
++ "Invalid busname %s",
++ registree_name);
++ goto send;
++ }
++
++ if (item_name_to_ptr(watcher, registree_name)) {
++ reply = dbus_message_new_error_printf(msg,
++ DBUS_ERROR_INVALID_ARGS,
++ "%s already tracked",
++ registree_name);
++ goto send;
++ }
++
++ item = createitem(registree_name, busobj, watcher);
++ wl_list_insert(&watcher->items, &item->link);
++ watcher_update_trays(watcher);
++
++ reply = dbus_message_new_method_return(msg);
++
++send:
++ if (!reply || !dbus_connection_send(conn, reply, NULL))
++ res = DBUS_HANDLER_RESULT_NEED_MEMORY;
++
++ if (reply)
++ dbus_message_unref(reply);
++ return res;
++}
++
++static int
++get_registered_items(const Watcher *watcher, DBusMessageIter *iter)
++{
++ DBusMessageIter names = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ Item *item;
++ int r;
++
++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
++ DBUS_TYPE_STRING_AS_STRING,
++ &names)) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ wl_list_for_each(item, &watcher->items, link) {
++ if (!dbus_message_iter_append_basic(&names, DBUS_TYPE_STRING,
++ &item->busname)) {
++ r = -ENOMEM;
++ goto fail;
++ }
++ }
++
++ dbus_message_iter_close_container(iter, &names);
++ return 0;
++
++fail:
++ dbus_message_iter_abandon_container_if_open(iter, &names);
++ return r;
++}
++
++static int
++get_registered_items_variant(const Watcher *watcher, DBusMessageIter *iter)
++{
++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ int r;
++
++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as",
++ &variant) ||
++ get_registered_items(watcher, &variant) < 0) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ dbus_message_iter_close_container(iter, &variant);
++ return 0;
++
++fail:
++ dbus_message_iter_abandon_container_if_open(iter, &variant);
++ return r;
++}
++
++static int
++get_isregistered(DBusMessageIter *iter)
++{
++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ dbus_bool_t is_registered = TRUE;
++ int r;
++
++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
++ DBUS_TYPE_BOOLEAN_AS_STRING,
++ &variant) ||
++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN,
++ &is_registered)) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ dbus_message_iter_close_container(iter, &variant);
++ return 0;
++
++fail:
++ dbus_message_iter_abandon_container_if_open(iter, &variant);
++ return r;
++}
++
++static int
++get_version(DBusMessageIter *iter)
++{
++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ dbus_int32_t protovers = 0;
++ int r;
++
++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
++ DBUS_TYPE_INT32_AS_STRING,
++ &variant) ||
++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_INT32,
++ &protovers)) {
++ r = -ENOMEM;
++ goto fail;
++ }
++
++ dbus_message_iter_close_container(iter, &variant);
++ return 0;
++
++fail:
++ dbus_message_iter_abandon_container_if_open(iter, &variant);
++ return r;
++}
++
++static DBusHandlerResult
++respond_get_prop(Watcher *watcher, DBusConnection *conn, DBusMessage *msg)
++{
++ DBusError err = DBUS_ERROR_INIT;
++ DBusMessage *reply = NULL;
++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ const char *iface, *prop;
++
++ if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &iface,
++ DBUS_TYPE_STRING, &prop,
++ DBUS_TYPE_INVALID)) {
++ reply = dbus_message_new_error(msg, err.name, err.message);
++ dbus_error_free(&err);
++ goto send;
++ }
++
++ if (strcmp(iface, SNW_IFACE) != 0) {
++ reply = dbus_message_new_error_printf(
++ msg, DBUS_ERROR_UNKNOWN_INTERFACE,
++ "Unknown interface \"%s\"", iface);
++ goto send;
++ }
++
++ reply = dbus_message_new_method_return(msg);
++ if (!reply)
++ goto fail;
++
++ if (strcmp(prop, "ProtocolVersion") == 0) {
++ dbus_message_iter_init_append(reply, &iter);
++ if (get_version(&iter) < 0)
++ goto fail;
++
++ } else if (strcmp(prop, "IsStatusNotifierHostRegistered") == 0) {
++ dbus_message_iter_init_append(reply, &iter);
++ if (get_isregistered(&iter) < 0)
++ goto fail;
++
++ } else if (strcmp(prop, "RegisteredStatusNotifierItems") == 0) {
++ dbus_message_iter_init_append(reply, &iter);
++ if (get_registered_items_variant(watcher, &iter) < 0)
++ goto fail;
++
++ } else {
++ dbus_message_unref(reply);
++ reply = dbus_message_new_error_printf(
++ reply, DBUS_ERROR_UNKNOWN_PROPERTY,
++ "Property \"%s\" does not exist", prop);
++ }
++
++send:
++ if (!reply || !dbus_connection_send(conn, reply, NULL))
++ goto fail;
++
++ if (reply)
++ dbus_message_unref(reply);
++ return DBUS_HANDLER_RESULT_HANDLED;
++
++fail:
++ if (reply)
++ dbus_message_unref(reply);
++ return DBUS_HANDLER_RESULT_NEED_MEMORY;
++}
++
++static DBusHandlerResult
++respond_all_props(Watcher *watcher, DBusConnection *conn, DBusMessage *msg)
++{
++ DBusMessage *reply = NULL;
++ DBusMessageIter array = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ DBusMessageIter dict = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED;
++ const char *prop;
++
++ reply = dbus_message_new_method_return(msg);
++ if (!reply)
++ goto fail;
++ dbus_message_iter_init_append(reply, &iter);
++
++ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}",
++ &array))
++ goto fail;
++
++ prop = "ProtocolVersion";
++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY,
++ NULL, &dict) ||
++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) ||
++ get_version(&dict) < 0 ||
++ !dbus_message_iter_close_container(&array, &dict)) {
++ goto fail;
++ }
++
++ prop = "IsStatusNotifierHostRegistered";
++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY,
++ NULL, &dict) ||
++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) ||
++ get_isregistered(&dict) < 0 ||
++ !dbus_message_iter_close_container(&array, &dict)) {
++ goto fail;
++ }
++
++ prop = "RegisteredStatusNotifierItems";
++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY,
++ NULL, &dict) ||
++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) ||
++ get_registered_items_variant(watcher, &dict) < 0 ||
++ !dbus_message_iter_close_container(&array, &dict)) {
++ goto fail;
++ }
++
++ if (!dbus_message_iter_close_container(&iter, &array) ||
++ !dbus_connection_send(conn, reply, NULL)) {
++ goto fail;
++ }
++
++ dbus_message_unref(reply);
++ return DBUS_HANDLER_RESULT_HANDLED;
++
++fail:
++ dbus_message_iter_abandon_container_if_open(&array, &dict);
++ dbus_message_iter_abandon_container_if_open(&iter, &array);
++ if (reply)
++ dbus_message_unref(reply);
++ return DBUS_HANDLER_RESULT_NEED_MEMORY;
++}
++
++static DBusHandlerResult
++respond_introspect(DBusConnection *conn, DBusMessage *msg)
++{
++ DBusMessage *reply = NULL;
++
++ reply = dbus_message_new_method_return(msg);
++ if (!reply)
++ goto fail;
++
++ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &snw_xml,
++ DBUS_TYPE_INVALID) ||
++ !dbus_connection_send(conn, reply, NULL)) {
++ goto fail;
++ }
++
++ dbus_message_unref(reply);
++ return DBUS_HANDLER_RESULT_HANDLED;
++
++fail:
++ if (reply)
++ dbus_message_unref(reply);
++ return DBUS_HANDLER_RESULT_NEED_MEMORY;
++}
++
++static DBusHandlerResult
++snw_message_handler(DBusConnection *conn, DBusMessage *msg, void *data)
++{
++ Watcher *watcher = data;
++
++ if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE,
++ "Introspect"))
++ return respond_introspect(conn, msg);
++
++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES,
++ "GetAll"))
++ return respond_all_props(watcher, conn, msg);
++
++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES,
++ "Get"))
++ return respond_get_prop(watcher, conn, msg);
++
++ else if (dbus_message_is_method_call(msg, SNW_IFACE,
++ "RegisterStatusNotifierItem"))
++ return respond_register_item(watcher, conn, msg);
++
++ else
++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
++}
++
++static const DBusObjectPathVTable snw_vtable = { .message_function =
++ snw_message_handler };
++
++void
++watcher_start(Watcher *watcher, DBusConnection *conn,
++ struct wl_event_loop *loop)
++{
++ DBusError err = DBUS_ERROR_INIT;
++ int r, flags;
++
++ wl_list_init(&watcher->items);
++ wl_list_init(&watcher->trays);
++ watcher->conn = conn;
++ watcher->loop = loop;
++
++ flags = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE;
++ r = dbus_bus_request_name(conn, SNW_NAME,
++ flags, NULL);
++ if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
++ goto fail;
++
++ if (!dbus_connection_add_filter(conn, filter_bus, watcher, NULL)) {
++ dbus_bus_release_name(conn, SNW_NAME, NULL);
++ goto fail;
++ }
++
++ dbus_bus_add_match(conn, match_rule, &err);
++ if (dbus_error_is_set(&err)) {
++ dbus_connection_remove_filter(conn, filter_bus, watcher);
++ dbus_bus_release_name(conn, SNW_NAME, NULL);
++ goto fail;
++ }
++
++ if (!dbus_connection_register_object_path(conn, SNW_OPATH, &snw_vtable,
++ watcher)) {
++ dbus_bus_remove_match(conn, match_rule, NULL);
++ dbus_connection_remove_filter(conn, filter_bus, watcher);
++ dbus_bus_release_name(conn, SNW_NAME, NULL);
++ goto fail;
++ }
++
++ watcher->running = 1;
++ return;
++
++fail:
++ fprintf(stderr, "Couldn't start watcher, systray not available\n");
++ dbus_error_free(&err);
++ return;
++}
++
++void
++watcher_stop(Watcher *watcher)
++{
++ dbus_connection_unregister_object_path(watcher->conn, SNW_OPATH);
++ dbus_bus_remove_match(watcher->conn, match_rule, NULL);
++ dbus_connection_remove_filter(watcher->conn, filter_bus, watcher);
++ dbus_bus_release_name(watcher->conn, SNW_NAME, NULL);
++ watcher->running = 0;
++}
++
++int
++watcher_get_n_items(const Watcher *watcher)
++{
++ return wl_list_length(&watcher->items);
++}
++
++void
++watcher_update_trays(Watcher *watcher)
++{
++ Tray *tray;
++
++ wl_list_for_each(tray, &watcher->trays, link)
++ tray_update(tray);
++}
+diff --git a/systray/watcher.h b/systray/watcher.h
+new file mode 100644
+index 0000000..127eb64
+--- /dev/null
++++ b/systray/watcher.h
+@@ -0,0 +1,35 @@
++#ifndef WATCHER_H
++#define WATCHER_H
++
++#include <dbus/dbus.h>
++#include <wayland-server-core.h>
++#include <wayland-util.h>
++
++/*
++ * The FDO spec says "org.freedesktop.StatusNotifierWatcher"[1],
++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierWatcher"
++ *
++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/
++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib
++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem
++ */
++#define SNW_NAME "org.kde.StatusNotifierWatcher"
++#define SNW_OPATH "/StatusNotifierWatcher"
++#define SNW_IFACE "org.kde.StatusNotifierWatcher"
++
++typedef struct {
++ struct wl_list items;
++ struct wl_list trays;
++ struct wl_event_loop *loop;
++ DBusConnection *conn;
++ int running;
++} Watcher;
++
++void watcher_start (Watcher *watcher, DBusConnection *conn,
++ struct wl_event_loop *loop);
++void watcher_stop (Watcher *watcher);
++
++int watcher_get_n_items (const Watcher *watcher);
++void watcher_update_trays (Watcher *watcher);
++
++#endif /* WATCHER_H */
+--
+2.49.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 <sewn@disroot.org>
+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 <sewn@disroot.org>
++ * Copyright (c) 2024 notchoc <notchoc@disroot.org>
++ *
++ * 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 <bjoern@hoehrmann.de>
++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
++ */
++#pragma once
++
++#include <stdlib.h>
++#include <fcft/fcft.h>
++#include <pixman-1/pixman.h>
++
++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 <fcntl.h>
+ #include <getopt.h>
+ #include <libinput.h>
+ #include <linux/input-event-codes.h>
+ #include <math.h>
++#include <libdrm/drm_fourcc.h>
+ #include <signal.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+@@ -59,6 +61,7 @@
+ #include <wlr/types/wlr_xdg_decoration_v1.h>
+ #include <wlr/types/wlr_xdg_output_v1.h>
+ #include <wlr/types/wlr_xdg_shell.h>
++#include <wlr/interfaces/wlr_buffer.h>
+ #include <wlr/util/log.h>
+ #include <wlr/util/region.h>
+ #include <xkbcommon/xkbcommon.h>
+@@ -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/better-resize-0.7.patch b/patches/better-resize-0.7.patch
new file mode 100644
index 0000000..633f181
--- /dev/null
+++ b/patches/better-resize-0.7.patch
@@ -0,0 +1,110 @@
+From 5fab55803d009d400f6c3fcbe6a0fc807431bbe7 Mon Sep 17 00:00:00 2001
+From: mmistika <mistikasoft@gmail.com>
+Date: Thu, 17 Jul 2025 11:59:18 +0200
+Subject: [PATCH] Add configurable window resize
+
+Signed-off-by: mmistika <mistikasoft@gmail.com>
+---
+ config.def.h | 12 ++++++++++++
+ dwl.c | 48 ++++++++++++++++++++++++++++++++++++++++--------
+ 2 files changed, 52 insertions(+), 8 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 22d2171..e404549 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -20,6 +20,18 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca
+ /* logging */
+ static int log_level = WLR_ERROR;
+
++/* window resizing */
++/* resize_corner:
++ * 0: top-left
++ * 1: top-right
++ * 2: bottom-left
++ * 3: bottom-right
++ * 4: closest to the cursor
++ */
++static const int resize_corner = 4;
++static const int warp_cursor = 1; /* 1: warp to corner, 0: don’t warp */
++static const int lock_cursor = 0; /* 1: lock cursor, 0: don't lock */
++
+ /* 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 */
+diff --git a/dwl.c b/dwl.c
+index c717c1d..0d56b49 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -407,6 +407,7 @@ static KeyboardGroup *kb_group;
+ static unsigned int cursor_mode;
+ static Client *grabc;
+ static int grabcx, grabcy; /* client-relative */
++static int rzcorner;
+
+ static struct wlr_output_layout *output_layout;
+ static struct wlr_box sgeom;
+@@ -1873,8 +1874,27 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
+ .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);
++ int cdx = (int)round(cursor->x) - grabcx;
++ int cdy = (int)round(cursor->y) - grabcy;
++
++ cdx = !(rzcorner & 1) && grabc->geom.width - 2 * (int)grabc->bw - cdx < 1 ? 0 : cdx;
++ cdy = !(rzcorner & 2) && grabc->geom.height - 2 * (int)grabc->bw - cdy < 1 ? 0 : cdy;
++
++ const struct wlr_box box = {
++ .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx),
++ .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy),
++ .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx),
++ .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy)
++ };
++ resize(grabc, box, 1);
++
++ if (!lock_cursor) {
++ grabcx += cdx;
++ grabcy += cdy;
++ } else {
++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy);
++ }
++
+ return;
+ }
+
+@@ -1920,12 +1940,24 @@ moveresize(const Arg *arg)
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
+ 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");
++ const char *cursors[] = { "nw-resize", "ne-resize", "sw-resize", "se-resize" };
++
++ rzcorner = resize_corner;
++ grabcx = (int)round(cursor->x);
++ grabcy = (int)round(cursor->y);
++
++ if (rzcorner == 4)
++ /* identify the closest corner index */
++ rzcorner = (grabcx - grabc->geom.x < grabc->geom.x + grabc->geom.width - grabcx ? 0 : 1)
++ + (grabcy - grabc->geom.y < grabc->geom.y + grabc->geom.height - grabcy ? 0 : 2);
++
++ if (warp_cursor) {
++ grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x;
++ grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height : grabc->geom.y;
++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy);
++ }
++
++ wlr_cursor_set_xcursor(cursor, cursor_mgr, cursors[rzcorner]);
+ break;
+ }
+ }
+--
+2.50.1
+
diff --git a/patches/monitorconfig-0.8.patch b/patches/monitorconfig-0.8.patch
new file mode 100644
index 0000000..9c6daf2
--- /dev/null
+++ b/patches/monitorconfig-0.8.patch
@@ -0,0 +1,90 @@
+From 72137ab7f63e251f2e1c9557e236fd4e9c4efa38 Mon Sep 17 00:00:00 2001
+From: A Frederick Christensen <dwl@ivories.org>
+Date: Tue, 24 Feb 2026 23:12:17 -0600
+Subject: [PATCH] monitorconfig: update for dwl 0.8
+
+---
+ config.def.h | 12 ++++++++----
+ dwl.c | 25 +++++++++++++++++++------
+ 2 files changed, 27 insertions(+), 10 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 8a6eda0..597e3bb 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -40,10 +40,14 @@ static const Layout layouts[] = {
+ * 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 },
++ /* name mfact nmaster scale layout rotate/reflect x y resx resy rate mode adaptive */
++ /*{"eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, 0, 0, 120.000f, 1, 1}, /* example of a HiDPI laptop monitor at 120Hz: */
++ /*
++ * mode lets the user decide how dwl should implement the modes:
++ * -1 sets a custom mode following the user's choice
++ * All other numbers set the mode at the index n; 0 is the standard mode; see wlr-randr
++ */
++ { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 0.0f, 0, 1},
+ /* default monitor rule: can be changed but cannot be eliminated; at least one monitor rule must exist */
+ };
+
+diff --git a/dwl.c b/dwl.c
+index 44f3ad9..987b5a6 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -215,6 +215,11 @@ typedef struct {
+ const Layout *lt;
+ enum wl_output_transform rr;
+ int x, y;
++ int resx;
++ int resy;
++ float rate;
++ int mode;
++ int adaptive;
+ } MonitorRule;
+
+ typedef struct {
+@@ -1041,6 +1046,7 @@ 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;
++ struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.next, mode, link);
+ const MonitorRule *r;
+ size_t i;
+ struct wlr_output_state state;
+@@ -1069,16 +1075,23 @@ createmon(struct wl_listener *listener, void *data)
+ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol));
+ wlr_output_state_set_scale(&state, r->scale);
+ wlr_output_state_set_transform(&state, r->rr);
++
++ wlr_output_state_set_adaptive_sync_enabled(&state, r->adaptive);
++
++ if(r->mode == -1)
++ wlr_output_state_set_custom_mode(&state, r->resx, r->resy,
++ (int) (r->rate > 0 ? r->rate * 1000 : 0));
++ else if (!wl_list_empty(&wlr_output->modes)) {
++ for (int j = 0; j < r->mode; j++) {
++ mode = wl_container_of(mode->link.next, mode, link);
++ }
++ wlr_output_state_set_mode(&state, mode);
++ }
++
+ 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);
+--
+2.52.0
+
diff --git a/patches/mouse-trackpad-split-0.7.patch b/patches/mouse-trackpad-split-0.7.patch
new file mode 100644
index 0000000..89e33ac
--- /dev/null
+++ b/patches/mouse-trackpad-split-0.7.patch
@@ -0,0 +1,96 @@
+From 211b52988756e9fecccf071fdea82832e1e17a0c Mon Sep 17 00:00:00 2001
+From: mmistika <mistikasoft@gmail.com>
+Date: Tue, 24 Jun 2025 22:25:00 +0200
+Subject: [PATCH] Separate trackpad/mouse natural scroll and accel
+
+Signed-off-by: mmistika <mistikasoft@gmail.com>
+---
+ config.def.h | 12 +++++++++---
+ dwl.c | 26 ++++++++++++++++++--------
+ 2 files changed, 27 insertions(+), 11 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 22d2171..9d05a89 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -67,10 +67,14 @@ static const int repeat_delay = 600;
+ 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;
++
++/* Natural scrolling */
++static const int trackpad_natural_scrolling = 0;
++static const int mouse_natural_scrolling = 0;
++
+ /* You can choose between:
+ LIBINPUT_CONFIG_SCROLL_NO_SCROLL
+ LIBINPUT_CONFIG_SCROLL_2FG
+@@ -97,8 +101,10 @@ static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
+ 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;
++static const enum libinput_config_accel_profile trackpad_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
++static const double trackpad_accel_speed = 0.0;
++static const enum libinput_config_accel_profile mouse_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
++static const double mouse_accel_speed = 0.0;
+
+ /* You can choose between:
+ LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle
+diff --git a/dwl.c b/dwl.c
+index c717c1d..f05d6c5 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -1083,14 +1083,29 @@ createpointer(struct wlr_pointer *pointer)
+ && (device = wlr_libinput_get_device_handle(&pointer->base))) {
+
+ if (libinput_device_config_tap_get_finger_count(device)) {
++ /* Trackpad */
+ 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_scroll_has_natural_scroll(device))
++ libinput_device_config_scroll_set_natural_scroll_enabled(device, trackpad_natural_scrolling);
++
++ if (libinput_device_config_accel_is_available(device)) {
++ libinput_device_config_accel_set_profile(device, trackpad_accel_profile);
++ libinput_device_config_accel_set_speed(device, trackpad_accel_speed);
++ }
++ } else {
++ /* Mouse */
++ if (libinput_device_config_scroll_has_natural_scroll(device))
++ libinput_device_config_scroll_set_natural_scroll_enabled(device, mouse_natural_scrolling);
++
++ if (libinput_device_config_accel_is_available(device)) {
++ libinput_device_config_accel_set_profile(device, mouse_accel_profile);
++ libinput_device_config_accel_set_speed(device, mouse_accel_speed);
++ }
++ }
+
+ if (libinput_device_config_dwt_is_available(device))
+ libinput_device_config_dwt_set_enabled(device, disable_while_typing);
+@@ -1109,11 +1124,6 @@ createpointer(struct wlr_pointer *pointer)
+
+ 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);
+--
+2.50.0
+
diff --git a/patches/movecenter.patch b/patches/movecenter.patch
new file mode 100644
index 0000000..f96bd36
--- /dev/null
+++ b/patches/movecenter.patch
@@ -0,0 +1,82 @@
+From bc5206882c71b32198dae5f1c85601a863a7c0a9 Mon Sep 17 00:00:00 2001
+From: wochap <gean.marroquin@gmail.com>
+Date: Wed, 31 Jul 2024 07:43:10 -0500
+Subject: [PATCH] implement movecenter fn
+
+---
+ config.def.h | 2 ++
+ dwl.c | 31 +++++++++++++++++++++++++++++++
+ 2 files changed, 33 insertions(+)
+
+diff --git a/config.def.h b/config.def.h
+index 22d2171..f5225d9 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff);
+ 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 */
++static const int respect_monitor_reserved_area = 0; /* 1 to monitor center while respecting the monitor's reserved area, 0 to monitor center */
+
+ /* tagging - TAGCOUNT must be no greater than 31 */
+ #define TAGCOUNT (9)
+@@ -142,6 +143,7 @@ static const Key keys[] = {
+ { MODKEY, XKB_KEY_space, setlayout, {0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
+ { MODKEY, XKB_KEY_e, togglefullscreen, {0} },
++ { MODKEY, XKB_KEY_x, movecenter, {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} },
+diff --git a/dwl.c b/dwl.c
+index 145fd01..791e598 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -336,6 +336,8 @@ 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 _movecenter(Client *c, int interact);
++static void movecenter(const Arg *arg);
+ static void toggletag(const Arg *arg);
+ static void toggleview(const Arg *arg);
+ static void unlocksession(struct wl_listener *listener, void *data);
+@@ -2683,6 +2685,35 @@ togglefullscreen(const Arg *arg)
+ setfullscreen(sel, !sel->isfullscreen);
+ }
+
++void
++_movecenter(Client *c, int interact)
++{
++ struct wlr_box b;
++
++ if (!c || !c->mon) {
++ return;
++ }
++
++ if (!c->isfloating) {
++ return;
++ }
++
++ b = respect_monitor_reserved_area ? c->mon->w : c->mon->m;
++ resize(c, (struct wlr_box){
++ .x = (b.width - c->geom.width) / 2 + b.x,
++ .y = (b.height - c->geom.height) / 2 + b.y,
++ .width = c->geom.width,
++ .height = c->geom.height,
++ }, interact);
++}
++
++void
++movecenter(const Arg *arg)
++{
++ Client *c = focustop(selmon);
++ _movecenter(c, 1);
++}
++
+ void
+ toggletag(const Arg *arg)
+ {
+--
+2.45.2
+
diff --git a/patches/movestack-0.8.patch b/patches/movestack-0.8.patch
new file mode 100644
index 0000000..cf0c933
--- /dev/null
+++ b/patches/movestack-0.8.patch
@@ -0,0 +1,87 @@
+From 7e8de6e79d24114272b22b55c906d7b55c36173a Mon Sep 17 00:00:00 2001
+From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
+Date: Mon, 16 Mar 2026 20:50:44 +0100
+Subject: [PATCH] Add movestack
+
+---
+ config.def.h | 2 ++
+ dwl.c | 43 +++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 45 insertions(+)
+
+diff --git a/config.def.h b/config.def.h
+index 8a6eda0..f490e24 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -125,6 +125,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 44f3ad9..726fbdd 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -300,6 +300,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);
+@@ -1838,6 +1839,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.53.0
+
diff --git a/patches/numlock-capslock-0.8.patch b/patches/numlock-capslock-0.8.patch
new file mode 100644
index 0000000..8b3f53f
--- /dev/null
+++ b/patches/numlock-capslock-0.8.patch
@@ -0,0 +1,79 @@
+From ba40e5a9866b3a22a9d56c9e30068a3d776e0991 Mon Sep 17 00:00:00 2001
+From: A Frederick Christensen <dwl@ivories.org>
+Date: Wed, 25 Feb 2026 19:59:18 -0600
+Subject: [PATCH] Apply numlock-capslock patch
+
+---
+ config.def.h | 4 ++++
+ dwl.c | 19 +++++++++++++++++++
+ 2 files changed, 23 insertions(+)
+
+diff --git a/config.def.h b/config.def.h
+index 8a6eda0..074fb88 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -56,6 +56,10 @@ static const struct xkb_rule_names xkb_rules = {
+ .options = NULL,
+ };
+
++/* numlock and capslock */
++static const int numlock = 1;
++static const int capslock = 0;
++
+ static const int repeat_rate = 25;
+ static const int repeat_delay = 600;
+
+diff --git a/dwl.c b/dwl.c
+index 44f3ad9..c6ace09 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -14,6 +14,7 @@
+ #include <wayland-server-core.h>
+ #include <wlr/backend.h>
+ #include <wlr/backend/libinput.h>
++#include <wlr/interfaces/wlr_keyboard.h>
+ #include <wlr/render/allocator.h>
+ #include <wlr/render/wlr_renderer.h>
+ #include <wlr/types/wlr_alpha_modifier_v1.h>
+@@ -355,6 +356,7 @@ static void zoom(const Arg *arg);
+ /* variables */
+ static pid_t child_pid = -1;
+ static int locked;
++static uint32_t locked_mods = 0;
+ static void *exclusive_focus;
+ static struct wl_display *dpy;
+ static struct wl_event_loop *event_loop;
+@@ -943,6 +945,8 @@ createkeyboard(struct wlr_keyboard *keyboard)
+ /* Set the keymap to match the group keymap */
+ wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap);
+
++ wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0);
++
+ /* Add the new keyboard to the group */
+ wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard);
+ }
+@@ -964,6 +968,21 @@ createkeyboardgroup(void)
+ die("failed to compile keymap");
+
+ wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap);
++ if (numlock) {
++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
++ if (mod_index != XKB_MOD_INVALID)
++ locked_mods |= (uint32_t)1 << mod_index;
++ }
++
++ if (capslock) {
++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
++ if (mod_index != XKB_MOD_INVALID)
++ locked_mods |= (uint32_t)1 << mod_index;
++ }
++
++ if (locked_mods)
++ wlr_keyboard_notify_modifiers(&group->wlr_group->keyboard, 0, 0, locked_mods, 0);
++
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+
+--
+2.52.0
+
diff --git a/patches/tablet-input-0.8.patch b/patches/tablet-input-0.8.patch
new file mode 100644
index 0000000..3c64854
--- /dev/null
+++ b/patches/tablet-input-0.8.patch
@@ -0,0 +1,378 @@
+From 1069919d0252e554faddccc1f9da154e171ccdda Mon Sep 17 00:00:00 2001
+From: A Frederick Christensen <dwl@ivories.org>
+Date: Thu, 26 Feb 2026 21:56:13 -0600
+Subject: [PATCH] Apply tablet-input patch
+
+---
+ Makefile | 6 +-
+ config.def.h | 1 +
+ dwl.c | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 246 insertions(+), 1 deletion(-)
+
+diff --git a/Makefile b/Makefile
+index 578194f..e0d1835 100644
+--- a/Makefile
++++ b/Makefile
+@@ -21,7 +21,8 @@ dwl: dwl.o util.o
+ $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@
+ dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \
+ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \
+- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h
++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \
++ tablet-v2-protocol.h
+ util.o: util.c util.h
+
+ # wayland-scanner is a tool which generates C headers and rigging for Wayland
+@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h:
+ xdg-shell-protocol.h:
+ $(WAYLAND_SCANNER) server-header \
+ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
++tablet-v2-protocol.h:
++ $(WAYLAND_SCANNER) server-header \
++ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@
+
+ config.h:
+ cp config.def.h $@
+diff --git a/config.def.h b/config.def.h
+index 8a6eda0..1f20dfd 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -4,6 +4,7 @@
+ ((hex >> 8) & 0xFF) / 255.0f, \
+ (hex & 0xFF) / 255.0f }
+ /* appearance */
++static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */
+ 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 */
+diff --git a/dwl.c b/dwl.c
+index 44f3ad9..4ef2cc8 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -51,6 +51,9 @@
+ #include <wlr/types/wlr_session_lock_v1.h>
+ #include <wlr/types/wlr_single_pixel_buffer_v1.h>
+ #include <wlr/types/wlr_subcompositor.h>
++#include <wlr/types/wlr_tablet_pad.h>
++#include <wlr/types/wlr_tablet_tool.h>
++#include <wlr/types/wlr_tablet_v2.h>
+ #include <wlr/types/wlr_viewporter.h>
+ #include <wlr/types/wlr_virtual_keyboard_v1.h>
+ #include <wlr/types/wlr_virtual_pointer_v1.h>
+@@ -268,6 +271,7 @@ 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 createtablet(struct wlr_input_device *device);
+ static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint);
+ static void cursorframe(struct wl_listener *listener, void *data);
+ static void cursorwarptohint(void);
+@@ -281,6 +285,9 @@ 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 void destroytablet(struct wl_listener *listener, void *data);
++static void destroytabletsurfacenotify(struct wl_listener *listener, void *data);
++static void destroytablettool(struct wl_listener *listener, void *data);
+ static Monitor *dirtomon(enum wlr_direction dir);
+ static void focusclient(Client *c, int lift);
+ static void focusmon(const Arg *arg);
+@@ -333,6 +340,11 @@ static void spawn(const Arg *arg);
+ static void startdrag(struct wl_listener *listener, void *data);
+ static void tag(const Arg *arg);
+ static void tagmon(const Arg *arg);
++static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy);
++static void tablettoolproximity(struct wl_listener *listener, void *data);
++static void tablettoolaxis(struct wl_listener *listener, void *data);
++static void tablettoolbutton(struct wl_listener *listener, void *data);
++static void tablettooltip(struct wl_listener *listener, void *data);
+ static void tile(Monitor *m);
+ static void togglefloating(const Arg *arg);
+ static void togglefullscreen(const Arg *arg);
+@@ -390,6 +402,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint;
+ static struct wlr_cursor *cursor;
+ static struct wlr_xcursor_manager *cursor_mgr;
+
++static struct wlr_tablet_manager_v2 *tablet_mgr;
++static struct wlr_tablet_v2_tablet *tablet = NULL;
++static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL;
++static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL;
++static struct wlr_surface *tablet_curr_surface = NULL;
++static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify};
++
+ static struct wlr_scene_rect *root_bg;
+ static struct wlr_session_lock_manager_v1 *session_lock_mgr;
+ static struct wlr_scene_rect *locked_bg;
+@@ -412,6 +431,12 @@ 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 tablet_device_destroy = {.notify = destroytablet};
++static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis};
++static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton};
++static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool};
++static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity};
++static struct wl_listener tablet_tool_tip = {.notify = tablettooltip};
+ 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};
+@@ -1199,6 +1224,28 @@ createpopup(struct wl_listener *listener, void *data)
+ LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup);
+ }
+
++void
++createtablet(struct wlr_input_device *device)
++{
++ if (!tablet) {
++ struct libinput_device *device_handle = NULL;
++ if (!wlr_input_device_is_libinput(device) ||
++ !(device_handle = wlr_libinput_get_device_handle(device)))
++ return;
++
++ tablet = wlr_tablet_create(tablet_mgr, seat, device);
++ wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy);
++ if (libinput_device_config_send_events_get_modes(device_handle)) {
++ libinput_device_config_send_events_set_mode(device_handle, send_events_mode);
++ wlr_cursor_attach_input_device(cursor, device);
++ }
++ } else if (device == tablet->wlr_device) {
++ wlr_log(WLR_ERROR, "createtablet: duplicate device");
++ } else {
++ wlr_log(WLR_ERROR, "createtablet: already have one tablet");
++ }
++}
++
+ void
+ cursorconstrain(struct wlr_pointer_constraint_v1 *constraint)
+ {
+@@ -1383,6 +1430,29 @@ destroykeyboardgroup(struct wl_listener *listener, void *data)
+ free(group);
+ }
+
++void
++destroytablet(struct wl_listener *listener, void *data)
++{
++ wl_list_remove(&tablet_device_destroy.link);
++ wlr_cursor_detach_input_device(cursor, tablet->wlr_device);
++ tablet = NULL;
++}
++
++void
++destroytabletsurfacenotify(struct wl_listener *listener, void *data)
++{
++ if (tablet_curr_surface)
++ wl_list_remove(&destroy_tablet_surface_listener.link);
++ tablet_curr_surface = NULL;
++}
++
++void
++destroytablettool(struct wl_listener *listener, void *data)
++{
++ destroytabletsurfacenotify(NULL, NULL);
++ tablet_tool = NULL;
++}
++
+ Monitor *
+ dirtomon(enum wlr_direction dir)
+ {
+@@ -1590,6 +1660,12 @@ inputdevice(struct wl_listener *listener, void *data)
+ case WLR_INPUT_DEVICE_POINTER:
+ createpointer(wlr_pointer_from_input_device(device));
+ break;
++ case WLR_INPUT_DEVICE_TABLET:
++ createtablet(device);
++ break;
++ case WLR_INPUT_DEVICE_TABLET_PAD:
++ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device);
++ break;
+ default:
+ /* TODO handle other input device types */
+ break;
+@@ -2585,6 +2661,8 @@ setup(void)
+
+ relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy);
+
++ tablet_mgr = wlr_tablet_v2_create(dpy);
++
+ /*
+ * Creates a cursor, which is a wlroots utility for tracking the cursor
+ * image shown on screen.
+@@ -2614,6 +2692,11 @@ setup(void)
+ wl_signal_add(&cursor->events.button, &cursor_button);
+ wl_signal_add(&cursor->events.axis, &cursor_axis);
+ wl_signal_add(&cursor->events.frame, &cursor_frame);
++ wl_signal_add(&cursor->events.tablet_tool_proximity, &tablet_tool_proximity);
++ wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis);
++ wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button);
++ wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip);
++
+
+ 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);
+@@ -2709,6 +2792,163 @@ tagmon(const Arg *arg)
+ setmon(sel, dirtomon(arg->i), 0);
+ }
+
++void
++tabletapplymap(double x, double y, struct wlr_input_device *dev)
++{
++ Client *p;
++ struct wlr_box geom = {0};
++ if (tabletmaptosurface && tablet_curr_surface) {
++ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL);
++ if (p) {
++ for (; client_get_parent(p); p = client_get_parent(p));
++ geom.x = p->geom.x + p->bw;
++ geom.y = p->geom.y + p->bw;
++ geom.width = p->geom.width - 2 * p->bw;
++ geom.height = p->geom.height - 2 * p->bw;
++ }
++ }
++ wlr_cursor_map_input_to_region(cursor, dev, &geom);
++ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output);
++}
++
++void
++tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y,
++ double x, double y, double dx, double dy)
++{
++ struct wlr_surface *surface = NULL;
++ double sx, sy;
++
++ if (!change_x && !change_y)
++ return;
++
++ tabletapplymap(x, y, tablet->wlr_device);
++
++ // TODO: apply constraints
++ switch (tablet_tool->wlr_tool->type) {
++ case WLR_TABLET_TOOL_TYPE_LENS:
++ case WLR_TABLET_TOOL_TYPE_MOUSE:
++ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy);
++ break;
++ default:
++ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN);
++ break;
++ }
++
++ motionnotify(0, NULL, 0, 0, 0, 0);
++
++ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy);
++ if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet))
++ surface = NULL;
++
++ if (surface != tablet_curr_surface) {
++ if (tablet_curr_surface) {
++ // TODO: wait until all buttons released before leaving
++ if (tablet_tool)
++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
++ if (tablet_pad)
++ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface);
++ wl_list_remove(&destroy_tablet_surface_listener.link);
++ }
++ if (surface) {
++ if (tablet_pad)
++ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface);
++ if (tablet_tool)
++ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface);
++ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener);
++ }
++ tablet_curr_surface = surface;
++ }
++
++ if (surface)
++ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy);
++}
++
++void
++tablettoolproximity(struct wl_listener *listener, void *data)
++{
++ struct wlr_tablet_tool_proximity_event *event = data;
++ struct wlr_tablet_tool *tool = event->tool;
++
++ if (!tablet_tool) {
++ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool);
++ wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &tablet_tool_destroy);
++ wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor);
++ }
++
++ switch (event->state) {
++ case WLR_TABLET_TOOL_PROXIMITY_OUT:
++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
++ destroytabletsurfacenotify(NULL, NULL);
++ break;
++ case WLR_TABLET_TOOL_PROXIMITY_IN:
++ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0);
++ break;
++ }
++}
++
++double tilt_x = 0;
++double tilt_y = 0;
++
++void
++tablettoolaxis(struct wl_listener *listener, void *data)
++{
++ struct wlr_tablet_tool_axis_event *event = data;
++
++ tablettoolmotion(tablet_tool,
++ event->updated_axes & WLR_TABLET_TOOL_AXIS_X,
++ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y,
++ event->x, event->y, event->dx, event->dy);
++
++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)
++ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure);
++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)
++ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance);
++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)
++ tilt_x = event->tilt_x;
++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)
++ tilt_y = event->tilt_y;
++ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y))
++ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, tilt_x, tilt_y);
++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION)
++ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation);
++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER)
++ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider);
++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL)
++ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0);
++}
++
++void
++tablettoolbutton(struct wl_listener *listener, void *data)
++{
++ struct wlr_tablet_tool_button_event *event = data;
++ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button,
++ (enum zwp_tablet_pad_v2_button_state)event->state);
++}
++
++void
++tablettooltip(struct wl_listener *listener, void *data)
++{
++ struct wlr_tablet_tool_tip_event *event = data;
++
++ if (!tablet_curr_surface) {
++ struct wlr_pointer_button_event fakeptrbtnevent = {
++ .button = BTN_LEFT,
++ .state = event->state == WLR_TABLET_TOOL_TIP_UP ?
++ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED,
++ .time_msec = event->time_msec,
++ };
++ buttonpress(NULL, (void *)&fakeptrbtnevent);
++ }
++
++ if (event->state == WLR_TABLET_TOOL_TIP_UP) {
++ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool);
++ return;
++ }
++
++ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool);
++ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool);
++}
++
+ void
+ tile(Monitor *m)
+ {
+--
+2.52.0
+
diff --git a/patches/touch-input-0.8.patch b/patches/touch-input-0.8.patch
new file mode 100644
index 0000000..45fc01e
--- /dev/null
+++ b/patches/touch-input-0.8.patch
@@ -0,0 +1,259 @@
+From 44dfe863a00f4bdc646df3d761ac97711dd2107a Mon Sep 17 00:00:00 2001
+From: A Frederick Christensen <dwl@ivories.org>
+Date: Thu, 26 Feb 2026 22:12:53 -0600
+Subject: [PATCH] Apply touch-input patch
+
+---
+ dwl.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 158 insertions(+)
+
+diff --git a/dwl.c b/dwl.c
+index 44f3ad9..ccfe33c 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -51,6 +51,7 @@
+ #include <wlr/types/wlr_session_lock_v1.h>
+ #include <wlr/types/wlr_single_pixel_buffer_v1.h>
+ #include <wlr/types/wlr_subcompositor.h>
++#include <wlr/types/wlr_touch.h>
+ #include <wlr/types/wlr_viewporter.h>
+ #include <wlr/types/wlr_virtual_keyboard_v1.h>
+ #include <wlr/types/wlr_virtual_pointer_v1.h>
+@@ -161,6 +162,12 @@ typedef struct {
+ struct wl_listener destroy;
+ } KeyboardGroup;
+
++typedef struct TouchGroup {
++ struct wl_list link;
++ struct wlr_touch *touch;
++ Monitor *m;
++} TouchGroup;
++
+ typedef struct {
+ /* Must keep this field first */
+ unsigned int type; /* LayerShell */
+@@ -268,7 +275,9 @@ 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 createtouch(struct wlr_touch *touch);
+ static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint);
++static void createtouch(struct wlr_touch *touch);
+ static void cursorframe(struct wl_listener *listener, void *data);
+ static void cursorwarptohint(void);
+ static void destroydecoration(struct wl_listener *listener, void *data);
+@@ -338,6 +347,10 @@ static void togglefloating(const Arg *arg);
+ static void togglefullscreen(const Arg *arg);
+ static void toggletag(const Arg *arg);
+ static void toggleview(const Arg *arg);
++static void touchdown(struct wl_listener *listener, void *data);
++static void touchup(struct wl_listener *listener, void *data);
++static void touchframe(struct wl_listener *listener, void *data);
++static void touchmotion(struct wl_listener *listener, void *data);
+ static void unlocksession(struct wl_listener *listener, void *data);
+ static void unmaplayersurfacenotify(struct wl_listener *listener, void *data);
+ static void unmapnotify(struct wl_listener *listener, void *data);
+@@ -405,6 +418,7 @@ static struct wlr_output_layout *output_layout;
+ static struct wlr_box sgeom;
+ static struct wl_list mons;
+ static Monitor *selmon;
++static struct wl_list touches;
+
+ /* global event handlers */
+ static struct wl_listener cursor_axis = {.notify = axisnotify};
+@@ -434,6 +448,10 @@ 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 touch_down = {.notify = touchdown};
++static struct wl_listener touch_frame = {.notify = touchframe};
++static struct wl_listener touch_motion = {.notify = touchmotion};
++static struct wl_listener touch_up = {.notify = touchup};
+ static struct wl_listener new_session_lock = {.notify = locksession};
+
+ #ifdef XWAYLAND
+@@ -781,6 +799,10 @@ cleanuplisteners(void)
+ wl_list_remove(&request_set_cursor_shape.link);
+ wl_list_remove(&request_start_drag.link);
+ wl_list_remove(&start_drag.link);
++ wl_list_remove(&touch_down.link);
++ wl_list_remove(&touch_frame.link);
++ wl_list_remove(&touch_motion.link);
++ wl_list_remove(&touch_up.link);
+ wl_list_remove(&new_session_lock.link);
+ #ifdef XWAYLAND
+ wl_list_remove(&new_xwayland_surface.link);
+@@ -1199,6 +1221,16 @@ createpopup(struct wl_listener *listener, void *data)
+ LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup);
+ }
+
++void
++createtouch(struct wlr_touch *wlr_touch)
++{
++ TouchGroup *touch = ecalloc(1, sizeof(TouchGroup));
++
++ touch->touch = wlr_touch;
++ wl_list_insert(&touches, &touch->link);
++ wlr_cursor_attach_input_device(cursor, &wlr_touch->base);
++}
++
+ void
+ cursorconstrain(struct wlr_pointer_constraint_v1 *constraint)
+ {
+@@ -1590,6 +1622,9 @@ inputdevice(struct wl_listener *listener, void *data)
+ case WLR_INPUT_DEVICE_POINTER:
+ createpointer(wlr_pointer_from_input_device(device));
+ break;
++ case WLR_INPUT_DEVICE_TOUCH:
++ createtouch(wlr_touch_from_input_device(device));
++ break;
+ default:
+ /* TODO handle other input device types */
+ break;
+@@ -1602,6 +1637,8 @@ inputdevice(struct wl_listener *listener, void *data)
+ caps = WL_SEAT_CAPABILITY_POINTER;
+ if (!wl_list_empty(&kb_group->wlr_group->devices))
+ caps |= WL_SEAT_CAPABILITY_KEYBOARD;
++ if (!wl_list_empty(&touches))
++ caps |= WL_SEAT_CAPABILITY_TOUCH;
+ wlr_seat_set_capabilities(seat, caps);
+ }
+
+@@ -2615,6 +2652,13 @@ setup(void)
+ wl_signal_add(&cursor->events.axis, &cursor_axis);
+ wl_signal_add(&cursor->events.frame, &cursor_frame);
+
++ wl_list_init(&touches);
++
++ wl_signal_add(&cursor->events.touch_down, &touch_down);
++ wl_signal_add(&cursor->events.touch_frame, &touch_frame);
++ wl_signal_add(&cursor->events.touch_motion, &touch_motion);
++ wl_signal_add(&cursor->events.touch_up, &touch_up);
++
+ 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);
+
+@@ -2787,6 +2831,120 @@ toggleview(const Arg *arg)
+ printstatus();
+ }
+
++void
++touchdown(struct wl_listener *listener, void *data)
++{
++ struct wlr_touch_down_event *event = data;
++ double lx, ly;
++ double sx, sy;
++ struct wlr_surface *surface;
++ Client *c = NULL;
++ uint32_t serial = 0;
++ Monitor *m;
++
++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
++
++ // Map the input to the appropriate output, to ensure that rotation is
++ // handled.
++ wl_list_for_each(m, &mons, link) {
++ if (m == NULL || m->wlr_output == NULL) {
++ continue;
++ }
++ if (event->touch->output_name != NULL && 0 != strcmp(event->touch->output_name, m->wlr_output->name)) {
++ continue;
++ }
++
++ wlr_cursor_map_input_to_output(cursor, &event->touch->base, m->wlr_output);
++ }
++
++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly);
++
++ /* Find the client under the pointer and send the event along. */
++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy);
++ if (sloppyfocus)
++ focusclient(c, 0);
++
++ if (surface != NULL) {
++ serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy);
++ }
++
++ if (serial && wlr_seat_touch_num_points(seat) == 1) {
++ /* Emulate a mouse click if the touch event wasn't handled */
++ struct wlr_pointer_button_event *button_event = data;
++ struct wlr_pointer_motion_absolute_event *motion_event = data;
++ double dx, dy;
++
++ wlr_cursor_absolute_to_layout_coords(cursor, &motion_event->pointer->base, motion_event->x, motion_event->y, &lx, &ly);
++ wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly);
++ dx = lx - cursor->x;
++ dy = ly - cursor->y;
++ motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, dy, dx, dy);
++
++ button_event->button = BTN_LEFT;
++ button_event->state = WL_POINTER_BUTTON_STATE_PRESSED;
++ buttonpress(listener, button_event);
++ }
++}
++
++void
++touchup(struct wl_listener *listener, void *data)
++{
++ struct wlr_touch_up_event *event = data;
++
++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) {
++ return;
++ }
++
++ if (wlr_seat_touch_num_points(seat) == 1) {
++ struct wlr_pointer_button_event *button_event = data;
++
++ button_event->button = BTN_LEFT;
++ button_event->state = WL_POINTER_BUTTON_STATE_RELEASED;
++ buttonpress(listener, button_event);
++ }
++
++ wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id);
++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
++}
++
++void
++touchframe(struct wl_listener *listener, void *data)
++{
++ wlr_seat_touch_notify_frame(seat);
++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
++}
++
++void
++touchmotion(struct wl_listener *listener, void *data)
++{
++ struct wlr_touch_motion_event *event = data;
++ double lx, ly;
++ double sx, sy;
++ struct wlr_surface *surface;
++ Client *c = NULL;
++
++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) {
++ return;
++ }
++
++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly);
++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy);
++
++ if (c != NULL && surface != NULL) {
++ if (sloppyfocus)
++ focusclient(c, 0);
++ wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy);
++ } else {
++ if (sloppyfocus)
++ focusclient(NULL, 0);
++ wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id);
++ }
++ wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy);
++
++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
++}
++
++
+ void
+ unlocksession(struct wl_listener *listener, void *data)
+ {
+--
+2.52.0
+
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 <guido@guidocella.xyz>
+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/vanitygaps-0.7.patch b/patches/vanitygaps-0.7.patch
new file mode 100644
index 0000000..622b444
--- /dev/null
+++ b/patches/vanitygaps-0.7.patch
@@ -0,0 +1,358 @@
+From 3c95b58bc2b87ebd9b8481b3b16e49d99883f0a7 Mon Sep 17 00:00:00 2001
+From: Bonicgamer <44382222+Bonicgamer@users.noreply.github.com>
+Date: Mon, 17 Aug 2020 14:48:24 -0400
+Subject: [PATCH 1/2] Implement vanitygaps
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Leonardo Hernández Hernández <leohdz172@proton.me>
+---
+ config.def.h | 21 ++++++++
+ dwl.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++----
+ 2 files changed, 161 insertions(+), 10 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 22d2171d..39e528b1 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -6,7 +6,12 @@
+ /* 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 const unsigned int borderpx = 1; /* border pixel of windows */
++static const unsigned int gappih = 10; /* horiz inner gap between windows */
++static const unsigned int gappiv = 10; /* vert inner gap between windows */
++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */
++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */
+ static const float rootcolor[] = COLOR(0x222222ff);
+ static const float bordercolor[] = COLOR(0x444444ff);
+ static const float focuscolor[] = COLOR(0x005577ff);
+@@ -133,6 +138,22 @@ static const Key keys[] = {
+ { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} },
+ { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} },
+ { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} },
++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } },
++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } },
++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } },
++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } },
++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } },
++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } },
++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} },
++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} },
++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } },
++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } },
++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } },
++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } },
++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } },
++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } },
++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } },
++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } },
+ { MODKEY, XKB_KEY_Return, zoom, {0} },
+ { MODKEY, XKB_KEY_Tab, view, {0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} },
+diff --git a/dwl.c b/dwl.c
+index a2711f67..d749728a 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -200,6 +200,10 @@ struct Monitor {
+ struct wlr_box w; /* window area, layout-relative */
+ struct wl_list layers[4]; /* LayerSurface.link */
+ const Layout *lt[2];
++ int gappih; /* horizontal gap between windows */
++ int gappiv; /* vertical gap between windows */
++ int gappoh; /* horizontal outer gaps */
++ int gappov; /* vertical outer gaps */
+ unsigned int seltags;
+ unsigned int sellt;
+ uint32_t tagset[2];
+@@ -273,6 +277,7 @@ 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 defaultgaps(const Arg *arg);
+ 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);
+@@ -293,6 +298,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data);
+ static void gpureset(struct wl_listener *listener, void *data);
+ static void handlesig(int signo);
+ static void incnmaster(const Arg *arg);
++static void incgaps(const Arg *arg);
++static void incigaps(const Arg *arg);
++static void incihgaps(const Arg *arg);
++static void incivgaps(const Arg *arg);
++static void incogaps(const Arg *arg);
++static void incohgaps(const Arg *arg);
++static void incovgaps(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);
+@@ -327,6 +339,7 @@ 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 setgamma(struct wl_listener *listener, void *data);
++static void setgaps(int oh, int ov, int ih, int iv);
+ static void setlayout(const Arg *arg);
+ static void setmfact(const Arg *arg);
+ static void setmon(Client *c, Monitor *m, uint32_t newtags);
+@@ -340,6 +353,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);
+@@ -413,6 +427,8 @@ static struct wlr_box sgeom;
+ static struct wl_list mons;
+ static Monitor *selmon;
+
++static int enablegaps = 1; /* enables gaps, used by togglegaps */
++
+ #ifdef XWAYLAND
+ static void activatex11(struct wl_listener *listener, void *data);
+ static void associatex11(struct wl_listener *listener, void *data);
+@@ -989,6 +1005,11 @@ createmon(struct wl_listener *listener, void *data)
+ for (i = 0; i < LENGTH(m->layers); i++)
+ wl_list_init(&m->layers[i]);
+
++ m->gappih = gappih;
++ m->gappiv = gappiv;
++ m->gappoh = gappoh;
++ m->gappov = gappov;
++
+ wlr_output_state_init(&state);
+ /* Initialize monitor state using configured rules */
+ m->tagset[0] = m->tagset[1] = 1;
+@@ -1171,6 +1192,12 @@ cursorwarptohint(void)
+ }
+ }
+
++void
++defaultgaps(const Arg *arg)
++{
++ setgaps(gappoh, gappov, gappih, gappiv);
++}
++
+ void
+ destroydecoration(struct wl_listener *listener, void *data)
+ {
+@@ -1524,6 +1551,83 @@ incnmaster(const Arg *arg)
+ arrange(selmon);
+ }
+
++void
++incgaps(const Arg *arg)
++{
++ setgaps(
++ selmon->gappoh + arg->i,
++ selmon->gappov + arg->i,
++ selmon->gappih + arg->i,
++ selmon->gappiv + arg->i
++ );
++}
++
++void
++incigaps(const Arg *arg)
++{
++ setgaps(
++ selmon->gappoh,
++ selmon->gappov,
++ selmon->gappih + arg->i,
++ selmon->gappiv + arg->i
++ );
++}
++
++void
++incihgaps(const Arg *arg)
++{
++ setgaps(
++ selmon->gappoh,
++ selmon->gappov,
++ selmon->gappih + arg->i,
++ selmon->gappiv
++ );
++}
++
++void
++incivgaps(const Arg *arg)
++{
++ setgaps(
++ selmon->gappoh,
++ selmon->gappov,
++ selmon->gappih,
++ selmon->gappiv + arg->i
++ );
++}
++
++void
++incogaps(const Arg *arg)
++{
++ setgaps(
++ selmon->gappoh + arg->i,
++ selmon->gappov + arg->i,
++ selmon->gappih,
++ selmon->gappiv
++ );
++}
++
++void
++incohgaps(const Arg *arg)
++{
++ setgaps(
++ selmon->gappoh + arg->i,
++ selmon->gappov,
++ selmon->gappih,
++ selmon->gappiv
++ );
++}
++
++void
++incovgaps(const Arg *arg)
++{
++ setgaps(
++ selmon->gappoh,
++ selmon->gappov + arg->i,
++ selmon->gappih,
++ selmon->gappiv
++ );
++}
++
+ void
+ inputdevice(struct wl_listener *listener, void *data)
+ {
+@@ -2352,6 +2456,16 @@ setgamma(struct wl_listener *listener, void *data)
+ wlr_output_schedule_frame(m->wlr_output);
+ }
+
++void
++setgaps(int oh, int ov, int ih, int iv)
++{
++ selmon->gappoh = MAX(oh, 0);
++ selmon->gappov = MAX(ov, 0);
++ selmon->gappih = MAX(ih, 0);
++ selmon->gappiv = MAX(iv, 0);
++ arrange(selmon);
++}
++
+ void
+ setlayout(const Arg *arg)
+ {
+@@ -2691,7 +2805,7 @@ tagmon(const Arg *arg)
+ void
+ tile(Monitor *m)
+ {
+- unsigned int mw, my, ty;
++ unsigned int mw, my, ty, h, r, oe = enablegaps, ie = enablegaps;
+ int i, n = 0;
+ Client *c;
+
+@@ -2701,22 +2815,31 @@ tile(Monitor *m)
+ if (n == 0)
+ return;
+
++ if (smartgaps == n) {
++ oe = 0; // outer gaps disabled
++ }
++
+ if (n > m->nmaster)
+- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0;
++ mw = m->nmaster ? (int)roundf((m->w.width + m->gappiv*ie) * m->mfact) : 0;
+ else
+- mw = m->w.width;
+- i = my = ty = 0;
++ mw = m->w.width - 2*m->gappov*oe + m->gappiv*ie;
++ i = 0;
++ my = ty = m->gappoh*oe;
+ 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 - m->gappoh*oe - m->gappih*ie * (r - 1)) / r;
++ resize(c, (struct wlr_box){.x = m->w.x + m->gappov*oe, .y = m->w.y + my,
++ .width = mw - m->gappiv*ie, .height = h}, 0);
++ my += c->geom.height + m->gappih*ie;
+ } else {
+- 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;
++ r = n - i;
++ h = (m->w.height - ty - m->gappoh*oe - m->gappih*ie * (r - 1)) / r;
++ resize(c, (struct wlr_box){.x = m->w.x + mw + m->gappov*oe, .y = m->w.y + ty,
++ .width = m->w.width - mw - 2*m->gappov*oe, .height = h}, 0);
++ ty += c->geom.height + m->gappih*ie;
+ }
+ i++;
+ }
+@@ -2739,6 +2862,13 @@ togglefullscreen(const Arg *arg)
+ setfullscreen(sel, !sel->isfullscreen);
+ }
+
++void
++togglegaps(const Arg *arg)
++{
++ enablegaps = !enablegaps;
++ arrange(selmon);
++}
++
+ void
+ toggletag(const Arg *arg)
+ {
+--
+2.46.0
+
+
+From 8b9db986eddeade22d92fb15a8c836912869be29 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?=
+ <leohdz172@protonmail.com>
+Date: Wed, 20 Jul 2022 00:15:32 -0500
+Subject: [PATCH 2/2] allow gaps in monocle layout if requested
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Leonardo Hernández Hernández <leohdz172@proton.me>
+---
+ config.def.h | 1 +
+ dwl.c | 6 +++++-
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/config.def.h b/config.def.h
+index 39e528b1..f4d4095d 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -7,6 +7,7 @@
+ 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 const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */
+ static const unsigned int borderpx = 1; /* border pixel of windows */
+ static const unsigned int gappih = 10; /* horiz inner gap between windows */
+ static const unsigned int gappiv = 10; /* vert inner gap between windows */
+diff --git a/dwl.c b/dwl.c
+index d749728a..dbc66ef8 100644
+--- a/dwl.c
++++ b/dwl.c
+@@ -1879,8 +1879,12 @@ monocle(Monitor *m)
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+- resize(c, m->w, 0);
+ n++;
++ if (!monoclegaps)
++ resize(c, m->w, 0);
++ else
++ resize(c, (struct wlr_box){.x = m->w.x + gappoh, .y = m->w.y + gappov,
++ .width = m->w.width - 2 * gappoh, .height = m->w.height - 2 * gappov}, 0);
+ }
+ if (n)
+ snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n);
+--
+2.46.0
+
diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml
new file mode 100644
index 0000000..d62fd51
--- /dev/null
+++ b/protocols/wlr-layer-shell-unstable-v1.xml
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_unstable_v1">
+ <copyright>
+ Copyright © 2017 Drew DeVault
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="zwlr_layer_shell_v1" version="4">
+ <description summary="create surfaces that are layers of the desktop">
+ Clients can use this interface to assign the surface_layer role to
+ wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ rendered with a defined z-depth respective to each other. They may also be
+ anchored to the edges and corners of a screen and specify input handling
+ semantics. This interface should be suitable for the implementation of
+ many desktop shell components, and a broad number of other applications
+ that interact with the desktop.
+ </description>
+
+ <request name="get_layer_surface">
+ <description summary="create a layer_surface from a surface">
+ Create a layer surface for an existing surface. This assigns the role of
+ layer_surface, or raises a protocol error if another role is already
+ assigned.
+
+ Creating a layer surface from a wl_surface which has a buffer attached
+ or committed is a client error, and any attempts by a client to attach
+ or manipulate a buffer prior to the first layer_surface.configure call
+ must also be treated as errors.
+
+ After creating a layer_surface object and setting it up, the client
+ must perform an initial commit without any buffer attached.
+ The compositor will reply with a layer_surface.configure event.
+ The client must acknowledge it and is then allowed to attach a buffer
+ to map the surface.
+
+ You may pass NULL for output to allow the compositor to decide which
+ output to use. Generally this will be the one that the user most
+ recently interacted with.
+
+ Clients can specify a namespace that defines the purpose of the layer
+ surface.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+ <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+ </request>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="wl_surface has another role"/>
+ <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+ <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+ </enum>
+
+ <enum name="layer">
+ <description summary="available layers for surfaces">
+ These values indicate which layers a surface can be rendered in. They
+ are ordered by z depth, bottom-most first. Traditional shell surfaces
+ will typically be rendered between the bottom and top layers.
+ Fullscreen shell surfaces are typically rendered at the top layer.
+ Multiple surfaces can share a single layer, and ordering within a
+ single layer is undefined.
+ </description>
+
+ <entry name="background" value="0"/>
+ <entry name="bottom" value="1"/>
+ <entry name="top" value="2"/>
+ <entry name="overlay" value="3"/>
+ </enum>
+
+ <!-- Version 3 additions -->
+
+ <request name="destroy" type="destructor" since="3">
+ <description summary="destroy the layer_shell object">
+ This request indicates that the client will not use the layer_shell
+ object any more. Objects that have been created through this instance
+ are not affected.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_layer_surface_v1" version="4">
+ <description summary="layer metadata interface">
+ An interface that may be implemented by a wl_surface, for surfaces that
+ are designed to be rendered as a layer of a stacked desktop-like
+ environment.
+
+ Layer surface state (layer, size, anchor, exclusive zone,
+ margin, interactivity) is double-buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+
+ Attaching a null buffer to a layer surface unmaps it.
+
+ Unmapping a layer_surface means that the surface cannot be shown by the
+ compositor until it is explicitly mapped again. The layer_surface
+ returns to the state it had right after layer_shell.get_layer_surface.
+ The client can re-map the surface by performing a commit without any
+ buffer attached, waiting for a configure event and handling it as usual.
+ </description>
+
+ <request name="set_size">
+ <description summary="sets the size of the surface">
+ Sets the size of the surface in surface-local coordinates. The
+ compositor will display the surface centered with respect to its
+ anchors.
+
+ If you pass 0 for either value, the compositor will assign it and
+ inform you of the assignment in the configure event. You must set your
+ anchor to opposite edges in the dimensions you omit; not doing so is a
+ protocol error. Both values are 0 by default.
+
+ Size is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </request>
+
+ <request name="set_anchor">
+ <description summary="configures the anchor point of the surface">
+ Requests that the compositor anchor the surface to the specified edges
+ and corners. If two orthogonal edges are specified (e.g. 'top' and
+ 'left'), then the anchor point will be the intersection of the edges
+ (e.g. the top left corner of the output); otherwise the anchor point
+ will be centered on that edge, or in the center if none is specified.
+
+ Anchor is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"/>
+ </request>
+
+ <request name="set_exclusive_zone">
+ <description summary="configures the exclusive geometry of this surface">
+ Requests that the compositor avoids occluding an area with other
+ surfaces. The compositor's use of this information is
+ implementation-dependent - do not assume that this region will not
+ actually be occluded.
+
+ A positive value is only meaningful if the surface is anchored to one
+ edge or an edge and both perpendicular edges. If the surface is not
+ anchored, anchored to only two perpendicular edges (a corner), anchored
+ to only two parallel edges or anchored to all edges, a positive value
+ will be treated the same as zero.
+
+ A positive zone is the distance from the edge in surface-local
+ coordinates to consider exclusive.
+
+ Surfaces that do not wish to have an exclusive zone may instead specify
+ how they should interact with surfaces that do. If set to zero, the
+ surface indicates that it would like to be moved to avoid occluding
+ surfaces with a positive exclusive zone. If set to -1, the surface
+ indicates that it would not like to be moved to accommodate for other
+ surfaces, and the compositor should extend it all the way to the edges
+ it is anchored to.
+
+ For example, a panel might set its exclusive zone to 10, so that
+ maximized shell surfaces are not shown on top of it. A notification
+ might set its exclusive zone to 0, so that it is moved to avoid
+ occluding the panel, but shell surfaces are shown underneath it. A
+ wallpaper or lock screen might set their exclusive zone to -1, so that
+ they stretch below or over the panel.
+
+ The default value is 0.
+
+ Exclusive zone is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="zone" type="int"/>
+ </request>
+
+ <request name="set_margin">
+ <description summary="sets a margin from the anchor point">
+ Requests that the surface be placed some distance away from the anchor
+ point on the output, in surface-local coordinates. Setting this value
+ for edges you are not anchored to has no effect.
+
+ The exclusive zone includes the margin.
+
+ Margin is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="top" type="int"/>
+ <arg name="right" type="int"/>
+ <arg name="bottom" type="int"/>
+ <arg name="left" type="int"/>
+ </request>
+
+ <enum name="keyboard_interactivity">
+ <description summary="types of keyboard interaction possible for a layer shell surface">
+ Types of keyboard interaction possible for layer shell surfaces. The
+ rationale for this is twofold: (1) some applications are not interested
+ in keyboard events and not allowing them to be focused can improve the
+ desktop experience; (2) some applications will want to take exclusive
+ keyboard focus.
+ </description>
+
+ <entry name="none" value="0">
+ <description summary="no keyboard focus is possible">
+ This value indicates that this surface is not interested in keyboard
+ events and the compositor should never assign it the keyboard focus.
+
+ This is the default value, set for newly created layer shell surfaces.
+
+ This is useful for e.g. desktop widgets that display information or
+ only have interaction with non-keyboard input devices.
+ </description>
+ </entry>
+ <entry name="exclusive" value="1">
+ <description summary="request exclusive keyboard focus">
+ Request exclusive keyboard focus if this surface is above the shell surface layer.
+
+ For the top and overlay layers, the seat will always give
+ exclusive keyboard focus to the top-most layer which has keyboard
+ interactivity set to exclusive. If this layer contains multiple
+ surfaces with keyboard interactivity set to exclusive, the compositor
+ determines the one receiving keyboard events in an implementation-
+ defined manner. In this case, no guarantee is made when this surface
+ will receive keyboard focus (if ever).
+
+ For the bottom and background layers, the compositor is allowed to use
+ normal focus semantics.
+
+ This setting is mainly intended for applications that need to ensure
+ they receive all keyboard events, such as a lock screen or a password
+ prompt.
+ </description>
+ </entry>
+ <entry name="on_demand" value="2" since="4">
+ <description summary="request regular keyboard focus semantics">
+ This requests the compositor to allow this surface to be focused and
+ unfocused by the user in an implementation-defined manner. The user
+ should be able to unfocus this surface even regardless of the layer
+ it is on.
+
+ Typically, the compositor will want to use its normal mechanism to
+ manage keyboard focus between layer shell surfaces with this setting
+ and regular toplevels on the desktop layer (e.g. click to focus).
+ Nevertheless, it is possible for a compositor to require a special
+ interaction to focus or unfocus layer shell surfaces (e.g. requiring
+ a click even if focus follows the mouse normally, or providing a
+ keybinding to switch focus between layers).
+
+ This setting is mainly intended for desktop shell components (e.g.
+ panels) that allow keyboard interaction. Using this option can allow
+ implementing a desktop shell that can be fully usable without the
+ mouse.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_keyboard_interactivity">
+ <description summary="requests keyboard events">
+ Set how keyboard events are delivered to this surface. By default,
+ layer shell surfaces do not receive keyboard events; this request can
+ be used to change this.
+
+ This setting is inherited by child surfaces set by the get_popup
+ request.
+
+ Layer surfaces receive pointer, touch, and tablet events normally. If
+ you do not want to receive them, set the input region on your surface
+ to an empty region.
+
+ Keyboard interactivity is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign this layer_surface as an xdg_popup parent">
+ This assigns an xdg_popup's parent to this layer_surface. This popup
+ should have been created via xdg_surface::get_popup with the parent set
+ to NULL, and this request must be invoked before committing the popup's
+ initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="popup" type="object" interface="xdg_popup"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the layer_surface">
+ This request destroys the layer surface.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event asks the client to resize its surface.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ The client is free to dismiss all but the last configure event it
+ received.
+
+ The width and height arguments specify the size of the window in
+ surface-local coordinates.
+
+ The size is a hint, in the sense that the client is free to ignore it if
+ it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+ resize in steps of NxM pixels). If the client picks a smaller size and
+ is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+ surface will be centered on this axis.
+
+ If the width or height arguments are zero, it means the client should
+ decide its own window dimension.
+ </description>
+ <arg name="serial" type="uint"/>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </event>
+
+ <event name="closed">
+ <description summary="surface should be closed">
+ The closed event is sent by the compositor when the surface will no
+ longer be shown. The output may have been destroyed or the user may
+ have asked for it to be removed. Further changes to the surface will be
+ ignored. The client should destroy the resource after receiving this
+ event, and create a new surface if they so choose.
+ </description>
+ </event>
+
+ <enum name="error">
+ <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+ <entry name="invalid_size" value="1" summary="size is invalid"/>
+ <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+ <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
+ </enum>
+
+ <enum name="anchor" bitfield="true">
+ <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+ <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+ <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+ <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+ </enum>
+
+ <!-- Version 2 additions -->
+
+ <request name="set_layer" since="2">
+ <description summary="change the layer of the surface">
+ Change the layer that the surface is rendered on.
+
+ Layer is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocols/wlr-output-power-management-unstable-v1.xml b/protocols/wlr-output-power-management-unstable-v1.xml
new file mode 100644
index 0000000..a977839
--- /dev/null
+++ b/protocols/wlr-output-power-management-unstable-v1.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_output_power_management_unstable_v1">
+ <copyright>
+ Copyright © 2019 Purism SPC
+
+ 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 (including the next
+ paragraph) 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.
+ </copyright>
+
+ <description summary="Control power management modes of outputs">
+ This protocol allows clients to control power management modes
+ of outputs that are currently part of the compositor space. The
+ intent is to allow special clients like desktop shells to power
+ down outputs when the system is idle.
+
+ To modify outputs not currently part of the compositor space see
+ wlr-output-management.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <interface name="zwlr_output_power_manager_v1" version="1">
+ <description summary="manager to create per-output power management">
+ This interface is a manager that allows creating per-output power
+ management mode controls.
+ </description>
+
+ <request name="get_output_power">
+ <description summary="get a power management for an output">
+ Create a output power management mode control that can be used to
+ adjust the power management mode for a given output.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the manager">
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_output_power_v1" version="1">
+ <description summary="adjust power management mode for an output">
+ This object offers requests to set the power management mode of
+ an output.
+ </description>
+
+ <enum name="mode">
+ <entry name="off" value="0"
+ summary="Output is turned off."/>
+ <entry name="on" value="1"
+ summary="Output is turned on, no power saving"/>
+ </enum>
+
+ <enum name="error">
+ <entry name="invalid_mode" value="1" summary="inexistent power save mode"/>
+ </enum>
+
+ <request name="set_mode">
+ <description summary="Set an outputs power save mode">
+ Set an output's power save mode to the given mode. The mode change
+ is effective immediately. If the output does not support the given
+ mode a failed event is sent.
+ </description>
+ <arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/>
+ </request>
+
+ <event name="mode">
+ <description summary="Report a power management mode change">
+ Report the power management mode change of an output.
+
+ The mode event is sent after an output changed its power
+ management mode. The reason can be a client using set_mode or the
+ compositor deciding to change an output's mode.
+ This event is also sent immediately when the object is created
+ so the client is informed about the current power management mode.
+ </description>
+ <arg name="mode" type="uint" enum="mode"
+ summary="the output's new power management mode"/>
+ </event>
+
+ <event name="failed">
+ <description summary="object no longer valid">
+ This event indicates that the output power management mode control
+ is no longer valid. This can happen for a number of reasons,
+ including:
+ - The output doesn't support power management
+ - Another client already has exclusive power management mode control
+ for this output
+ - The output disappeared
+
+ Upon receiving this event, the client should destroy this object.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy this power management">
+ Destroys the output power management mode control object.
+ </description>
+ </request>
+ </interface>
+</protocol>
diff --git a/startdwl.sh b/startdwl.sh
new file mode 100755
index 0000000..fe89667
--- /dev/null
+++ b/startdwl.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+kdeconnectd &
+dunst &
+# wlr-randr --output DP-1 --mode 2560x1440@240.001007
+# wlr-randr --output DP-1 --mode 2560x1440@260.002014
+# sleep 1
+export QT_QPA_PLATFORMTHEME=qt6ct
+
+# slstatus -s | dwl -s "wlr-randr --output DP-1 --mode 2560x1440@260.002014 & swaybg -i /usr/share/backgrounds/meine/wallhaven-k911kq.jpg & disown" > dwl.log 2>&1
+# slstatus -s | dwl -s "swaybg -i /usr/share/backgrounds/meine/wallhaven-k911kq.jpg & disown" > dwl.log 2>&1
+# slstatus -s | dwl -s "wlr-randr --output DP-1 --mode 2560x1440@260.002014" > dwl.log 2>&1
+slstatus -s | dwl > dwl.log 2>&1
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..b925987
--- /dev/null
+++ b/util.c
@@ -0,0 +1,51 @@
+/* See LICENSE.dwm file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "util.h"
+
+void
+die(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ exit(1);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+
+ if (!(p = calloc(nmemb, size)))
+ die("calloc:");
+ return p;
+}
+
+int
+fd_set_nonblock(int fd) {
+ int flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ perror("fcntl(F_GETFL):");
+ return -1;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ perror("fcntl(F_SETFL):");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..226980d
--- /dev/null
+++ b/util.h
@@ -0,0 +1,5 @@
+/* See LICENSE.dwm file for copyright and license details. */
+
+void die(const char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);
+int fd_set_nonblock(int fd);