diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 055e5ffa..7a1fa58e 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -4,6 +4,7 @@ packages: - eudev-dev - gdk-pixbuf-dev - json-c-dev + - lcms2-dev - libdisplay-info-dev - libevdev-dev - libinput-dev diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 9972c01a..e249571e 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -3,6 +3,7 @@ packages: - cairo - gdk-pixbuf2 - json-c + - lcms2 - libdisplay-info - libegl - libinput diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 29c6312a..8084574c 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -8,6 +8,7 @@ packages: - devel/pkgconf - graphics/cairo - graphics/gdk-pixbuf2 +- graphics/lcms2 - graphics/wayland - graphics/wayland-protocols - textproc/scdoc diff --git a/include/sway/commands.h b/include/sway/commands.h index 27058587..0a9fdc70 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -283,6 +283,7 @@ sway_cmd input_cmd_xkb_variant; sway_cmd output_cmd_adaptive_sync; sway_cmd output_cmd_background; +sway_cmd output_cmd_color_profile; sway_cmd output_cmd_disable; sway_cmd output_cmd_dpms; sway_cmd output_cmd_enable; diff --git a/include/sway/config.h b/include/sway/config.h index 5ccc3e77..3e3a104e 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include "../include/config.h" @@ -285,6 +286,8 @@ struct output_config { int max_render_time; // In milliseconds int adaptive_sync; enum render_bit_depth render_bit_depth; + bool set_color_transform; + struct wlr_color_transform *color_transform; char *background; char *background_option; diff --git a/include/sway/output.h b/include/sway/output.h index d546d488..2189c6e8 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -66,6 +66,8 @@ struct sway_output { struct wl_signal disable; } events; + struct wlr_color_transform *color_transform; + struct timespec last_presentation; uint32_t refresh_nsec; int max_render_time; // In milliseconds diff --git a/sway/commands/output.c b/sway/commands/output.c index 5e5d31b3..b822e770 100644 --- a/sway/commands/output.c +++ b/sway/commands/output.c @@ -10,6 +10,7 @@ static const struct cmd_handler output_handlers[] = { { "adaptive_sync", output_cmd_adaptive_sync }, { "background", output_cmd_background }, { "bg", output_cmd_background }, + { "color_profile", output_cmd_color_profile }, { "disable", output_cmd_disable }, { "dpms", output_cmd_dpms }, { "enable", output_cmd_enable }, diff --git a/sway/commands/output/color_profile.c b/sway/commands/output/color_profile.c new file mode 100644 index 00000000..792bd55f --- /dev/null +++ b/sway/commands/output/color_profile.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include "sway/commands.h" +#include "sway/config.h" + +static bool read_file_into_buf(const char *path, void **buf, size_t *size) { + /* Why not use fopen/fread directly? glibc will succesfully open directories, + * not just files, and supports seeking on them. Instead, we directly + * work with file descriptors and use the more consistent open/fstat/read. */ + int fd = open(path, O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (fd == -1) { + return false; + } + char *b = NULL; + struct stat info; + if (fstat(fd, &info) == -1) { + goto fail; + } + // only regular files, to avoid issues with e.g. opening pipes + if (!S_ISREG(info.st_mode)) { + goto fail; + } + off_t s = info.st_size; + if (s <= 0) { + goto fail; + } + b = calloc(1, s); + if (!b) { + goto fail; + } + size_t nread = 0; + while (nread < (size_t)s) { + size_t to_read = (size_t)s - nread; + ssize_t r = read(fd, b + nread, to_read); + if ((r == -1 && errno != EINTR) || r == 0) { + goto fail; + } + nread += (size_t)r; + } + close(fd); + *buf = b; + *size = (size_t)s; + return true; // success +fail: + free(b); + close(fd); + return false; +} + +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"); + } + if (!argc) { + return cmd_results_new(CMD_INVALID, "Missing color_profile first argument."); + } + + if (strcmp(*argv, "srgb") == 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.leftovers.argc = argc - 1; + config->handler_context.leftovers.argv = argv + 1; + } else if (strcmp(*argv, "icc") == 0) { + if (argc < 2) { + return cmd_results_new(CMD_INVALID, + "Invalid color profile specification: icc type requires a file"); + } + void *data = NULL; + size_t size = 0; + if (!read_file_into_buf(argv[1], &data, &size)) { + return cmd_results_new(CMD_FAILURE, + "Failed to load color profile: could not read ICC file"); + } + + struct wlr_color_transform *tmp = + wlr_color_transform_init_linear_to_icc(data, size); + if (!tmp) { + free(data); + return cmd_results_new(CMD_FAILURE, + "Failed to load color profile: failed to initialize transform from ICC"); + } + free(data); + + 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.leftovers.argc = argc - 2; + config->handler_context.leftovers.argv = argv + 2; + } else { + return cmd_results_new(CMD_INVALID, + "Invalid color profile specification: first argument should be icc|srgb"); + } + + return NULL; +} diff --git a/sway/config/output.c b/sway/config/output.c index 9a447388..bcd21b9b 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -76,6 +76,8 @@ 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_transform = NULL; oc->power = -1; return oc; } @@ -191,6 +193,14 @@ 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_transform) { + wlr_color_transform_ref(src->color_transform); + } + wlr_color_transform_unref(dst->color_transform); + dst->set_color_transform = true; + dst->color_transform = src->color_transform; + } if (src->background) { free(dst->background); dst->background = strdup(src->background); @@ -557,6 +567,13 @@ static bool finalize_output_config(struct output_config *oc, struct sway_output output->max_render_time = oc->max_render_time; } + if (oc && oc->set_color_transform) { + if (oc->color_transform) { + wlr_color_transform_ref(oc->color_transform); + } + wlr_color_transform_unref(output->color_transform); + output->color_transform = oc->color_transform; + } return true; } @@ -997,6 +1014,7 @@ void free_output_config(struct output_config *oc) { free(oc->name); free(oc->background); free(oc->background_option); + wlr_color_transform_unref(oc->color_transform); free(oc); } diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 2722e556..cfa53021 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -269,7 +269,10 @@ static int output_repaint_timer_handler(void *data) { return 0; } - wlr_scene_output_commit(output->scene_output, NULL); + struct wlr_scene_output_state_options opts = { + .color_transform = output->color_transform, + }; + wlr_scene_output_commit(output->scene_output, &opts); return 0; } diff --git a/sway/meson.build b/sway/meson.build index 47b51d0c..a189fe9a 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -202,6 +202,7 @@ sway_sources = files( 'commands/output/toggle.c', 'commands/output/transform.c', 'commands/output/unplug.c', + 'commands/output/color_profile.c', 'tree/arrange.c', 'tree/container.c', diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd index 7d088d5d..6d7c0860 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd @@ -178,6 +178,18 @@ 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* color_profile srgb|[icc ] + Sets the color profile for an output. The default is _srgb_. 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 + profile may be inaccurate. + + This command is experimental, and may be removed or changed in the future. It + may have no effect or produce unexpected output when used together with future + HDR support features. + # SEE ALSO *sway*(5) *sway-input*(5) diff --git a/sway/tree/output.c b/sway/tree/output.c index 2d11195e..6c8dd6dc 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -279,6 +279,7 @@ void output_destroy(struct sway_output *output) { list_free(output->workspaces); list_free(output->current.workspaces); wl_event_source_remove(output->repaint_timer); + wlr_color_transform_unref(output->color_transform); free(output); }