mirror of
https://codeberg.org/dwl/dwl-patches.git
synced 2025-12-13 00:13:23 +00:00
427 lines
14 KiB
Diff
427 lines
14 KiB
Diff
From 3c380f4614f7e95134724d846191af5bcf3c4eef Mon Sep 17 00:00:00 2001
|
|
From: korei999 <ju7t1xe@gmail.com>
|
|
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 <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_tearing_control_v1.h>
|
|
#include <wlr/types/wlr_viewporter.h>
|
|
#include <wlr/types/wlr_virtual_keyboard_v1.h>
|
|
#include <wlr/types/wlr_virtual_pointer_v1.h>
|
|
@@ -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
|
|
|