/** * 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 "server.h" #include "chaining.h" #include "../state.h" #include "../communication.h" #include "../util.h" #include "../gamma-server/server.h" #include #include #include #include /** * 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++) { 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; } output->table_filters[j] = output->table_filters[k]; output->table_sums[j] = output->table_sums[k]; } 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, 0, "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) memcpy(buf + n, output->table_sums[end].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; filter.class = NULL; filter.ramps = NULL; if (flush_filters(output, (size_t)r)) goto fail; 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; }