From e104ef34959518d4f199fc7f39daf295bfeed1ef Mon Sep 17 00:00:00 2001 From: korei999 Date: Tue, 11 Nov 2025 00:21:49 +0200 Subject: [PATCH] tearing patch for 0.7 --- patches/tearing/README.md | 3 +- patches/tearing/tearing-0.7.patch | 426 ++++++++++++++++++++++++++++++ 2 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 patches/tearing/tearing-0.7.patch diff --git a/patches/tearing/README.md b/patches/tearing/README.md index 45efbf9..762c83a 100644 --- a/patches/tearing/README.md +++ b/patches/tearing/README.md @@ -11,7 +11,8 @@ static const ForceTearingRule force_tearing[] = { }; ``` ### Download -- [git branch](https://codeberg.org/korei999/dwl/src/branch/tearing) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tearing/tearing-0.7.patch) - [2025-04-22](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tearing/tearing.patch) +- [git branch](https://codeberg.org/korei999/dwl/src/branch/tearing) ### Authors - [korei999](https://codeberg.org/korei999) diff --git a/patches/tearing/tearing-0.7.patch b/patches/tearing/tearing-0.7.patch new file mode 100644 index 0000000..69a9ac0 --- /dev/null +++ b/patches/tearing/tearing-0.7.patch @@ -0,0 +1,426 @@ +From 3c380f4614f7e95134724d846191af5bcf3c4eef Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Tue, 11 Nov 2025 00:15:41 +0200 +Subject: [PATCH] implement tearing protocol + +--- + Makefile | 8 +- + config.def.h | 10 +++ + dwl.c | 211 +++++++++++++++++++++++++++++++++++++++++++-------- + 3 files changed, 194 insertions(+), 35 deletions(-) + +diff --git a/Makefile b/Makefile +index ff53040..f873069 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +@@ -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 \ ++ tearing-control-v1-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 $@ ++tearing-control-v1-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/staging/tearing-control/tearing-control-v1.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..6b27774 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -28,6 +28,15 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++/* tearing */ ++static int tearing_allowed = 1; ++static int global_tearing = 0; ++static const ForceTearingRule force_tearing_whitelist[] = { ++ {.title = "", .appid = "hl_linux"}, ++ {.title = "Warcraft III", .appid = ""}, ++ {.title = "", .appid = "gamescope"}, ++}; ++ + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ +@@ -125,6 +134,7 @@ static const char *menucmd[] = { "wmenu-run", NULL }; + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_t, toggleglobaltearing, {0} }, + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index c717c1d..4c95ede 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -50,6 +50,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -89,6 +90,11 @@ enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ + #endif + ++typedef struct ForceTearingRule { ++ const char* title; ++ const char* appid; ++} ForceTearingRule; ++ + typedef union { + int i; + uint32_t ui; +@@ -141,6 +147,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ enum wp_tearing_control_v1_presentation_hint tearing_hint; + } Client; + + typedef struct { +@@ -242,6 +249,17 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct TearingController { ++ struct wlr_tearing_control_v1 *tearing_control; ++ struct wl_listener set_hint; ++ struct wl_listener destroy; ++} TearingController; ++ ++typedef struct SendFrameDoneData { ++ struct timespec when; ++ struct Monitor *mon; ++} SendFrameDoneData; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -289,6 +307,7 @@ 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 forcetearingrule(Client *c); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -308,6 +327,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int moncantear(Monitor* m); + 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); +@@ -322,6 +342,7 @@ 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 sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data); + 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); +@@ -337,9 +358,13 @@ 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 tearingcontrollersethint(struct wl_listener *listener, void *data); ++static void tearingcontrollerdestroy(struct wl_listener *listener, void *data); ++static void tearingnewhint(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void toggleglobaltearing(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -402,6 +427,9 @@ static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; + ++static struct wlr_tearing_control_manager_v1 *tearing_control_v1; ++static struct wl_listener tearing_control_new_object = {.notify = tearingnewhint}; ++ + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; + static unsigned int cursor_mode; +@@ -1466,6 +1494,35 @@ focustop(Monitor *m) + return NULL; + } + ++static void ++forcetearingrule(Client *c) ++{ ++ int success = 0; ++ const char* appid = client_get_appid(c); ++ const char* title = client_get_title(c); ++ ++ for (unsigned i = 0; i < LENGTH(force_tearing_whitelist); ++i) { ++ if (appid) { ++ if (strcmp(force_tearing_whitelist[i].appid, appid) == 0) { ++ success = 1; ++ break; ++ } ++ } ++ ++ if (title) { ++ if (strcmp(force_tearing_whitelist[i].title, title) == 0) { ++ success = 1; ++ break; ++ } ++ } ++ } ++ ++ if (success) { ++ c->tearing_hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC; ++ fprintf(stderr, "Forcing tearing for appid: '%s', title: '%s'\n", appid, title); ++ } ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -1694,6 +1751,8 @@ mapnotify(struct wl_listener *listener, void *data) + Monitor *m; + int i; + ++ forcetearingrule(c); ++ + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); +@@ -1930,6 +1989,13 @@ moveresize(const Arg *arg) + } + } + ++int ++moncantear(Monitor* m) ++{ ++ Client *c = focustop(m); ++ return (c && c->isfullscreen && c->tearing_hint); /* 1 == ASYNC */ ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2102,53 +2168,56 @@ quit(const Arg *arg) + 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_scene_output *scene_output = m->scene_output; + struct wlr_output_state pending = {0}; +- struct wlr_gamma_control_v1 *gamma_control; +- struct timespec now; ++ struct wlr_gamma_control_v1 *gamma_control = {0}; ++ SendFrameDoneData frame_done_data = {0}; + +- /* 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; ++ m->wlr_output->frame_pending = false; ++ ++ if (!m->wlr_output->needs_frame && !m->gamma_lut_changed && ++ !pixman_region32_not_empty(&scene_output->pending_commit_damage)) { ++ goto skip; + } + +- /* +- * HACK: The "correct" way to set the gamma is to commit it together with +- * the rest of the state in one go, but to do that we would need to rewrite +- * wlr_scene_output_commit() in order to add the gamma to the pending +- * state before committing, instead try to commit the gamma in one frame, +- * and commit the rest of the state in the next one (or in the same frame if +- * the gamma can not be committed). +- */ ++ wlr_output_state_init(&pending); ++ if (!wlr_scene_output_build_state(m->scene_output, &pending, NULL)) ++ goto skip; ++ + if (m->gamma_lut_changed) { +- gamma_control +- = wlr_gamma_control_manager_v1_get_control(gamma_control_mgr, m->wlr_output); +- m->gamma_lut_changed = 0; ++ m->gamma_lut_changed = false; ++ gamma_control = wlr_gamma_control_manager_v1_get_control(gamma_control_mgr, m->wlr_output); + +- if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) +- goto commit; ++ if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) { ++ wlr_output_state_finish(&pending); ++ goto skip; ++ } + + if (!wlr_output_test_state(m->wlr_output, &pending)) { + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); +- goto commit; ++ wlr_output_state_set_gamma_lut(&pending, 0, NULL, NULL, NULL); + } +- wlr_output_commit_state(m->wlr_output, &pending); +- wlr_output_schedule_frame(m->wlr_output); +- } else { +-commit: +- 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); ++ if (global_tearing || (tearing_allowed && moncantear(m))) { ++ pending.tearing_page_flip = true; ++ ++ if (!wlr_output_test_state(m->wlr_output, &pending)) { ++ fprintf(stderr, "Output test failed on '%s', retrying without tearing page-flip\n", m->wlr_output->name); ++ pending.tearing_page_flip = false; ++ } ++ } ++ ++ if (!wlr_output_commit_state(m->wlr_output, &pending)) ++ fprintf(stderr, "Page-flip failed on output %s", m->wlr_output->name); ++ + wlr_output_state_finish(&pending); ++ ++skip: ++ clock_gettime(CLOCK_MONOTONIC, &frame_done_data.when); ++ frame_done_data.mon = m; ++ wlr_scene_output_for_each_buffer(m->scene_output, sendframedoneiterator, &frame_done_data); + } + + void +@@ -2272,6 +2341,16 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data) ++{ ++ SendFrameDoneData *data = user_data; ++ if (buffer->primary_output != data->mon->scene_output) ++ return; ++ ++ wlr_scene_buffer_send_frame_done(buffer, &data->when); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2628,6 +2707,9 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ tearing_control_v1 = wlr_tearing_control_manager_v1_create(dpy, 1); ++ wl_signal_add(&tearing_control_v1->events.new_object, &tearing_control_new_object); ++ + /* 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 */ +@@ -2691,6 +2773,63 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tearingcontrollersethint(struct wl_listener *listener, void *data) ++{ ++ Client *c = NULL, *i = NULL; ++ TearingController *controller = wl_container_of(listener, controller, set_hint); ++ ++ struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#ifdef XWAYLAND ++ struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#endif ++ ++ wl_list_for_each(i, &fstack, flink) { ++ if (i->surface.xdg == surface ++#ifdef XWAYLAND ++ || i->surface.xwayland == xsurface ++#endif ++ ) { ++ c = i; ++ break; ++ } ++ } ++ ++ if (c) { ++ enum wp_tearing_control_v1_presentation_hint hint = controller->tearing_control->current; ++ fprintf( ++ stderr, "TEARING: found surface: %p(appid: '%s', title: '%s'), hint: %d(%s)\n", ++ (void*)c, client_get_appid(c), client_get_title(c), hint, hint ? "ASYNC" : "VSYNC" ++ ); ++ c->tearing_hint = controller->tearing_control->current; ++ } ++} ++ ++void ++tearingcontrollerdestroy(struct wl_listener *listener, void *data) ++{ ++ TearingController *controller = wl_container_of(listener, controller, destroy); ++ ++ wl_list_remove(&controller->set_hint.link); ++ wl_list_remove(&controller->destroy.link); ++ free(controller); ++} ++ ++void ++tearingnewhint(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tearing_control_v1 *tearing_control = data; ++ TearingController *controller = ecalloc(1, sizeof(*controller)); ++ ++ controller->tearing_control = tearing_control; ++ ++ controller->set_hint.notify = tearingcontrollersethint; ++ wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); ++ ++ controller->destroy.notify = tearingcontrollerdestroy; ++ wl_signal_add(&tearing_control->events.destroy, &controller->destroy); ++} ++ + void + tile(Monitor *m) + { +@@ -2742,6 +2881,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++static void ++toggleglobaltearing(const Arg *arg) ++{ ++ global_tearing ^= 1; ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.51.1 +