/* 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 #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 *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; }