/* See LICENSE file for copyright and license details. */
#include "servers-coopgamma.h"
#include "servers-gamma.h"
#include "state.h"
#include "communication.h"
#include "util.h"
#include "types-output.h"
#include <libclut.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Apply a filter on top of another filter
*
* @param dest The output for the resulting ramp-trio, must be initialised
* @param application The red, green and blue ramps, as one single raw array,
* of the filter that should be applied
* @param depth -1: `float` stops
* -2: `double` stops
* Other: the number of bits of each (integral) stop
* @param base The CLUT on top of which the new filter should be applied,
* this can be the same pointer as `dest`
*/
static void
apply_filter(union gamma_ramps *dest, void *restrict application, int depth, union gamma_ramps *base)
{
union gamma_ramps app;
size_t bytedepth;
size_t red_width, green_width, blue_width;
if (depth == -1)
bytedepth = sizeof(float);
else if (depth == -2)
bytedepth = sizeof(double);
else
bytedepth = (size_t)depth / 8;
red_width = (app.u8.red_size = base->u8.red_size) * bytedepth;
green_width = (app.u8.green_size = base->u8.green_size) * bytedepth;
blue_width = (app.u8.blue_size = base->u8.blue_size) * bytedepth;
app.u8.red = application;
app.u8.green = &app.u8.red[red_width];
app.u8.blue = &app.u8.green[green_width];
if (dest != base) {
memcpy(dest->u8.red, base->u8.red, red_width);
memcpy(dest->u8.green, base->u8.green, green_width);
memcpy(dest->u8.blue, base->u8.blue, blue_width);
}
switch (depth) {
case 8:
libclut_apply(&dest->u8, UINT8_MAX, uint8_t, &app.u8, UINT8_MAX, uint8_t, 1, 1, 1);
break;
case 16:
libclut_apply(&dest->u16, UINT16_MAX, uint16_t, &app.u16, UINT16_MAX, uint16_t, 1, 1, 1);
break;
case 32:
libclut_apply(&dest->u32, UINT32_MAX, uint32_t, &app.u32, UINT32_MAX, uint32_t, 1, 1, 1);
break;
case 64:
libclut_apply(&dest->u64, UINT64_MAX, uint64_t, &app.u64, UINT64_MAX, uint64_t, 1, 1, 1);
break;
case -1:
libclut_apply(&dest->f, 1.0f, float, &app.d, 1.0f, float, 1, 1, 1);
break;
case -2:
libclut_apply(&dest->d, (double)1, double, &app.f, (double)1, double, 1, 1, 1);
break;
default:
abort();
}
}
/**
* Remove a filter from an output
*
* @param out The output
* @param filter The filter
* @return The index of the filter, `out->table_size` if not found
*/
static ssize_t
remove_filter(struct output *restrict out, struct filter *restrict filter)
{
size_t i, n = out->table_size;
for (i = 0; i < n; i++)
if (!strcmp(filter->class, out->table_filters[i].class))
break;
if (i == out->table_size) {
fprintf(stderr, "%s: ignoring attempt to removing non-existing filter on CRTC %s: %s\n",
argv0, out->name, filter->class);
return (ssize_t)(out->table_size);
}
filter_destroy(&out->table_filters[i]);
libgamma_gamma_ramps8_destroy(&out->table_sums[i].u8);
n = n - i - 1;
memmove(out->table_filters + i, out->table_filters + i + 1, n * sizeof(*(out->table_filters)));
memmove(out->table_sums + i, out->table_sums + i + 1, n * sizeof(*(out->table_sums)));
out->table_size--;
return (ssize_t)i;
}
/**
* Add a filter to an output
*
* @param out The output
* @param filter The filter
* @return The index given to the filter, -1 on error
*/
static ssize_t
add_filter(struct output *restrict out, struct filter *restrict filter)
{
size_t i, n = out->table_size;
int r = -1;
void *new;
/* Remove? */
if (filter->lifespan == LIFESPAN_REMOVE)
return remove_filter(out, filter);
/* Update? */
for (i = 0; i < n; i++)
if (!strcmp(filter->class, out->table_filters[i].class))
break;
if (i != n) {
filter_destroy(&out->table_filters[i]);
out->table_filters[i] = *filter;
filter->class = NULL;
filter->ramps = NULL;
return (ssize_t)i;
}
/* Add! */
for (i = 0; i < n; i++)
if (filter->priority > out->table_filters[i].priority)
break;
if (n == out->table_alloc) {
new = realloc(out->table_filters, (n + 10) * sizeof(*out->table_filters));
if (!new)
return -1;
out->table_filters = new;
new = realloc(out->table_sums, (n + 10) * sizeof(*out->table_sums));
if (!new)
return -1;
out->table_sums = new;
out->table_alloc += 10;
}
memmove(&out->table_filters[i + 1], &out->table_filters[i], (n - i) * sizeof(*out->table_filters));
memmove(&out->table_sums [i + 1], &out->table_sums [i], (n - i) * sizeof(*out->table_sums));
out->table_size++;
out->table_filters[i] = *filter;
filter->class = NULL;
filter->ramps = NULL;
COPY_RAMP_SIZES(&out->table_sums[i].u8, out);
switch (out->depth) {
case 8: r = libgamma_gamma_ramps8_initialise(&(out->table_sums[i].u8)); break;
case 16: r = libgamma_gamma_ramps16_initialise(&(out->table_sums[i].u16)); break;
case 32: r = libgamma_gamma_ramps32_initialise(&(out->table_sums[i].u32)); break;
case 64: r = libgamma_gamma_ramps64_initialise(&(out->table_sums[i].u64)); break;
case -1: r = libgamma_gamma_rampsf_initialise(&(out->table_sums[i].f)); break;
case -2: r = libgamma_gamma_rampsd_initialise(&(out->table_sums[i].d)); break;
default:
abort();
}
if (r < 0)
return -1;
return (ssize_t)i;
}
/**
* Handle a closed connection
*
* @param client The file descriptor for the client
* @return Zero on success, -1 on error
*/
int
connection_closed(int client)
{
size_t i, j, k;
int remove;
struct output *output;
ssize_t updated;
for (i = 0; i < outputs_n; i++) {
output = outputs + i;
updated = -1;
for (j = k = 0; j < output->table_size; j += !remove, k++) {
if (j != k) {
output->table_filters[j] = output->table_filters[k];
output->table_sums[j] = output->table_sums[k];
}
remove = output->table_filters[j].client == client;
remove = remove && output->table_filters[j].lifespan == LIFESPAN_UNTIL_DEATH;
if (remove) {
filter_destroy(&output->table_filters[j]);
libgamma_gamma_ramps8_destroy(&output->table_sums[j].u8);
output->table_size -= 1;
if (updated == -1)
updated = (ssize_t)j;
}
}
if (updated >= 0)
if (flush_filters(output, (size_t)updated) < 0)
return -1;
}
return 0;
}
/**
* Handle a ‘Command: get-gamma’ message
*
* @param conn The index of the connection
* @param message_id The value of the ‘Message ID’ header
* @param crtc The value of the ‘CRTC’ header
* @param coalesce The value of the ‘Coalesce’ header
* @param high_priority The value of the ‘High priority’ header
* @param low_priority The value of the ‘Low priority’ header
* @return Zero on success (even if ignored), -1 on error,
* 1 if connection closed
*/
int
handle_get_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc,
const char *restrict coalesce, const char *restrict high_priority,
const char *restrict low_priority)
{
struct output *restrict output;
int64_t high, low;
int coal;
char *restrict buf;
size_t start, end, len, n, i;
char depth[3 * sizeof(output->depth) + 2];
char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)];
union gamma_ramps ramps;
if (!crtc) return send_error("protocol error: 'CRTC' header omitted");
if (!coalesce) return send_error("protocol error: 'Coalesce' header omitted");
if (!high_priority) return send_error("protocol error: 'High priority' header omitted");
if (!low_priority) return send_error("protocol error: 'Low priority' header omitted");
high = (int64_t)atoll(high_priority);
low = (int64_t)atoll(low_priority);
if (!strcmp(coalesce, "yes"))
coal = 1;
else if (!strcmp(coalesce, "no"))
coal = 0;
else
return send_error("protocol error: recognised value for 'Coalesce' header");
output = output_find_by_name(crtc, outputs, outputs_n);
if (!output)
return send_error("selected CRTC does not exist");
else if (output->supported == LIBGAMMA_NO)
return send_error("selected CRTC does not support gamma adjustments");
for (start = 0; start < output->table_size; start++)
if (output->table_filters[start].priority <= high)
break;
for (end = output->table_size; end > 0; end--)
if (output->table_filters[end - 1].priority >= low)
break;
switch (output->depth) {
case -2: strcpy(depth, "d"); break;
case -1: strcpy(depth, "f"); break;
default:
sprintf(depth, "%i", output->depth);
break;
}
if (coal) {
*tables = '\0';
n = output->ramps_size;
} else {
sprintf(tables, "Tables: %zu\n", end - start);
n = (sizeof(int64_t) + output->ramps_size) * (end - start);
for (i = start; i < end; i++)
n += strlen(output->table_filters[i].class) + 1;
}
MAKE_MESSAGE(&buf, &n, n,
"In response to: %s\n"
"Depth: %s\n"
"Red size: %zu\n"
"Green size: %zu\n"
"Blue size: %zu\n"
"%s"
"Length: %zu\n"
"\n",
message_id, depth, output->red_size, output->green_size,
output->blue_size, tables, n);
if (coal) {
if (!start && start < end) {
memcpy(&buf[n], output->table_sums[end - 1].u8.red, output->ramps_size);
} else {
if (make_plain_ramps(&ramps, output)) {
free(buf);
return -1;
}
for (i = start; i < end; i++)
apply_filter(&ramps, output->table_filters[i].ramps, output->depth, &ramps);
memcpy(&buf[n], ramps.u8.red, output->ramps_size);
libgamma_gamma_ramps8_destroy(&(ramps.u8));
}
n += output->ramps_size;
} else {
for (i = start; i < end; i++) {
#if defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-align"
#endif
*(int64_t *)&buf[n] = output->table_filters[i].priority;
#if defined(__clang__)
# pragma GCC diagnostic pop
#endif
n += sizeof(int64_t);
len = strlen(output->table_filters[i].class) + 1;
memcpy(&buf[n], output->table_filters[i].class, len);
n += len;
memcpy(&buf[n], output->table_filters[i].ramps, output->ramps_size);
n += output->ramps_size;
}
}
return send_message(conn, buf, n);
}
/**
* Handle a ‘Command: set-gamma’ message
*
* @param conn The index of the connection
* @param message_id The value of the ‘Message ID’ header
* @param crtc The value of the ‘CRTC’ header
* @param priority The value of the ‘Priority’ header
* @param class The value of the ‘Class’ header
* @param lifespan The value of the ‘Lifespan’ header
* @return Zero on success (even if ignored), -1 on error,
* 1 if connection closed
*/
int
handle_set_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc,
const char *restrict priority, const char *restrict class, const char *restrict lifespan)
{
struct message *restrict msg = inbound + conn;
struct output *restrict output = NULL;
struct filter filter;
char *restrict p;
char *restrict q;
int saved_errno;
ssize_t r;
if (!crtc) return send_error("protocol error: 'CRTC' header omitted");
if (!class) return send_error("protocol error: 'Class' header omitted");
if (!lifespan) return send_error("protocol error: 'Lifespan' header omitted");
filter.client = connections[conn];
filter.priority = !priority ? 0 : (int64_t)atoll(priority);
filter.ramps = NULL;
output = output_find_by_name(crtc, outputs, outputs_n);
if (!output)
return send_error("CRTC does not exists");
p = strstr(class, "::");
if (!p || p == class)
return send_error("protocol error: malformatted value for 'Class' header");
q = strstr(p + 2, "::");
if (!q || q == p)
return send_error("protocol error: malformatted value for 'Class' header");
if (!strcmp(lifespan, "until-removal"))
filter.lifespan = LIFESPAN_UNTIL_REMOVAL;
else if (!strcmp(lifespan, "until-death"))
filter.lifespan = LIFESPAN_UNTIL_DEATH;
else if (!strcmp(lifespan, "remove"))
filter.lifespan = LIFESPAN_REMOVE;
else
return send_error("protocol error: recognised value for 'Lifespan' header");
if (filter.lifespan == LIFESPAN_REMOVE) {
if (msg->payload_size)
fprintf(stderr, "%s: ignoring superfluous payload on Command: set-gamma message with "
"Lifespan: remove\n", argv0);
if (priority)
fprintf(stderr, "%s: ignoring superfluous Priority header on Command: set-gamma message with "
"Lifespan: remove\n", argv0);
} else if (msg->payload_size != output->ramps_size) {
return send_error("invalid payload: size of message payload does matched the expectancy");
} else if (!priority) {
return send_error("protocol error: 'Priority' header omitted");
}
filter.class = memdup(class, strlen(class) + 1);
if (!filter.class)
goto fail;
if (filter.lifespan != LIFESPAN_REMOVE) {
filter.ramps = memdup(msg->payload, msg->payload_size);
if (!filter.ramps)
goto fail;
}
if ((r = add_filter(output, &filter)) < 0)
goto fail;
if (flush_filters(output, (size_t)r))
goto fail;
free(filter.class);
free(filter.ramps);
return send_errno(0);
fail:
saved_errno = errno;
send_errno(saved_errno);
free(filter.class);
free(filter.ramps);
errno = saved_errno;
return -1;
}
/**
* Recalculate the resulting gamma and
* update push the new gamma ramps to the CRTC
*
* @param output The output
* @param first_updated The index of the first added or removed filter
* @return Zero on success, -1 on error
*/
int
flush_filters(struct output *restrict output, size_t first_updated)
{
union gamma_ramps plain, *last;
size_t i;
if (!first_updated) {
if (make_plain_ramps(&plain, output) < 0)
return -1;
last = &plain;
} else {
last = output->table_sums + (first_updated - 1);
}
for (i = first_updated; i < output->table_size; i++) {
apply_filter(&output->table_sums[i], output->table_filters[i].ramps, output->depth, last);
last = output->table_sums + i;
}
set_gamma(output, last);
if (!first_updated)
libgamma_gamma_ramps8_destroy(&plain.u8);
return 0;
}
/**
* Preserve current gamma ramps at priority 0 for all outputs
*
* @return Zero on success, -1 on error
*/
int
preserve_gamma(void)
{
size_t i;
struct filter filter;
for (i = 0; i < outputs_n; i++) {
filter.client = -1;
filter.priority = 0;
filter.class = NULL;
filter.lifespan = LIFESPAN_UNTIL_REMOVAL;
filter.ramps = NULL;
outputs[i].table_filters = calloc(4, sizeof(*outputs[i].table_filters));
outputs[i].table_sums = calloc(4, sizeof(*outputs[i].table_sums));
outputs[i].table_alloc = 4;
outputs[i].table_size = 1;
filter.class = memdup(PKGNAME"::"COMMAND"::preserved", sizeof(PKGNAME"::"COMMAND"::preserved"));
if (!filter.class)
return -1;
filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size);
if (!filter.ramps)
return -1;
outputs[i].table_filters[0] = filter;
COPY_RAMP_SIZES(&outputs[i].table_sums[0].u8, outputs + i);
if (!gamma_ramps_unmarshal(outputs[i].table_sums, outputs[i].saved_ramps.u8.red, outputs[i].ramps_size))
return -1;
}
return 0;
}