Skip to content

Commit

Permalink
sway/commands/output: Add command to set color profile
Browse files Browse the repository at this point in the history
This makes it possible to render output buffers in a different color
space, by specifying an ICC profile for the output.
  • Loading branch information
mstoeckl committed May 8, 2024
1 parent dcdb727 commit 88ae791
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 1 deletion.
1 change: 1 addition & 0 deletions include/sway/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions include/sway/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <wlr/interfaces/wlr_switch.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/util/box.h>
#include <wlr/render/color.h>
#include <xkbcommon/xkbcommon.h>
#include <xf86drmMode.h>
#include "../include/config.h"
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions include/sway/output.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions sway/commands/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
103 changes: 103 additions & 0 deletions sway/commands/output/color_profile.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#define _XOPEN_SOURCE 700
#include <fcntl.h>
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wlr/render/color.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, 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;
}

18 changes: 18 additions & 0 deletions sway/config/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,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;
}
Expand Down Expand Up @@ -190,6 +192,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);
Expand Down Expand Up @@ -556,6 +566,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;
}

Expand Down Expand Up @@ -996,6 +1013,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);
}

Expand Down
5 changes: 4 additions & 1 deletion sway/desktop/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions sway/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
12 changes: 12 additions & 0 deletions sway/sway-output.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -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* <name> color_profile srgb|[icc <file>]
Sets the color profile for an output. The default is _srgb_. <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 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. It
may have no effect or produce unexpected output when used together with future
HDR support features.

# SEE ALSO

*sway*(5) *sway-input*(5)
1 change: 1 addition & 0 deletions sway/tree/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down

0 comments on commit 88ae791

Please sign in to comment.