diff --git a/include/sway/commands.h b/include/sway/commands.h index 2705858703..0a9fdc7059 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 f9da19675c..e132b4085e 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; + int has_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 50d90d25dc..ae83700c85 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -53,6 +53,9 @@ struct sway_output { struct wl_signal disable; } events; + bool has_color_transform; + 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 df32c67318..2f2293bfa7 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 0000000000..28f1e3f6cd --- /dev/null +++ b/sway/commands/output/color_profile.c @@ -0,0 +1,108 @@ +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include +#include "log.h" +#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, (void *)(b + nread), to_read); + if ((r == -1 && errno != EINTR) || r == 0) { + goto fail; + } + nread += (size_t)r; + } + *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) { + if (config->handler_context.output_config->has_color_transform == 1) { + wlr_color_transform_unref(config->handler_context.output_config->color_transform); + } + config->handler_context.output_config->color_transform = NULL; + config->handler_context.output_config->has_color_transform = 0; + + 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_INVALID, + "Failed to read color profile 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_INVALID, + "Invalid color profile specification; failed to initialize transform from ICC"); + } + free(data); + + if (config->handler_context.output_config->has_color_transform == 1) { + wlr_color_transform_unref(config->handler_context.output_config->color_transform); + } + config->handler_context.output_config->color_transform = tmp; + config->handler_context.output_config->has_color_transform = 1; + + sway_log(SWAY_ERROR, "ICC profile loaded and set up"); + + 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 6fb29ded98..e7a12abc0e 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -75,6 +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->has_color_transform = -1; oc->power = -1; return oc; } @@ -125,6 +126,17 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { dst->render_bit_depth = src->render_bit_depth; } + if (src->has_color_transform != -1) { + if (src->has_color_transform == 1) { + wlr_color_transform_ref(src->color_transform); + } + if (dst->has_color_transform == 1) { + wlr_color_transform_unref(dst->color_transform); + } + dst->has_color_transform = src->has_color_transform; + dst->color_transform = src->color_transform; + + } if (src->background) { free(dst->background); dst->background = strdup(src->background); @@ -584,6 +596,17 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { output->max_render_time = oc->max_render_time; } + if (oc && oc->has_color_transform >= 0) { + if (oc->has_color_transform == 1) { + wlr_color_transform_ref(oc->color_transform); + } + if (output->has_color_transform) { + wlr_color_transform_unref(output->color_transform); + } + output->has_color_transform = oc->has_color_transform; + output->color_transform = oc->color_transform; + } + // Reconfigure all devices, since input config may have been applied before // this output came online, and some config items (like map_to_output) are // dependent on an output being present. @@ -765,6 +788,9 @@ void free_output_config(struct output_config *oc) { free(oc->name); free(oc->background); free(oc->background_option); + if (oc->has_color_transform == 1) { + wlr_color_transform_unref(oc->color_transform); + } free(oc); } diff --git a/sway/desktop/output.c b/sway/desktop/output.c index f6ff90ef44..826340005c 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -633,8 +633,13 @@ static int output_repaint_timer_handler(void *data) { goto out; } + struct wlr_buffer_pass_options opts = { + .timer = NULL, + .color_transform = output->has_color_transform ? output->color_transform : NULL, + }; + struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass( - wlr_output->renderer, buffer, NULL); + wlr_output->renderer, buffer, &opts); if (render_pass == NULL) { wlr_buffer_unlock(buffer); goto out; diff --git a/sway/meson.build b/sway/meson.build index 3abd778db4..772488a73f 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -203,6 +203,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 028cb7abc3..a7e18c9fba 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd @@ -180,6 +180,16 @@ 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 works. 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. + # SEE ALSO *sway*(5) *sway-input*(5) diff --git a/sway/tree/output.c b/sway/tree/output.c index eccab2f72c..144b6a496e 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -241,6 +241,9 @@ void output_destroy(struct sway_output *output) { list_free(output->workspaces); list_free(output->current.workspaces); wl_event_source_remove(output->repaint_timer); + if (output->has_color_transform) { + wlr_color_transform_unref(output->color_transform); + } free(output); }