diff options
Diffstat (limited to '')
-rw-r--r-- | servers-coopgamma.c | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/servers-coopgamma.c b/servers-coopgamma.c new file mode 100644 index 0000000..f14c0c4 --- /dev/null +++ b/servers-coopgamma.c @@ -0,0 +1,524 @@ +/* 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; +} |