aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFélix Poisot <felix@lhfblc.fr>2025-12-05 21:51:54 +0000
committerSimon Ser <contact@emersion.fr>2026-01-04 19:48:23 +0100
commit26eb393d6dd7631dc5b7edd0df95342d606e907c (patch)
tree3cbec86436580be9092bae42373d4dde9fea14b1
parent776d445ec5f8a8d5791511c3909d2b02de7c3c31 (diff)
sway/commands/output: add color_profile "--device-primaries"
When a display is connected, create a color transform from its self-reported color characteristics
-rw-r--r--include/sway/config.h8
-rw-r--r--sway/commands/output/color_profile.c18
-rw-r--r--sway/config/output.c59
-rw-r--r--sway/sway-output.5.scd16
4 files changed, 88 insertions, 13 deletions
diff --git a/include/sway/config.h b/include/sway/config.h
index 3c380933..16b822fe 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -267,6 +267,12 @@ enum render_bit_depth {
RENDER_BIT_DEPTH_10,
};
+enum color_profile {
+ COLOR_PROFILE_DEFAULT, // default is Transform with NULL color_transform
+ COLOR_PROFILE_TRANSFORM, // use color_transform from output_config
+ COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES, // create transform from wlr_output
+};
+
/**
* Size and position configuration for a particular output.
*
@@ -288,7 +294,7 @@ struct output_config {
int max_render_time; // In milliseconds
int adaptive_sync;
enum render_bit_depth render_bit_depth;
- bool set_color_transform;
+ enum color_profile color_profile;
struct wlr_color_transform *color_transform;
int allow_tearing;
int hdr;
diff --git a/sway/commands/output/color_profile.c b/sway/commands/output/color_profile.c
index b145d449..0456b19b 100644
--- a/sway/commands/output/color_profile.c
+++ b/sway/commands/output/color_profile.c
@@ -55,6 +55,14 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) {
if (!config->handler_context.output_config) {
return cmd_results_new(CMD_FAILURE, "Missing output config");
}
+
+ enum color_profile new_mode = COLOR_PROFILE_TRANSFORM;
+ if (argc >= 2 && strcmp(*argv, "--device-primaries") == 0) {
+ new_mode = COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES;
+ argc--;
+ argv++;
+ }
+
if (!argc) {
return cmd_results_new(CMD_INVALID, "Missing color_profile first argument.");
}
@@ -62,7 +70,7 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) {
if (strcmp(*argv, "gamma22") == 0) {
wlr_color_transform_unref(config->handler_context.output_config->color_transform);
config->handler_context.output_config->color_transform = NULL;
- config->handler_context.output_config->set_color_transform = true;
+ config->handler_context.output_config->color_profile = new_mode;
config->handler_context.leftovers.argc = argc - 1;
config->handler_context.leftovers.argv = argv + 1;
@@ -70,7 +78,7 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) {
wlr_color_transform_unref(config->handler_context.output_config->color_transform);
config->handler_context.output_config->color_transform =
wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_SRGB);
- config->handler_context.output_config->set_color_transform = true;
+ config->handler_context.output_config->color_profile = new_mode;
config->handler_context.leftovers.argc = argc - 1;
config->handler_context.leftovers.argv = argv + 1;
@@ -79,6 +87,10 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) {
return cmd_results_new(CMD_INVALID,
"Invalid color profile specification: icc type requires a file");
}
+ if (new_mode != COLOR_PROFILE_TRANSFORM) {
+ return cmd_results_new(CMD_INVALID,
+ "Invalid color profile specification: --device-primaries cannot be used with icc");
+ }
char *icc_path = strdup(argv[1]);
if (!expand_path(&icc_path)) {
@@ -108,7 +120,7 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) {
wlr_color_transform_unref(config->handler_context.output_config->color_transform);
config->handler_context.output_config->color_transform = tmp;
- config->handler_context.output_config->set_color_transform = true;
+ config->handler_context.output_config->color_profile = COLOR_PROFILE_TRANSFORM;
config->handler_context.leftovers.argc = argc - 2;
config->handler_context.leftovers.argv = argv + 2;
diff --git a/sway/config/output.c b/sway/config/output.c
index b884a785..3d25b46c 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -75,7 +75,7 @@ struct output_config *new_output_config(const char *name) {
oc->max_render_time = -1;
oc->adaptive_sync = -1;
oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT;
- oc->set_color_transform = false;
+ oc->color_profile = COLOR_PROFILE_DEFAULT;
oc->color_transform = NULL;
oc->power = -1;
oc->allow_tearing = -1;
@@ -130,12 +130,12 @@ static void supersede_output_config(struct output_config *dst, struct output_con
if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
dst->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT;
}
- if (src->set_color_transform) {
+ if (src->color_profile != COLOR_PROFILE_DEFAULT) {
if (dst->color_transform) {
wlr_color_transform_unref(dst->color_transform);
dst->color_transform = NULL;
}
- dst->set_color_transform = false;
+ dst->color_profile = COLOR_PROFILE_DEFAULT;
}
if (src->background) {
free(dst->background);
@@ -207,12 +207,12 @@ static void merge_output_config(struct output_config *dst, struct output_config
if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
dst->render_bit_depth = src->render_bit_depth;
}
- if (src->set_color_transform) {
+ if (src->color_profile != COLOR_PROFILE_DEFAULT) {
if (src->color_transform) {
wlr_color_transform_ref(src->color_transform);
}
wlr_color_transform_unref(dst->color_transform);
- dst->set_color_transform = true;
+ dst->color_profile = src->color_profile;
dst->color_transform = src->color_transform;
}
if (src->background) {
@@ -570,13 +570,60 @@ static void config_output_state_finish(struct config_output_state *state) {
wlr_color_transform_unref(state->color_transform);
}
+static struct wlr_color_transform *color_profile_from_device(struct wlr_output *wlr_output,
+ struct wlr_color_transform *transfer_function) {
+ struct wlr_color_primaries srgb_primaries;
+ wlr_color_primaries_from_named(&srgb_primaries, WLR_COLOR_NAMED_PRIMARIES_SRGB);
+
+ const struct wlr_color_primaries *primaries = wlr_output->default_primaries;
+ if (primaries == NULL) {
+ sway_log(SWAY_INFO, "output has no reported color information");
+ if (transfer_function) {
+ wlr_color_transform_ref(transfer_function);
+ }
+ return transfer_function;
+ } else if (memcmp(primaries, &srgb_primaries, sizeof(*primaries)) == 0) {
+ sway_log(SWAY_INFO, "output reports sRGB colors, no correction needed");
+ if (transfer_function) {
+ wlr_color_transform_ref(transfer_function);
+ }
+ return transfer_function;
+ } else {
+ sway_log(SWAY_INFO, "Creating color profile from reported color primaries: "
+ "R(%f, %f) G(%f, %f) B(%f, %f) W(%f, %f)",
+ primaries->red.x, primaries->red.y, primaries->green.x, primaries->green.y,
+ primaries->blue.x, primaries->blue.y, primaries->white.x, primaries->white.y);
+ float matrix[9];
+ wlr_color_primaries_transform_absolute_colorimetric(&srgb_primaries, primaries, matrix);
+ struct wlr_color_transform *matrix_transform = wlr_color_transform_init_matrix(matrix);
+ if (matrix_transform == NULL) {
+ return NULL;
+ }
+ struct wlr_color_transform *resolved_tf = transfer_function ?
+ wlr_color_transform_ref(transfer_function) :
+ wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_GAMMA22);
+ if (resolved_tf == NULL) {
+ wlr_color_transform_unref(matrix_transform);
+ return NULL;
+ }
+ struct wlr_color_transform *transforms[] = { matrix_transform, resolved_tf };
+ size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]);
+ struct wlr_color_transform *result = wlr_color_transform_init_pipeline(transforms, transforms_len);
+ wlr_color_transform_unref(matrix_transform);
+ wlr_color_transform_unref(resolved_tf);
+ return result;
+ }
+}
+
static struct wlr_color_transform *get_color_profile(struct wlr_output *output,
struct output_config *oc) {
- if (oc && oc->set_color_transform) {
+ if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM) {
if (oc->color_transform) {
wlr_color_transform_ref(oc->color_transform);
}
return oc->color_transform;
+ } else if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES) {
+ return color_profile_from_device(output, oc->color_transform);
} else {
return NULL;
}
diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd
index afe21364..faf59a1f 100644
--- a/sway/sway-output.5.scd
+++ b/sway/sway-output.5.scd
@@ -178,9 +178,19 @@ must be separated by one space. For example:
updated to work with different bit depths. This command is experimental,
and may be removed or changed in the future.
-*output* <name> color_profile gamma22|srgb|[icc <file>]
- Sets the color profile for an output. The default is _gamma22_. <file> should be a
- path to a display ICC profile.
+*output* <name> color_profile [--device-primaries] gamma22|srgb
+ Sets the color profile for an output. The default is _gamma22_.
+
+ _--device-primaries_ will use the output's self-reported color primaries
+ when available (e.g. from display EDID).
+
+ Not all renderers support this feature; currently it only works with the
+ the Vulkan renderer. It is not compatible with HDR support features.
+
+*output* <name> color_profile icc <file>
+ Sets the color profile for an output.
+
+ <file> should be a path to a display ICC profile.
Not all renderers support this feature; currently it only works with the
the Vulkan renderer. Even where supported, the application of the color