diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index cd8d167d..98a5f747 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -12,6 +12,7 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | | `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | | `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | +| `hdr_hdr_depth` | `0`/`1`/`2` | Set the hdr depth for the current display. `0` is Default, `1` is HDR8, `2` is HDR10. | ## Focus & Input diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md index a8001ae0..ff64bc10 100644 --- a/docs/configuration/monitors.md +++ b/docs/configuration/monitors.md @@ -30,6 +30,7 @@ monitorrule=name:Values,Parameter:Values,Parameter:Values | `y` | integer | 0-99999 | Y position | | `scale` | float | 0.01-100.0 | Monitor scale | | `vrr` | integer | 0, 1 | Enable variable refresh rate | +| `hdr` | integer | 0, 1 | Enable hdr support | | `rr` | integer | 0-7 | Monitor transform | | `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | diff --git a/src/action/client.h b/src/action/client.h index 85ab1aaf..22072a09 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -265,4 +265,9 @@ void client_set_group_mon(Client *c, Monitor *m) { client_change_mon(cur, m); cur = cur->group_next; } +} + +void handle_client_focus_change(Client *c) { + check_keep_idle_inhibit(c); + check_vrr_enable(c); } \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 457408b6..fccbbef9 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -22,6 +22,11 @@ enum { NUM_TYPE_MINUS, NUM_TYPE_PLUS, NUM_TYPE_DEFAULT }; enum { KEY_TYPE_CODE, KEY_TYPE_SYM }; +enum render_bit_depth { + MANGO_RENDER_BIT_DEPTH_DEFAULT = 0, + MANGO_RENDER_BIT_DEPTH_8, + MANGO_RENDER_BIT_DEPTH_10, +}; typedef struct { uint32_t keycode1; uint32_t keycode2; @@ -112,6 +117,7 @@ typedef struct { float refresh; // Refresh rate int32_t vrr; // variable refresh rate int32_t custom; // enable custom mode + int32_t hdr; // enable hdr mode } ConfigMonitorRule; // 修改后的宏定义 @@ -391,6 +397,8 @@ typedef struct { struct xkb_keymap *keymap; DecorateDrawData jumplabeldata; DecorateDrawData tabdata; + + int32_t hdr_depth; } Config; typedef int32_t (*FuncType)(const Arg *); @@ -1464,6 +1472,8 @@ bool parse_option(Config *config, char *key, char *value) { config->drag_floating_refresh_interval = atof(value); } else if (strcmp(key, "allow_tearing") == 0) { config->allow_tearing = atoi(value); + } else if (strcmp(key, "hdr_depth") == 0) { + config->hdr_depth = atoi(value); } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { config->allow_shortcuts_inhibit = atoi(value); } else if (strcmp(key, "allow_lock_transparent") == 0) { @@ -2047,6 +2057,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->height = -1; rule->refresh = 0.0f; rule->vrr = 0; + rule->hdr = 0; rule->custom = 0; bool parse_error = false; @@ -2085,6 +2096,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f); } else if (strcmp(key, "vrr") == 0) { rule->vrr = CLAMP_INT(atoi(val), 0, 1); + } else if (strcmp(key, "hdr") == 0) { + rule->hdr = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "custom") == 0) { rule->custom = CLAMP_INT(atoi(val), 0, 1); } else { @@ -3421,6 +3434,7 @@ void override_config(void) { config.drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); config.drag_tile_small = CLAMP_INT(config.drag_tile_small, 0, 1); config.allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); + config.hdr_depth = CLAMP_INT(config.hdr_depth, 0, 2); config.allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); config.allow_lock_transparent = @@ -3588,6 +3602,7 @@ void set_value_default() { config.drag_tile_refresh_interval = 8.0f; config.drag_floating_refresh_interval = 8.0f; config.allow_tearing = TEARING_DISABLED; + config.hdr_depth = MANGO_RENDER_BIT_DEPTH_DEFAULT; config.allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; config.allow_lock_transparent = 0; config.no_border_when_single = 0; @@ -3868,13 +3883,12 @@ void reapply_monitor_rules(void) { Monitor *m = NULL; int32_t ji, vrr, custom; int32_t mx, my; - struct wlr_output_state state; wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) continue; - wlr_output_state_init(&state); + wlr_output_state_init(&m->pending); for (ji = 0; ji < config.monitor_rules_count; ji++) { if (config.monitor_rules_count < 1) @@ -3887,16 +3901,31 @@ void reapply_monitor_rules(void) { my = mr->y == INT32_MAX ? m->m.y : mr->y; vrr = mr->vrr >= 0 ? mr->vrr : 0; custom = mr->custom >= 0 ? mr->custom : 0; + m->hdr_enable = mr->hdr >= 0 ? mr->hdr : 0; - (void)apply_rule_to_state(m, mr, &state, vrr, custom); + (void)apply_rule_to_state(m, mr, &m->pending, vrr, custom); wlr_output_layout_add(output_layout, m->wlr_output, mx, my); - wlr_output_commit_state(m->wlr_output, &state); break; } } - wlr_output_state_finish(&state); + wlr_output_state_set_enabled(&m->pending, true); + + if (m->hdr_enable) { + output_state_setup_hdr(m, false); + bool success = wlr_output_commit_state(m->wlr_output, &m->pending); + if (!success) { // 多尝试一次 + output_state_setup_hdr(m, true); + } + } else { + wlr_output_commit_state(m->wlr_output, &m->pending); + } + + wlr_output_state_finish(&m->pending); + wlr_output_effective_resolution(m->wlr_output, &m->m.width, + &m->m.height); } + updatemons(NULL, NULL); } diff --git a/src/ext-protocol/all.h b/src/ext-protocol/all.h index fac2779d..e9923c35 100644 --- a/src/ext-protocol/all.h +++ b/src/ext-protocol/all.h @@ -1,6 +1,7 @@ #include "dwl-ipc.h" #include "ext-workspace.h" #include "foreign-toplevel.h" +#include "hdr.h" #include "tablet.h" #include "tearing.h" #include "text-input.h" diff --git a/src/ext-protocol/hdr.h b/src/ext-protocol/hdr.h new file mode 100644 index 00000000..2f2ff9a3 --- /dev/null +++ b/src/ext-protocol/hdr.h @@ -0,0 +1,128 @@ +#include + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static uint32_t output_formats_8bit[] = { + DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888, DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, +}; + +static uint32_t output_formats_10bit[] = { + DRM_FORMAT_XRGB2101010, DRM_FORMAT_XBGR2101010, DRM_FORMAT_RGBX1010102, + DRM_FORMAT_BGRX1010102, DRM_FORMAT_ARGB2101010, DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, DRM_FORMAT_BGRA1010102, +}; + +static bool output_set_render_format(Monitor *m, uint32_t candidates[], + size_t count) { + for (size_t i = 0; i < count; i++) { + wlr_output_state_set_render_format(&m->pending, candidates[i]); + if (wlr_output_test_state(m->wlr_output, &m->pending)) + return true; + } + return false; +} + +static bool output_format_in_candidates(uint32_t format, uint32_t candidates[], + size_t count) { + for (size_t i = 0; i < count; i++) + if (candidates[i] == format) + return true; + return false; +} + +static enum render_bit_depth bit_depth_from_format(uint32_t render_format) { + if (output_format_in_candidates(render_format, output_formats_10bit, + ARRAY_SIZE(output_formats_10bit))) + return MANGO_RENDER_BIT_DEPTH_10; + if (output_format_in_candidates(render_format, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) + return MANGO_RENDER_BIT_DEPTH_8; + return MANGO_RENDER_BIT_DEPTH_DEFAULT; +} + +static bool output_supports_hdr(const struct wlr_output *output, + const char **reason) { + const char *r = NULL; + if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020)) + r = "BT2020 primaries not supported"; + else if (!(output->supported_transfer_functions & + WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ)) + r = "PQ transfer function not supported"; + else if (!drw->features.output_color_transform) + r = "renderer doesn't support output color transforms"; + if (reason) + *reason = r; + return !r; +} + +void output_enable_hdr(Monitor *m, struct wlr_output_state *os, bool enabled, + bool silent) { + if (enabled && !output_supports_hdr(m->wlr_output, NULL)) + enabled = false; + + if (!enabled) { + if (m->wlr_output->supported_primaries || + m->wlr_output->supported_transfer_functions) { + if (!silent) + wlr_log(WLR_DEBUG, "Disabling HDR on output %s", + m->wlr_output->name); + wlr_output_state_set_image_description(os, NULL); + } + return; + } + + if (!silent) + wlr_log(WLR_DEBUG, "Enabling HDR on output %s", m->wlr_output->name); + struct wlr_output_image_description desc = { + .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020, + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ, + }; + wlr_output_state_set_image_description(os, &desc); +} + +void output_state_setup_hdr(Monitor *m, bool silent) { + uint32_t render_format = m->wlr_output->render_format; + const char *unsupported_reason = NULL; + bool hdr_supported = + output_supports_hdr(m->wlr_output, &unsupported_reason); + bool hdr_succeeded = false; + + enum render_bit_depth depth = config.hdr_depth; + if (depth == MANGO_RENDER_BIT_DEPTH_DEFAULT) + depth = bit_depth_from_format(render_format); + + if (!hdr_supported && depth == MANGO_RENDER_BIT_DEPTH_10) { + if (!silent) + wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s", + m->wlr_output->name, unsupported_reason); + depth = MANGO_RENDER_BIT_DEPTH_8; + } + + if (depth == MANGO_RENDER_BIT_DEPTH_10 && + bit_depth_from_format(render_format) == depth) { + hdr_succeeded = true; // 上次已经成功设置10位,直接复用 + } else if (depth == MANGO_RENDER_BIT_DEPTH_10) { + hdr_succeeded = output_set_render_format( + m, output_formats_10bit, ARRAY_SIZE(output_formats_10bit)); + if (!hdr_succeeded) { + if (!silent) + wlr_log(WLR_INFO, + "No 10 bit color formats supported, HDR disabled."); + if (!output_set_render_format(m, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) + if (!silent) + wlr_log(WLR_ERROR, "No 8 bit color formats either!"); + } + } else { + // 明确要求8位或自动降级 + if (!output_set_render_format(m, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) + if (!silent) + wlr_log(WLR_ERROR, "No 8 bit color formats supported!"); + } + + output_enable_hdr(m, &m->pending, hdr_succeeded, silent); +} \ No newline at end of file diff --git a/src/ext-protocol/tearing.h b/src/ext-protocol/tearing.h index 5a9851e4..02394eaa 100644 --- a/src/ext-protocol/tearing.h +++ b/src/ext-protocol/tearing.h @@ -100,73 +100,44 @@ bool custom_wlr_scene_output_commit(struct wlr_scene_output *scene_output, 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); + if (!wlr_scene_output_needs_frame(scene_output)) 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); + // 构建状态,将场景的 Buffer 附着到 state 上 + if (!wlr_scene_output_build_state(scene_output, state, NULL)) return false; + + // 测试是否支持撕裂 + if (!wlr_output_test_state(wlr_output, state)) { + // 如果 DRM 拒绝(例如当前输出/驱动不支持撕裂),降级关闭撕裂 + state->tearing_page_flip = 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; + return committed; } 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 (!wlr_scene_output_needs_frame(m->scene_output)) + return; - 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); - } + wlr_output_state_init(&m->pending); + m->pending.tearing_page_flip = true; + if (!custom_wlr_scene_output_commit(m->scene_output, &m->pending)) { + wlr_log(WLR_ERROR, "Failed to commit output %s", + m->scene_output->output->name); } } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index b5c4e493..d7353844 100644 --- a/src/mango.c +++ b/src/mango.c @@ -586,6 +586,7 @@ struct Monitor { int8_t carousel_anim_dir; bool vrr_global_enable; bool is_vrr_opening; + bool hdr_enable; }; typedef struct { @@ -947,6 +948,9 @@ static void global_draw_group_bar(Client *c, int32_t x, int32_t y, static void client_reparent_group(Client *c); static void client_change_mon(Client *c, Monitor *m); static void check_vrr_enable(Client *c); +static void output_state_setup_hdr(Monitor *m, bool silent); +static void output_enable_hdr(Monitor *m, struct wlr_output_state *os, + bool enabled, bool silent); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -3346,7 +3350,6 @@ void createmon(struct wl_listener *listener, void *data) { const ConfigMonitorRule *r; uint32_t i; int32_t ji, vrr, custom; - struct wlr_output_state state; Monitor *m = NULL; bool custom_monitor_mode = false; @@ -3374,6 +3377,8 @@ void createmon(struct wl_listener *listener, void *data) { m->vrr_global_enable = false; m->is_vrr_opening = false; + m->hdr_enable = false; + m->wlr_output = wlr_output; m->wlr_output->data = m; @@ -3394,9 +3399,9 @@ void createmon(struct wl_listener *listener, void *data) { float scale = 1; enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL; - wlr_output_state_init(&state); - wlr_output_state_set_scale(&state, scale); - wlr_output_state_set_transform(&state, rr); + wlr_output_state_init(&m->pending); + wlr_output_state_set_scale(&m->pending, scale); + wlr_output_state_set_transform(&m->pending, rr); for (ji = 0; ji < config.monitor_rules_count; ji++) { if (config.monitor_rules_count < 1) @@ -3411,8 +3416,9 @@ void createmon(struct wl_listener *listener, void *data) { custom = r->custom >= 0 ? r->custom : 0; scale = r->scale; rr = r->rr; + m->hdr_enable = r->hdr; - if (apply_rule_to_state(m, r, &state, vrr, custom)) { + if (apply_rule_to_state(m, r, &m->pending, vrr, custom)) { custom_monitor_mode = true; } break; // 只应用第一个匹配规则 @@ -3420,7 +3426,7 @@ void createmon(struct wl_listener *listener, void *data) { } if (!custom_monitor_mode) - wlr_output_state_set_mode(&state, + wlr_output_state_set_mode(&m->pending, wlr_output_preferred_mode(wlr_output)); /* Set up event listeners */ @@ -3429,9 +3435,16 @@ void createmon(struct wl_listener *listener, void *data) { LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); - wlr_output_state_set_enabled(&state, 1); - wlr_output_commit_state(wlr_output, &state); - wlr_output_state_finish(&state); + wlr_output_state_set_enabled(&m->pending, 1); + + if (m->hdr_enable) { + output_state_setup_hdr(m, false); + } + + wlr_output_commit_state(wlr_output, &m->pending); + wlr_output_state_finish(&m->pending); + + wlr_output_effective_resolution(m->wlr_output, &m->m.width, &m->m.height); wl_list_insert(&mons, &m->link); @@ -3908,8 +3921,7 @@ void focusclient(Client *c, int32_t lift) { selmon->sel = c; c->isfocusing = true; - check_keep_idle_inhibit(c); - check_vrr_enable(c); + handle_client_focus_change(c); if (last_focus_client && !last_focus_client->iskilling && last_focus_client != c) {