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 90be17e..e692938 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/preset.h b/src/config/preset.h index d9425a7..dfc37b0 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -101,6 +101,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..eae9ff6 --- /dev/null +++ b/src/ext-protocol/tearing.h @@ -0,0 +1,106 @@ +#include + +struct tearing_controller { + struct wlr_tearing_control_v1 *tearing_control; + struct wl_listener set_hint; + struct wl_listener destroy; +}; + +typedef struct SendFrameDoneData { + struct timespec when; + struct Monitor *mon; +} SendFrameDoneData; + +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 output_get_tearing_allowance(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_FORCED; + } + + /* honor tearing as requested by action */ + return c->force_tearing == STATE_ENABLED; +} + +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); +} diff --git a/src/mango.c b/src/mango.c index 2e29017..e9386f4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -164,6 +164,14 @@ enum { UP, DOWN, LEFT, RIGHT, UNDIR }; /* smartmovewin */ enum { NONE, OPEN, MOVE, CLOSE, TAG }; 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, + TEARING_FULLSCREEN_FORCED, +}; typedef struct Pertag Pertag; typedef struct Monitor Monitor; @@ -347,6 +355,8 @@ struct Client { bool ismaster; bool cursor_in_upper_half, cursor_in_left_half; bool isleftstack; + bool tearing_hint; + bool force_tearing; }; typedef struct { @@ -1940,6 +1950,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); @@ -4078,10 +4089,27 @@ void rendermon(struct wl_listener *listener, void *data) { struct timespec now; bool need_more_frames = false; + bool skip_commit = false; // 新增:控制是否跳过提交 + struct wlr_scene_output *scene_output = m->scene_output; + SendFrameDoneData frame_done_data = {0}; + + m->wlr_output->frame_pending = false; + + if (!wlr_scene_output_needs_frame(scene_output)) { + goto skip; + } + + // // 初始化输出状态 + wlr_output_state_init(&pending); + if (!wlr_scene_output_build_state(m->scene_output, &pending, NULL)) { + wlr_output_state_finish(&pending); + goto skip; + } + + // 绘制层和淡出效果 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; } @@ -4095,24 +4123,43 @@ 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)) - goto skip; + if (!animations && allow_tearing && c->configure_serial && + !c->isfloating && client_is_rendered_on_mon(c, m) && + !client_is_stopped(c)) { + skip_commit = true; // 标记跳过提交,但不中断循环 + } } - wlr_scene_output_commit(m->scene_output, NULL); + // 如果不需要跳过提交,才进行实际提交 + if (!skip_commit) { + if (output_get_tearing_allowance(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: - - // 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); + frame_done_data.mon = m; + wlr_scene_output_for_each_buffer(m->scene_output, sendframedoneiterator, + &frame_done_data); if (need_more_frames) { wlr_output_schedule_frame(m->wlr_output); @@ -4900,6 +4947,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);