sway/commands/output: Add command to set color profile

This makes it possible to render output buffers in a different color
space, by specifying an ICC profile for the output.
This commit is contained in:
Manuel Stoeckl 2023-07-17 21:40:28 -04:00 committed by Simon Ser
parent 2e9139df66
commit 40ca4150b2
13 changed files with 147 additions and 1 deletions

View file

@ -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 },

View file

@ -0,0 +1,101 @@
#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;
}