From 9c7436ba71b77184403f24be0fd4ff824c9727a0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 4 Nov 2025 23:02:29 +0800 Subject: [PATCH] feat: tearing support --- protocols/meson.build | 1 + src/client/client.h | 14 ++++ src/config/parse_config.h | 9 ++ src/config/preset.h | 1 + src/ext-protocol/all.h | 1 + src/ext-protocol/tearing.h | 168 +++++++++++++++++++++++++++++++++++++ src/mango.c | 51 ++++++++--- 7 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 src/ext-protocol/tearing.h diff --git a/protocols/meson.build b/protocols/meson.build index 1069157..cafab64 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -19,6 +19,7 @@ wayland_xmls = [ wl_protocol_dir + '/staging/ext-image-capture-source/ext-image-capture-source-v1.xml', wl_protocol_dir + '/staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', wl_protocol_dir + '/staging/ext-workspace/ext-workspace-v1.xml', + wl_protocol_dir + '/staging/tearing-control/tearing-control-v1.xml', 'wlr-foreign-toplevel-management-unstable-v1.xml', 'dwl-ipc-unstable-v2.xml', 'wlr-layer-shell-unstable-v1.xml', diff --git a/src/client/client.h b/src/client/client.h index a3c9073..2b763fa 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -153,6 +153,20 @@ static inline void client_get_geometry(Client *c, struct wlr_box *geom) { *geom = c->surface.xdg->geometry; } +static inline Client *get_client_from_surface(struct wlr_surface *surface) { + if (!surface) + return NULL; + + // 从 surface 的 data 指针获取 scene tree + struct wlr_scene_tree *scene_tree = surface->data; + if (!scene_tree) + return NULL; + + // 从 scene tree 的 node data 获取 Client + Client *c = scene_tree->node.data; + return c; +} + static inline Client *client_get_parent(Client *c) { Client *p = NULL; #ifdef XWAYLAND diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 72140d1..7e1be63 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -79,6 +79,7 @@ typedef struct { int isterm; int allow_csd; int force_maximize; + int force_tearing; int noswallow; int noblur; float focused_opacity; @@ -328,6 +329,7 @@ typedef struct { int xwayland_persistence; int syncobj_enable; int adaptive_sync; + int allow_tearing; struct xkb_rule_names xkb_rules; @@ -1232,6 +1234,8 @@ void parse_option(Config *config, char *key, char *value) { config->syncobj_enable = atoi(value); } else if (strcmp(key, "adaptive_sync") == 0) { config->adaptive_sync = atoi(value); + } else if (strcmp(key, "allow_tearing") == 0) { + config->allow_tearing = atoi(value); } else if (strcmp(key, "no_border_when_single") == 0) { config->no_border_when_single = atoi(value); } else if (strcmp(key, "no_radius_when_single") == 0) { @@ -1673,6 +1677,7 @@ void parse_option(Config *config, char *key, char *value) { rule->isterm = -1; rule->allow_csd = -1; rule->force_maximize = -1; + rule->force_tearing = -1; rule->noswallow = -1; rule->noblur = -1; rule->nofadein = -1; @@ -1769,6 +1774,8 @@ void parse_option(Config *config, char *key, char *value) { rule->allow_csd = atoi(val); } else if (strcmp(key, "force_maximize") == 0) { rule->force_maximize = atoi(val); + } else if (strcmp(key, "force_tearing") == 0) { + rule->force_tearing = atoi(val); } else if (strcmp(key, "noswallow") == 0) { rule->noswallow = atoi(val); } else if (strcmp(key, "noblur") == 0) { @@ -2642,6 +2649,7 @@ void override_config(void) { xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); adaptive_sync = CLAMP_INT(config.adaptive_sync, 0, 1); + allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); axis_bind_apply_timeout = CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000); focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); @@ -2816,6 +2824,7 @@ void set_value_default() { config.xwayland_persistence = xwayland_persistence; config.syncobj_enable = syncobj_enable; config.adaptive_sync = adaptive_sync; + config.allow_tearing = allow_tearing; config.no_border_when_single = no_border_when_single; config.no_radius_when_single = no_radius_when_single; config.snap_distance = snap_distance; diff --git a/src/config/preset.h b/src/config/preset.h index 39cf509..2f994ec 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -103,6 +103,7 @@ int xwayland_persistence = 1; /* xwayland persistence */ int syncobj_enable = 0; int adaptive_sync = 0; double drag_refresh_interval = 30.0; +int allow_tearing = TEARING_DISABLED; /* keyboard */ diff --git a/src/ext-protocol/all.h b/src/ext-protocol/all.h index 0103248..bc690fe 100644 --- a/src/ext-protocol/all.h +++ b/src/ext-protocol/all.h @@ -1,4 +1,5 @@ #include "dwl-ipc.h" #include "ext-workspace.h" #include "foreign-toplevel.h" +#include "tearing.h" #include "text-input.h" \ No newline at end of file diff --git a/src/ext-protocol/tearing.h b/src/ext-protocol/tearing.h new file mode 100644 index 0000000..5ad36e8 --- /dev/null +++ b/src/ext-protocol/tearing.h @@ -0,0 +1,168 @@ +#include + +struct tearing_controller { + struct wlr_tearing_control_v1 *tearing_control; + struct wl_listener set_hint; + struct wl_listener destroy; +}; + +struct wlr_tearing_control_manager_v1 *tearing_control; +struct wl_listener tearing_new_object; + +static void handle_controller_set_hint(struct wl_listener *listener, + void *data) { + struct tearing_controller *controller = + wl_container_of(listener, controller, set_hint); + Client *c = get_client_from_surface(controller->tearing_control->surface); + if (c) { + /* + * tearing_control->current is actually an enum: + * WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC = 0 + * WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC = 1 + * + * Using it as a bool here allows us to not ship the XML. + */ + c->tearing_hint = controller->tearing_control->current; + } +} + +static void handle_controller_destroy(struct wl_listener *listener, + void *data) { + struct tearing_controller *controller = + wl_container_of(listener, controller, destroy); + wl_list_remove(&controller->set_hint.link); + wl_list_remove(&controller->destroy.link); + free(controller); +} + +void handle_tearing_new_object(struct wl_listener *listener, void *data) { + struct wlr_tearing_control_v1 *new_tearing_control = data; + + enum wp_tearing_control_v1_presentation_hint hint = + wlr_tearing_control_manager_v1_surface_hint_from_surface( + tearing_control, new_tearing_control->surface); + wlr_log(WLR_DEBUG, "New presentation hint %d received for surface %p", hint, + new_tearing_control->surface); + + struct tearing_controller *controller = + ecalloc(1, sizeof(struct tearing_controller)); + controller->tearing_control = new_tearing_control; + + controller->set_hint.notify = handle_controller_set_hint; + wl_signal_add(&new_tearing_control->events.set_hint, &controller->set_hint); + + controller->destroy.notify = handle_controller_destroy; + wl_signal_add(&new_tearing_control->events.destroy, &controller->destroy); +} + +bool check_tearing_frame_allow(Monitor *m) { + /* never allow tearing when disabled */ + if (!allow_tearing) { + return false; + } + + Client *c = selmon->sel; + + /* tearing is only allowed for the output with the active client */ + if (!c || c->mon != m) { + return false; + } + + /* allow tearing for any window when requested or forced */ + if (allow_tearing == TEARING_ENABLED) { + if (c->force_tearing == STATE_UNSPECIFIED) { + return c->tearing_hint; + } else { + return c->force_tearing == STATE_ENABLED; + } + } + + /* remaining tearing options apply only to full-screen windows */ + if (!c->isfullscreen) { + return false; + } + + if (c->force_tearing == STATE_UNSPECIFIED) { + /* honor the tearing hint or the fullscreen-force preference */ + return c->tearing_hint || allow_tearing == TEARING_FULLSCREEN_ONLY; + } + + /* honor tearing as requested by action */ + return c->force_tearing == STATE_ENABLED; +} + +bool custom_wlr_scene_output_commit(struct wlr_scene_output *scene_output, + struct wlr_output_state *state) { + struct wlr_output *wlr_output = scene_output->output; + Monitor *m = wlr_output->data; + + // 检查是否需要帧 + if (!wlr_scene_output_needs_frame(scene_output)) { + wlr_log(WLR_DEBUG, "No frame needed for output %s", wlr_output->name); + return true; + } + + // 构建输出状态 + if (!wlr_scene_output_build_state(scene_output, state, NULL)) { + wlr_log(WLR_ERROR, "Failed to build output state for %s", + wlr_output->name); + return false; + } + + // 测试撕裂翻页 + if (state->tearing_page_flip) { + if (!wlr_output_test_state(wlr_output, state)) { + state->tearing_page_flip = false; + } + } + + // 尝试提交 + bool committed = wlr_output_commit_state(wlr_output, state); + + // 如果启用撕裂翻页但提交失败,重试禁用撕裂翻页 + if (!committed && state->tearing_page_flip) { + wlr_log(WLR_DEBUG, "Retrying commit without tearing for %s", + wlr_output->name); + state->tearing_page_flip = false; + committed = wlr_output_commit_state(wlr_output, state); + } + + // 处理状态清理 + if (committed) { + wlr_log(WLR_DEBUG, "Successfully committed output %s", + wlr_output->name); + if (state == &m->pending) { + wlr_output_state_finish(&m->pending); + wlr_output_state_init(&m->pending); + } + } else { + wlr_log(WLR_ERROR, "Failed to commit output %s", wlr_output->name); + // 即使提交失败,也清理状态避免积累 + if (state == &m->pending) { + wlr_output_state_finish(&m->pending); + wlr_output_state_init(&m->pending); + } + return false; + } + + return true; +} + +void apply_tear_state(Monitor *m) { + if (wlr_scene_output_needs_frame(m->scene_output)) { + wlr_output_state_init(&m->pending); + if (wlr_scene_output_build_state(m->scene_output, &m->pending, NULL)) { + struct wlr_output_state *pending = &m->pending; + pending->tearing_page_flip = true; + + if (!custom_wlr_scene_output_commit(m->scene_output, pending)) { + wlr_log(WLR_ERROR, "Failed to commit output %s", + m->scene_output->output->name); + } + } else { + wlr_log(WLR_ERROR, "Failed to build state for output %s", + m->scene_output->output->name); + wlr_output_state_finish(&m->pending); + } + } +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 690775b..6e48eb1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -164,6 +164,13 @@ enum { UP, DOWN, LEFT, RIGHT, UNDIR }; /* smartmovewin */ enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS }; enum { UNFOLD, FOLD, INVALIDFOLD }; enum { PREV, NEXT }; +enum { STATE_UNSPECIFIED = 0, STATE_ENABLED, STATE_DISABLED }; + +enum tearing_mode { + TEARING_DISABLED = 0, + TEARING_ENABLED, + TEARING_FULLSCREEN_ONLY, +}; typedef struct Pertag Pertag; typedef struct Monitor Monitor; @@ -357,6 +364,8 @@ struct Client { bool ismaster; bool cursor_in_upper_half, cursor_in_left_half; bool isleftstack; + int tearing_hint; + int force_tearing; }; typedef struct { @@ -426,6 +435,7 @@ struct Monitor { struct wl_list link; struct wlr_output *wlr_output; struct wlr_scene_output *scene_output; + struct wlr_output_state pending; struct wl_listener frame; struct wl_listener destroy; struct wl_listener request_state; @@ -1117,6 +1127,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, isterm); APPLY_INT_PROP(c, r, allow_csd); APPLY_INT_PROP(c, r, force_maximize); + APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, noswallow); APPLY_INT_PROP(c, r, nofadein); APPLY_INT_PROP(c, r, nofadeout); @@ -1952,6 +1963,7 @@ void cleanuplisteners(void) { wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); wl_list_remove(&drm_lease_request.link); + wl_list_remove(&tearing_new_object.link); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); wl_list_remove(&xwayland_ready.link); @@ -2017,6 +2029,7 @@ void cleanupmon(struct wl_listener *listener, void *data) { wlr_scene_node_destroy(&m->blur->node); m->blur = NULL; } + m->wlr_output->data = NULL; free(m->pertag); free(m); } @@ -2546,6 +2559,7 @@ void createmon(struct wl_listener *listener, void *data) { m = wlr_output->data = ecalloc(1, sizeof(*m)); m->wlr_output = wlr_output; + m->wlr_output->data = m; wl_list_init(&m->dwl_ipc_outputs); @@ -3538,6 +3552,7 @@ void init_client_properties(Client *c) { c->isterm = 0; c->allow_csd = 0; c->force_maximize = 0; + c->force_tearing = 0; } void // old fix to 0.5 @@ -4066,10 +4081,11 @@ void rendermon(struct wl_listener *listener, void *data) { struct timespec now; bool need_more_frames = false; + bool frame_allow_tearing = check_tearing_frame_allow(m); + // 绘制层和淡出效果 for (i = 0; i < LENGTH(m->layers); i++) { layer_list = &m->layers[i]; - // Draw frames for all layer wl_list_for_each_safe(l, tmpl, layer_list, link) { need_more_frames = layer_draw_frame(l) || need_more_frames; } @@ -4083,25 +4099,34 @@ void rendermon(struct wl_listener *listener, void *data) { need_more_frames = layer_draw_fadeout_frame(l) || need_more_frames; } - // Draw frames for all clients + // 绘制客户端 wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; - if (!animations && c->configure_serial && !c->isfloating && - client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) + if (!animations && !allow_tearing && c->configure_serial && + !c->isfloating && client_is_rendered_on_mon(c, m) && + !client_is_stopped(c)) { goto skip; + } } - wlr_scene_output_commit(m->scene_output, NULL); + // 只有在需要帧时才构建和提交状态 + if (allow_tearing && frame_allow_tearing) { + apply_tear_state(m); + } else { + wlr_scene_output_commit(m->scene_output, NULL); + } skip: - - // Send frame done notification + // 发送帧完成通知 clock_gettime(CLOCK_MONOTONIC, &now); - wlr_scene_output_send_frame_done(m->scene_output, &now); - - // // Clean up pending state - wlr_output_state_finish(&pending); + if (allow_tearing && frame_allow_tearing) { + wlr_scene_output_send_frame_done(m->scene_output, &now); + } else { + wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); + } + // 如果需要更多帧,确保安排下一帧 if (need_more_frames) { request_fresh_all_monitors(); } @@ -4843,6 +4868,10 @@ void setup(void) { power_mgr = wlr_output_power_manager_v1_create(dpy); wl_signal_add(&power_mgr->events.set_mode, &output_power_mgr_set_mode); + tearing_control = wlr_tearing_control_manager_v1_create(dpy, 1); + tearing_new_object.notify = handle_tearing_new_object; + wl_signal_add(&tearing_control->events.new_object, &tearing_new_object); + /* Creates an output layout, which a wlroots utility for working with an * arrangement of screens in a physical layout. */ output_layout = wlr_output_layout_create(dpy);