/** * coopgammad -- Cooperative gamma server * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "coopgamma.h" #include "gamma.h" #include "../state.h" #include "../communication.h" #include "../util.h" #include "../types/output.h" #include #include #include #include #include /** * 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* restrict dest, void* restrict application, int depth, union gamma_ramps* restrict 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; /* 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) { void* new; new = realloc(out->table_filters, (n + 10) * sizeof(*(out->table_filters))); if (new == NULL) return -1; out->table_filters = new; new = realloc(out->table_sums, (n + 10) * sizeof(*(out->table_sums))); if (new == NULL) 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; for (i = 0; i < outputs_n; i++) { struct output* output = outputs + i; ssize_t 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]; char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)]; if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted"); if (coalesce == NULL) return send_error("protocol error: 'Coalesce' header omitted"); if (high_priority == NULL) return send_error("protocol error: 'High priority' header omitted"); if (low_priority == NULL) 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 == NULL) 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 == 0) && (start < end)) memcpy(buf + n, output->table_sums[end - 1].u8.red, output->ramps_size); else { union gamma_ramps ramps; if (make_plain_ramps(&ramps, output)) { int saved_errno = errno; free(buf); errno = saved_errno; 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 == NULL) return send_error("protocol error: 'CRTC' header omitted"); if (class == NULL) return send_error("protocol error: 'Class' header omitted"); if (lifespan == NULL) return send_error("protocol error: 'Lifespan' header omitted"); filter.client = connections[conn]; filter.priority = priority == NULL ? 0 : (int64_t)atoll(priority); filter.ramps = NULL; output = output_find_by_name(crtc, outputs, outputs_n); if (output == NULL) return send_error("CRTC does not exists"); p = strstr(class, "::"); if ((p == NULL) || (p == class)) return send_error("protocol error: malformatted value for 'Class' header"); q = strstr(p + 2, "::"); if ((q == NULL) || (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 != NULL) 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 == NULL) return send_error("protocol error: 'Priority' header omitted"); filter.class = memdup(class, strlen(class) + 1); if (filter.class == NULL) goto fail; if (filter.lifespan != LIFESPAN_REMOVE) { filter.ramps = memdup(msg->payload, msg->payload_size); if (filter.ramps == NULL) 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; union gamma_ramps* last; size_t i; if (first_updated == 0) { 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 == 0) 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; for (i = 0; i < outputs_n; i++) { struct filter filter = { .client = -1, .priority = 0, .class = NULL, .lifespan = LIFESPAN_UNTIL_REMOVAL, .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 == NULL) return -1; filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size); if (filter.ramps == NULL) 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; }