Compare commits

..

3 Commits

Author SHA1 Message Date
Sivecano
9be77b2925 hot-reload: markdown is hard 2025-05-30 02:55:21 +02:00
Sivecano
50c6a46c35 update hot-reload README with a more comprehensive explanation of how to integrate it with other patches. 2025-05-30 02:43:50 +02:00
Sivecano
83b68bee53 update and fix hot-reload patch
we now depend on config.mk better and apply to latest dwl main
2025-05-30 01:44:22 +02:00
3 changed files with 143 additions and 50 deletions

View File

@ -18,6 +18,9 @@ Reloading the compositor will replace all functionality except for `main`, `setu
Note that you're responsible yourself for reloading ressources like fonts, which may only get acquired once.
A lot of components of dwl will also only get run on a trigger (the tiling for example).
So not every change will be immediate.
Furthermore, any patch adding more global state to dwl cannot currently be reloaded properly since
we keep state in the cold part. These patches will still work and their functionality will (hopefully) be
reloadable but you will need to restart the compositor once.
#### Notes
##### reduce compile errors
@ -28,12 +31,79 @@ So you may want to disable this compile option in order to get readable compiler
This does depend on you having a notification daemon like `dunst` or `mako` running as well as
having `notify-send` installed in order for the compositor to inform you of the reload.
#### How?
Most of all dwl functionality is moved into a shared object file `dwl.so`, which can be reloaded at runtime.
#### How do I make this work with other patches?
Most patches should already put everything in more or less the correct place but if they don't, then here is
where you learn how to fix it.
The concept itself is quite simple. We compile dwl.c twice once normally and once with the `HOT` macro defined.
The first run will yield the executable and the second will yield a shared object file to be reloaded at runtime.
From the cold part there are some newly available macros:
> symbol names are written as-is, never as string literals
* `TSYM(T, s)` dynamically loads the symbol `s` with type `T` from the shared object file use this if you need to call functions in the cold part (i.e. the `setup` function).
* `CSYM(T, v)` dynamically accesses the value of the symbol `v` of type `T` from the shared object. Use this to query values from config.h for example.
* `LISTEN_GLOBAL(E, L)` is similar to the `LISTEN` macro. `E` is an event and `L` the name of a global
listener. Current implementation is a bit messy and I may fix it if someone bothers me about it.
* `UNLISTEN(L)` takes a listener and unregisteres it. This is important for reloading.
When adding new code there are some considerations to be made. Since dwl decorates all symbols with `static` by default, we cannot access them as-is.
C's macro system is a bit too powerful though and we use this to our advantage. We will repeatedly define and
undefine a macro called `static` in order to replace the `static` keyword inside some sections.
This allows us to do less refactoring and preserve a lot of the original patch compatability since we're only
strategically adding lines. We're tring to be as minimally invasive as we can.
As a general guide:
* global state should be global for the cold part and `extern` in the cold part meaning it should be inside a block like this:
```C
#ifdef HOT
#define static extern
#else
#define static
#endif
... // your global variables go here
#undef static
```
* function declarations should be visible in the hot part but not included in the cold part meaning they should be enclosed like this:
```C
#ifdef HOT
#define static
... // your function declarations go here
#undef static
#endif
```
* static data like the event handler structs in the current `main` branch are a bit more difficult but we will let them reside inside the hot part.
Thus, we enclose them the same way we do functions:
```C
#ifdef HOT
#define static
... // your struct wl_listener event handlers go here
#undef static
#endif
```
* function definitions should go in the hot part, so they need to be inside a big block like this:
```C
#ifdef HOT
... // function definitions here
#endif
* enfore use of the `LISTEN_GLOBAL` and `UNLISTEN` macros (I know this sucks but what can I do, I need to get
access to the callbacks somehow). So you want
* `wl_list_remove(listener.link)` to become `UNLISTEN(listener)` and
* `wl_signal_add(event, global_listener)` to become `LISTEN_GLOBAL(event, global_listener)`.
* Make sure that any patch you're using also uses static everywhere.
* If a patch adds any config variables that are accessed in the cold part (i.e. probably `setup`),
then you'll have to manually remove the `static` keyword from them.
Note that usually you do not have to create the big `#ifdef` blocks yourself.
There is for example already a huge `#ifdef HOT`-delimited codeblock at the bottom
of dwl.c where all the function definitions go.
If you have any troubles, feel free to reach out.
### Download
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload-0.7.patch)
- [main 2025-02-14](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload.patch)
- [main 2025-05-30](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload.patch)
- find the repo for the patch [here](/Sivecano/dwl/src/branch/hot-reload)
### Authors
- [Sivecano](https://codeberg.org/Sivecano)

View File

@ -1,4 +1,4 @@
From caa1adaf02ab4f9a326761ade5d1346149bc7c59 Mon Sep 17 00:00:00 2001
From 168456db2d9d2e1c6f1183b0ecc660b21ecb9271 Mon Sep 17 00:00:00 2001
From: Sivecano <sivecano@gmail.com>
Date: Sun, 26 Jan 2025 18:30:02 +0100
Subject: [PATCH] redo hot-reloading in one file
@ -12,7 +12,7 @@ Subject: [PATCH] redo hot-reloading in one file
5 files changed, 351 insertions(+), 50 deletions(-)
diff --git a/Makefile b/Makefile
index 3358bae..70d3d0f 100644
index 3358bae..e7ee9ff 100644
--- a/Makefile
+++ b/Makefile
@@ -13,13 +13,16 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \
@ -28,7 +28,7 @@ index 3358bae..70d3d0f 100644
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.o: dwl.c cursor-shape-v1-protocol.h \
+dwl.o: dwl.c 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
+dwl.so: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \
@ -766,5 +766,5 @@ index 226980d..11aab34 100644
+struct listens* append_listener(struct wl_listener* l, struct listens* ls);
+struct listens* remove_listener(struct wl_listener* l, struct listens* ls);
--
2.48.1
2.49.0

View File

@ -1,18 +1,18 @@
From 79fbc2405919a049a35dd58e860b6519ebb7943b Mon Sep 17 00:00:00 2001
From 1858097340a93293f2489dcb8f78dc52ad2a40df Mon Sep 17 00:00:00 2001
From: Sivecano <sivecano@gmail.com>
Date: Sun, 26 Jan 2025 18:30:02 +0100
Subject: [PATCH] redo hot-reloading in one file
---
Makefile | 19 ++-
Makefile | 19 +-
config.def.h | 5 +-
dwl.c | 475 +++++++++++++++++++++++++++++++++++++++------------
dwl.c | 487 +++++++++++++++++++++++++++++++++++++++------------
util.c | 34 ++++
util.h | 6 +
5 files changed, 421 insertions(+), 118 deletions(-)
5 files changed, 433 insertions(+), 118 deletions(-)
diff --git a/Makefile b/Makefile
index 578194f..0714ed1 100644
index 578194f..69c1b54 100644
--- a/Makefile
+++ b/Makefile
@@ -13,13 +13,16 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \
@ -28,7 +28,7 @@ index 578194f..0714ed1 100644
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.o: dwl.c cursor-shape-v1-protocol.h \
+dwl.o: dwl.c 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
+dwl.so: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \
@ -99,7 +99,7 @@ index 22d2171..6e3dda1 100644
{ MODKEY, XKB_KEY_k, focusstack, {.i = -1} },
{ MODKEY, XKB_KEY_i, incnmaster, {.i = +1} },
diff --git a/dwl.c b/dwl.c
index ec4ca86..8d1eceb 100644
index 4816159..5125931 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,15 @@
@ -265,14 +265,37 @@ index ec4ca86..8d1eceb 100644
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};
@@ -449,8 +535,38 @@ static struct wl_listener xwayland_ready = {.notify = xwaylandready};
static struct wlr_xwayland *xwayland;
#endif
@@ -436,7 +522,15 @@ 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};
+/* undoes the shadowing of static from above */
+#endif
+#undef static
+
+
#ifdef XWAYLAND
+#ifdef HOT
+#define static
+
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);
@@ -446,11 +540,45 @@ 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};
+
+#define static extern
+#else
+#define static
+#endif
+
static struct wlr_xwayland *xwayland;
+
+#undef static
+#endif
+
+/* this is where we put global hot-reload state */
+#ifdef HOT
+#define COLD extern
@ -292,8 +315,8 @@ index ec4ca86..8d1eceb 100644
+#ifndef HOT
+static char* runpath;
+
+#endif
+
#endif
+
+#ifdef HOT
+
@ -304,7 +327,7 @@ index ec4ca86..8d1eceb 100644
/* attempt to encapsulate suck into one file */
#include "client.h"
@@ -692,10 +808,12 @@ checkidleinhibitor(struct wlr_surface *exclude)
@@ -695,10 +823,12 @@ checkidleinhibitor(struct wlr_surface *exclude)
wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited);
}
@ -318,7 +341,7 @@ index ec4ca86..8d1eceb 100644
#ifdef XWAYLAND
wlr_xwayland_destroy(xwayland);
xwayland = NULL;
@@ -707,7 +825,7 @@ cleanup(void)
@@ -710,7 +840,7 @@ cleanup(void)
}
wlr_xcursor_manager_destroy(cursor_mgr);
@ -327,7 +350,7 @@ index ec4ca86..8d1eceb 100644
/* 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 */
@@ -719,6 +837,8 @@ cleanup(void)
@@ -722,6 +852,8 @@ cleanup(void)
wlr_scene_node_destroy(&scene->tree.node);
}
@ -336,7 +359,7 @@ index ec4ca86..8d1eceb 100644
void
cleanupmon(struct wl_listener *listener, void *data)
{
@@ -732,10 +852,10 @@ cleanupmon(struct wl_listener *listener, void *data)
@@ -735,10 +867,10 @@ cleanupmon(struct wl_listener *listener, void *data)
wlr_layer_surface_v1_destroy(l->layer_surface);
}
@ -350,7 +373,7 @@ index ec4ca86..8d1eceb 100644
m->wlr_output->data = NULL;
wlr_output_layout_remove(output_layout, m->wlr_output);
wlr_scene_output_destroy(m->scene_output);
@@ -748,37 +868,37 @@ cleanupmon(struct wl_listener *listener, void *data)
@@ -751,37 +883,37 @@ cleanupmon(struct wl_listener *listener, void *data)
void
cleanuplisteners(void)
{
@ -418,7 +441,7 @@ index ec4ca86..8d1eceb 100644
#endif
}
@@ -905,8 +1025,7 @@ commitpopup(struct wl_listener *listener, void *data)
@@ -908,8 +1040,7 @@ commitpopup(struct wl_listener *listener, void *data)
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);
@ -428,7 +451,7 @@ index ec4ca86..8d1eceb 100644
}
void
@@ -1236,8 +1355,8 @@ destroydecoration(struct wl_listener *listener, void *data)
@@ -1239,8 +1370,8 @@ destroydecoration(struct wl_listener *listener, void *data)
{
Client *c = wl_container_of(listener, c, destroy_decoration);
@ -439,7 +462,7 @@ index ec4ca86..8d1eceb 100644
}
void
@@ -1246,8 +1365,7 @@ destroydragicon(struct wl_listener *listener, void *data)
@@ -1249,8 +1380,7 @@ 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);
@ -449,7 +472,7 @@ index ec4ca86..8d1eceb 100644
}
void
@@ -1256,8 +1374,7 @@ destroyidleinhibitor(struct wl_listener *listener, void *data)
@@ -1259,8 +1389,7 @@ 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));
@ -459,7 +482,7 @@ index ec4ca86..8d1eceb 100644
}
void
@@ -1266,9 +1383,9 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data)
@@ -1269,9 +1398,9 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data)
LayerSurface *l = wl_container_of(listener, l, destroy);
wl_list_remove(&l->link);
@ -472,7 +495,7 @@ index ec4ca86..8d1eceb 100644
wlr_scene_node_destroy(&l->scene->node);
wlr_scene_node_destroy(&l->popups->node);
free(l);
@@ -1287,9 +1404,9 @@ destroylock(SessionLock *lock, int unlock)
@@ -1290,9 +1419,9 @@ destroylock(SessionLock *lock, int unlock)
motionnotify(0, NULL, 0, 0, 0, 0);
destroy:
@ -485,7 +508,7 @@ index ec4ca86..8d1eceb 100644
wlr_scene_node_destroy(&lock->scene->node);
cur_lock = NULL;
@@ -1303,7 +1420,7 @@ destroylocksurface(struct wl_listener *listener, void *data)
@@ -1306,7 +1435,7 @@ destroylocksurface(struct wl_listener *listener, void *data)
struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface;
m->lock_surface = NULL;
@ -494,7 +517,7 @@ index ec4ca86..8d1eceb 100644
if (lock_surface->surface != seat->keyboard_state.focused_surface)
return;
@@ -1323,23 +1440,23 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1326,23 +1455,23 @@ destroynotify(struct wl_listener *listener, void *data)
{
/* Called when the xdg_toplevel is destroyed. */
Client *c = wl_container_of(listener, c, destroy);
@ -530,7 +553,7 @@ index ec4ca86..8d1eceb 100644
}
free(c);
}
@@ -1354,7 +1471,7 @@ destroypointerconstraint(struct wl_listener *listener, void *data)
@@ -1357,7 +1486,7 @@ destroypointerconstraint(struct wl_listener *listener, void *data)
active_constraint = NULL;
}
@ -539,7 +562,7 @@ index ec4ca86..8d1eceb 100644
free(pointer_constraint);
}
@@ -1370,9 +1487,9 @@ destroykeyboardgroup(struct wl_listener *listener, void *data)
@@ -1373,9 +1502,9 @@ destroykeyboardgroup(struct wl_listener *listener, void *data)
{
KeyboardGroup *group = wl_container_of(listener, group, destroy);
wl_event_source_remove(group->key_repeat_source);
@ -552,7 +575,7 @@ index ec4ca86..8d1eceb 100644
wlr_keyboard_group_destroy(group->wlr_group);
free(group);
}
@@ -1538,8 +1655,8 @@ gpureset(struct wl_listener *listener, void *data)
@@ -1541,8 +1670,8 @@ gpureset(struct wl_listener *listener, void *data)
if (!(alloc = wlr_allocator_autocreate(backend, drw)))
die("couldn't recreate allocator");
@ -563,7 +586,7 @@ index ec4ca86..8d1eceb 100644
wlr_compositor_set_renderer(compositor, drw);
@@ -2229,6 +2346,8 @@ resize(Client *c, struct wlr_box geo, int interact)
@@ -2232,6 +2361,8 @@ resize(Client *c, struct wlr_box geo, int interact)
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip);
}
@ -572,7 +595,7 @@ index ec4ca86..8d1eceb 100644
void
run(char *startup_cmd)
{
@@ -2268,11 +2387,11 @@ run(char *startup_cmd)
@@ -2271,11 +2402,11 @@ run(char *startup_cmd)
if (fd_set_nonblock(STDOUT_FILENO) < 0)
close(STDOUT_FILENO);
@ -586,7 +609,7 @@ index ec4ca86..8d1eceb 100644
/* 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
@@ -2288,6 +2407,9 @@ run(char *startup_cmd)
@@ -2291,6 +2422,9 @@ run(char *startup_cmd)
wl_display_run(dpy);
}
@ -596,7 +619,7 @@ index ec4ca86..8d1eceb 100644
void
setcursor(struct wl_listener *listener, void *data)
{
@@ -2434,17 +2556,19 @@ setsel(struct wl_listener *listener, void *data)
@@ -2437,17 +2571,19 @@ setsel(struct wl_listener *listener, void *data)
wlr_seat_set_selection(seat, event->source, event->serial);
}
@ -618,7 +641,7 @@ index ec4ca86..8d1eceb 100644
/* The Wayland display is managed by libwayland. It handles accepting
* clients from the Unix socket, manging Wayland globals, and so on. */
@@ -2460,7 +2584,7 @@ setup(void)
@@ -2463,7 +2599,7 @@ setup(void)
/* Initialize the scene graph used to lay out windows */
scene = wlr_scene_create();
@ -627,7 +650,7 @@ index ec4ca86..8d1eceb 100644
for (i = 0; i < NUM_LAYERS; i++)
layers[i] = wlr_scene_tree_create(&scene->tree);
drag_icon = wlr_scene_tree_create(&scene->tree);
@@ -2472,7 +2596,7 @@ setup(void)
@@ -2475,7 +2611,7 @@ setup(void)
* supports for shared memory, this configures that for clients. */
if (!(drw = wlr_renderer_autocreate(backend)))
die("couldn't create renderer");
@ -636,7 +659,7 @@ index ec4ca86..8d1eceb 100644
/* Create shm, drm and linux_dmabuf interfaces by ourselves.
* The simplest way is call:
@@ -2519,24 +2643,24 @@ setup(void)
@@ -2522,24 +2658,24 @@ setup(void)
/* Initializes the interface used to implement urgency hints */
activation = wlr_xdg_activation_v1_create(dpy);
@ -665,7 +688,7 @@ index ec4ca86..8d1eceb 100644
/* 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
@@ -2548,19 +2672,19 @@ setup(void)
@@ -2551,19 +2687,19 @@ setup(void)
wl_list_init(&fstack);
xdg_shell = wlr_xdg_shell_create(dpy, 6);
@ -690,7 +713,7 @@ index ec4ca86..8d1eceb 100644
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);
@@ -2570,10 +2694,10 @@ setup(void)
@@ -2573,10 +2709,10 @@ setup(void)
wlr_server_decoration_manager_create(dpy),
WLR_SERVER_DECORATION_MANAGER_MODE_SERVER);
xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy);
@ -703,7 +726,7 @@ index ec4ca86..8d1eceb 100644
relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy);
@@ -2601,14 +2725,14 @@ setup(void)
@@ -2604,14 +2740,14 @@ setup(void)
*
* And more comments are sprinkled throughout the notify functions above.
*/
@ -724,7 +747,7 @@ index ec4ca86..8d1eceb 100644
/*
* Configures a seat, which is a single "seat" at which a user sits and
@@ -2616,27 +2740,27 @@ setup(void)
@@ -2619,27 +2755,27 @@ setup(void)
* 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.
*/
@ -765,7 +788,7 @@ index ec4ca86..8d1eceb 100644
/* 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
@@ -2648,8 +2772,8 @@ setup(void)
@@ -2651,8 +2787,8 @@ setup(void)
* It will be started when the first X client is started.
*/
if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) {
@ -776,7 +799,7 @@ index ec4ca86..8d1eceb 100644
setenv("DISPLAY", xwayland->display_name, 1);
} else {
@@ -2658,6 +2782,9 @@ setup(void)
@@ -2661,6 +2797,9 @@ setup(void)
#endif
}
@ -786,7 +809,7 @@ index ec4ca86..8d1eceb 100644
void
spawn(const Arg *arg)
{
@@ -3139,8 +3266,8 @@ void
@@ -3142,8 +3281,8 @@ void
dissociatex11(struct wl_listener *listener, void *data)
{
Client *c = wl_container_of(listener, c, dissociate);
@ -797,7 +820,7 @@ index ec4ca86..8d1eceb 100644
}
void
@@ -3175,17 +3302,141 @@ xwaylandready(struct wl_listener *listener, void *data)
@@ -3178,17 +3317,141 @@ xwaylandready(struct wl_listener *listener, void *data)
}
#endif
@ -940,7 +963,7 @@ index ec4ca86..8d1eceb 100644
else if (c == 'v')
die("dwl " VERSION);
else
@@ -3205,3 +3456,5 @@ main(int argc, char *argv[])
@@ -3208,3 +3471,5 @@ main(int argc, char *argv[])
usage:
die("Usage: %s [-v] [-d] [-s startup command]", argv[0]);
}
@ -1005,5 +1028,5 @@ index 226980d..11aab34 100644
+struct listens* append_listener(struct wl_listener* l, struct listens* ls);
+struct listens* remove_listener(struct wl_listener* l, struct listens* ls);
--
2.48.1
2.49.0