diff options
Diffstat (limited to 'cg-base.c')
-rw-r--r-- | cg-base.c | 910 |
1 files changed, 910 insertions, 0 deletions
diff --git a/cg-base.c b/cg-base.c new file mode 100644 index 0000000..33d9c4d --- /dev/null +++ b/cg-base.c @@ -0,0 +1,910 @@ +/* See LICENSE file for copyright and license details. */ +#include "cg-base.h" + +#include <libclut.h> + +#include <alloca.h> +#include <errno.h> +#include <inttypes.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + + +/** + * The process's name + */ +const char* argv0 = NULL; + +/** + * The libcoopgamma context + */ +libcoopgamma_context_t cg; + +/** + * The names of the selected CRTC:s + */ +char** crtcs = NULL; + +/** + * Gamma ramp updates for each CRTC + */ +filter_update_t* crtc_updates = NULL; + +/** + * CRTC and monitor information about + * each selected CRTC and connect monitor + */ +libcoopgamma_crtc_info_t* crtc_info = NULL; + +/** + * The number of selected CRTC:s + */ +size_t crtcs_n = 0; + +/** + * The number of filters + */ +size_t filters_n = 0; + + +/** + * Contexts for asynchronous ramp updates + */ +static libcoopgamma_async_context_t* asyncs = NULL; + +/** + * The number of pending receives + */ +static size_t pending_recvs = 0; + +/** + * Whether message must be flushed + */ +static int flush_pending = 0; + + + +/** + * Data used to sort CRTC:s + */ +struct crtc_sort_data +{ + /** + * The gamma ramp type + */ + libcoopgamma_depth_t depth; + + /** + * Should be 0 + */ + int __padding; + + /** + * The size of the red gamma ramp + */ + size_t red_size; + + /** + * The size of the green gamma ramp + */ + size_t green_size; + + /** + * The size of the blue gamma ramp + */ + size_t blue_size; + + /** + * The index of the CRTC + */ + size_t index; +}; + + + +/** + * Compare two strings + * + * @param a Return -1 if this string is `NULL` or less than `b` + * @param b Return +1 if this string is less than `a` + * @return See `a` and `b`, 0 is returned if `a` and `b` are equal + */ +static int nulstrcmp(const char *a, const char *b) +{ + return (a == NULL) ? -1 : strcmp(a, b); +} + + +/** + * Compare two instances of `crtc_sort_data` + * + * @param a_ Return -1 if this one is lower + * @param b_ Return +1 if this one is higher + * @return See `a_` and `b_`, only -1 or +1 can be returned + */ +static int crtc_sort_data_cmp(const void* a_, const void* b_) +{ + const struct crtc_sort_data* a = a_; + const struct crtc_sort_data* b = b_; + int cmp = memcmp(a, b, sizeof(*a) - sizeof(a->index)); + return cmp ? cmp : a->index < b->index ? -1 : +1; +} + + +/** + * Make elements in `crtc_updates` slaves where appropriate + * + * @return Zero on success, -1 on error + */ +int make_slaves(void) +{ + struct crtc_sort_data* data; + size_t i, j, n = 0, master = 0, master_i; + + data = alloca(filters_n * sizeof(*data)); + memset(data, 0, filters_n * sizeof(*data)); + for (i = 0; i < filters_n; i++) + { + if (!(crtc_info[crtc_updates[i].crtc].supported)) + continue; + + data[n].depth = crtc_updates[i].filter.depth; + data[n].red_size = crtc_updates[i].filter.ramps.u8.red_size; + data[n].green_size = crtc_updates[i].filter.ramps.u8.green_size; + data[n].blue_size = crtc_updates[i].filter.ramps.u8.blue_size; + data[n].index = i; + n++; + } + + qsort(data, n, sizeof(*data), crtc_sort_data_cmp); + if (n == 0) + return 0; + + master_i = data[0].index; + for (i = 1; i < n; i++) + if (memcmp(data + i, data + master, sizeof(*data) - sizeof(data->index))) + { + if (master + 1 < i) + { + crtc_updates[master_i].slaves = calloc(i - master, sizeof(size_t)); + if (crtc_updates[master_i].slaves == NULL) + return -1; + for (j = 1; master + j < i; j++) + crtc_updates[master_i].slaves[j - 1] = data[master + j].index; + } + master = i; + master_i = data[master].index; + } + else + { + libcoopgamma_ramps_destroy(&(crtc_updates[data[i].index].filter.ramps.u8)); + crtc_updates[data[i].index].master = 0; + crtc_updates[data[i].index].filter.ramps.u8 = crtc_updates[master_i].filter.ramps.u8; + } + + if (master + 1 < i) + { + crtc_updates[master_i].slaves = calloc(i - master, sizeof(size_t)); + if (crtc_updates[master_i].slaves == NULL) + return -1; + for (j = 1; master + j < i; j++) + crtc_updates[master_i].slaves[j - 1] = data[master + j].index; + } + + return 0; +} + + +/** + * Update a filter and synchronise calls + * + * @param index The index of the CRTC + * @param timeout The number of milliseconds a call to `poll` may block, + * -1 if it may block forever + * @return 1: Success, no pending synchronisations + * 0: Success, with still pending synchronisations + * -1: Error, `errno` set + * -2: Error, `cg.error` set + * + * @throws EINTR Call to `poll` was interrupted by a signal + * @throws EAGAIN Call to `poll` timed out + */ +int update_filter(size_t index, int timeout) +{ + filter_update_t* filter = crtc_updates + index; + + if (!(filter->synced) || filter->failed) + abort(); + + pending_recvs += 1; + + if (libcoopgamma_set_gamma_send(&(filter->filter), &cg, asyncs + index) < 0) + switch (errno) + { + case EINTR: + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + flush_pending = 1; + break; + default: + return -1; + } + + filter->synced = 0; + return synchronise(timeout); +} + + +/** + * Synchronised calls + * + * @param timeout The number of milliseconds a call to `poll` may block, + * -1 if it may block forever + * @return 1: Success, no pending synchronisations + * 0: Success, with still pending synchronisations + * -1: Error, `errno` set + * -2: Error, `cg.error` set + * + * @throws EINTR Call to `poll` was interrupted by a signal + * @throws EAGAIN Call to `poll` timed out + */ +int synchronise(int timeout) +{ + struct pollfd pollfd; + size_t selected; + + pollfd.fd = cg.fd; + pollfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI; + if (flush_pending > 0) + pollfd.events |= POLLOUT; + + pollfd.revents = 0; + if (poll(&pollfd, (nfds_t)1, timeout) < 0) + return -1; + + if (pollfd.revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL)) + { + if (libcoopgamma_flush(&cg) < 0) + goto sync; + flush_pending = 0; + } + + if ((timeout < 0) && (pending_recvs > 0)) + if (!(pollfd.revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI))) + { + pollfd.revents = 0; + if (poll(&pollfd, (nfds_t)1, -1) < 0) + return -1; + } + + sync: + if (pollfd.revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP | POLLNVAL)) + for (;;) + { + if (libcoopgamma_synchronise(&cg, asyncs, filters_n, &selected) < 0) + { + if (errno == 0) + continue; + else + goto fail; + } + if (crtc_updates[selected].synced) + continue; + crtc_updates[selected].synced = 1; + pending_recvs -= 1; + if (libcoopgamma_set_gamma_recv(&cg, asyncs + selected) < 0) + { + if (cg.error.server_side) + { + crtc_updates[selected].error = cg.error; + crtc_updates[selected].failed = 1; + memset(&(cg.error), 0, sizeof(cg.error)); + } + else + goto cg_fail; + } + } + + return pending_recvs == 0; + cg_fail: + return -2; + fail: + switch (errno) + { + case EINTR: + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + return pending_recvs == 0; + default: + return -1; + } +} + + +/** + * Initialise the process, specifically + * reset the signal mask and signal handlers + * + * @return Zero on success, -1 on error + */ +static int initialise_proc(void) +{ + sigset_t sigmask; + int sig; + + for (sig = 1; sig < _NSIG; sig++) + if (signal(sig, SIG_DFL) == SIG_ERR) + if (sig == SIGCHLD) + return -1; + + if (sigemptyset(&sigmask) < 0) + return -1; + if (sigprocmask(SIG_SETMASK, &sigmask, NULL) < 0) + return -1; + + return 0; +} + + +/** + * Print, to stdout, a list of all + * recognised adjustment methods + * + * @return Zero on success, -1 on error + */ +static int list_methods(void) +{ + char** list; + size_t i; + + list = libcoopgamma_get_methods(); + if (list == NULL) + return -1; + for (i = 0; list[i]; i++) + printf("%s\n", list[i]); + free(list); + if (fflush(stdout) < 0) + return -1; + + return 0; +} + + +/** + * Print, to stdout, a list of all CRTC:s + * + * A connection to the coopgamma server + * must have been made + * + * @return Zero on success, -1 on error, -2 + * on libcoopgamma error + */ +static int list_crtcs(void) +{ + char** list; + size_t i; + + list = libcoopgamma_get_crtcs_sync(&cg); + if (list == NULL) + return -2; + for (i = 0; list[i]; i++) + printf("%s\n", list[i]); + free(list); + if (fflush(stdout) < 0) + return -1; + + return 0; +} + + +/** + * Fill the list of CRTC information + * + * @return Zero on success, -1 on error, -2 + * on libcoopgamma error + */ +static int get_crtc_info(void) +{ + size_t i, unsynced = 0, selected; + char* synced; + int need_flush = 0; + struct pollfd pollfd; + + synced = alloca(crtcs_n * sizeof(*synced)); + memset(synced, 0, crtcs_n * sizeof(*synced)); + + i = 0; + pollfd.fd = cg.fd; + pollfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI; + + while ((unsynced > 0) || (i < crtcs_n)) + { + wait: + if (i < crtcs_n) + pollfd.events |= POLLOUT; + else + pollfd.events &= ~POLLOUT; + + pollfd.revents = 0; + if (poll(&pollfd, (nfds_t)1, -1) < 0) + goto fail; + + if (pollfd.revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL)) + { + if (need_flush && (libcoopgamma_flush(&cg) < 0)) + goto send_fail; + need_flush = 0; + for (; i < crtcs_n; i++) + if (unsynced++, libcoopgamma_get_gamma_info_send(crtcs[i], &cg, asyncs + i) < 0) + goto send_fail; + goto send_done; + send_fail: + switch (errno) + { + case EINTR: + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + i++; + need_flush = 1; + break; + default: + goto fail; + } + } + send_done: + + if ((unsynced == 0) && (i == crtcs_n)) + break; + + if (pollfd.revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) + while (unsynced > 0) + switch (libcoopgamma_synchronise(&cg, asyncs, i, &selected)) + { + case 0: + if (synced[selected]) + { + libcoopgamma_skip_message(&cg); + break; + } + synced[selected] = 1; + unsynced -= 1; + if (libcoopgamma_get_gamma_info_recv(crtc_info + selected, &cg, asyncs + selected) < 0) + goto cg_fail; + break; + case -1: + switch (errno) + { + case 0: + break; + case EINTR: + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + goto wait; + default: + goto fail; + } + break; + } + } + + return 0; + fail: + return -1; + cg_fail: + return -2; +} + + +/** + * -M METHOD + * Select adjustment method. If METHOD is "?", + * available methods will be printed to stdout. + * + * -S SITE + * Select site (display server instance). + * + * -c CRTC + * Select CRT controller. If CRTC is "?", CRTC:s + * will be printed to stdout. + * + * This option can be used multiple times. If it + * is not used at all, all CRTC:s will be selected. + * + * -p PRIORITY + * Select the priority for the filter, this should + * be a signed two's-complement integer. If + * PRIORITY is "?", the default priority for the + * program is printed to stdout. + * + * -R RULE + * The rule of the filter, that is, the last part + * of the class which is its identifier. If RULE + * is "?" the default rule is printed to stdout, + * if RULE is "??" the default class is printed + * to stdout. + * + * @param argc The number of command line arguments + * @param argv The command line arguments + * @return 0 on success, 1 on error + */ +int main(int argc, char* argv[]) +{ + int stage = 0; + int dealloc_crtcs = 0; + int rc = 0; + char* method = NULL; + char* site = NULL; + size_t crtc_i = 0; + int64_t priority = default_priority; + char* prio = NULL; + char* rule = NULL; + char* class = default_class; + char** classes = NULL; + size_t classes_n = 0; + int explicit_crtcs = 0; + int have_crtc_q = 0; + size_t i, filter_i; + + argv0 = *argv++, argc--; + + if (initialise_proc() < 0) + goto fail; + + crtcs = alloca(argc * sizeof(*crtcs)); + + for (; *argv; argv++, argc--) + { + char* args = *argv; + char opt[3]; + if (!strcmp(args, "--")) + { + argv++, argc--; + break; + } + opt[0] = *args++; + opt[2] = '\0'; + if ((*opt != '-') && (*opt != '+')) + break; + while (*args) + { + char* arg; + int at_end; + opt[1] = *args++; + arg = args; + if ((at_end = !*arg)) + arg = argv[1]; + if (!strcmp(opt, "-M")) + { + if ((method != NULL) || ((method = arg) == NULL)) + usage(); + } + else if (!strcmp(opt, "-S")) + { + if ((site != NULL) || ((site = arg) == NULL)) + usage(); + } + else if (!strcmp(opt, "-c")) + { + if (arg == NULL) + usage(); + crtcs[crtc_i++] = arg; + explicit_crtcs = 1; + if (!have_crtc_q && !strcmp(arg, "?")) + have_crtc_q = 1; + } + else if (!strcmp(opt, "-p")) + { + if ((prio != NULL) || ((prio = arg) == NULL)) + usage(); + } + else if (!strcmp(opt, "-R")) + { + if ((rule != NULL) || ((rule = arg) == NULL)) + usage(); + } + else + switch (handle_opt(opt, arg)) + { + case 0: + goto next_opt; + case 1: + break; + default: + goto fail; + } + argv += at_end; + argc -= at_end; + break; + next_opt:; + } + } + + crtcs_n = crtc_i; + crtcs[crtc_i] = NULL; + if (!have_crtc_q && nulstrcmp(method, "?") && + nulstrcmp(rule, "?") && nulstrcmp(rule, "??") && + ((default_priority == NO_DEFAULT_PRIORITY) || nulstrcmp(prio, "?"))) + if (handle_args(argc, argv, prio) < 0) + goto fail; + + if (default_priority != NO_DEFAULT_PRIORITY) + { + if (!nulstrcmp(prio, "?")) + { + printf("%" PRIi64 "\n", priority); + return 0; + } + else if (prio != NULL) + { + char *end; + errno = 0; + priority = (int64_t)strtoll(prio, &end, 10); + if (errno || *end || !*prio) + usage(); + } + } + + if (!nulstrcmp(rule, "??")) + { + size_t i; + if (*class_suffixes == NULL) + printf("%s\n", class); + else + for (i = 0; class_suffixes[i] != NULL; i++) + printf("%s%s\n", class, class_suffixes[i]); + return 0; + } + else if (!nulstrcmp(rule, "?")) + { + printf("%s\n", strstr(strstr(class, "::") + 2, "::") + 2); + return 0; + } + else if (rule != NULL) + { + char* p = strstr(strstr(class, "::") + 2, "::") + 2; + size_t n = (size_t)(p - class); + class = alloca(strlen(rule) + n + (size_t)1); + memcpy(class, default_class, n); + strcpy(class + n, rule); + if (strchr(class, '\n')) + { + fprintf(stderr, "%s: LF character is not allowed in the filter's class\n", argv0); + goto custom_fail; + } + } + + if (!nulstrcmp(method, "?")) + { + if (list_methods() < 0) + goto fail; + return 0; + } + + if (libcoopgamma_context_initialise(&cg) < 0) + goto fail; + stage++; + if (libcoopgamma_connect(method, site, &cg) < 0) + { + fprintf(stderr, "%s: server failed to initialise\n", argv0); + goto custom_fail; + } + stage++; + + if (have_crtc_q) + switch (list_crtcs()) + { + case 0: + goto done; + case -1: + goto fail; + default: + goto cg_fail; + } + + if (crtcs_n == 0) + { + crtcs = libcoopgamma_get_crtcs_sync(&cg); + if (crtcs == NULL) + goto cg_fail; + dealloc_crtcs = 1; + for (; crtcs[crtcs_n] != NULL; crtcs_n++); + } + + if (crtcs_n == 0) + { + fprintf(stderr, "%s: no CRTC:s are available\n", argv0); + goto custom_fail; + } + + if (*class_suffixes == NULL) + { + classes = &class; + classes_n = 1; + } + else + { + size_t len = strlen(class); + while (class_suffixes[classes_n]) + classes_n++; + classes = alloca(classes_n * sizeof(*classes)); + for (i = 0; i < classes_n; i++) + { + classes[i] = alloca(len + strlen(class_suffixes[i]) + sizeof(":")); + stpcpy(stpcpy(stpcpy(classes[i], class), ":"), class_suffixes[i]); + } + } + filters_n = classes_n * crtcs_n; + + crtc_info = alloca(crtcs_n * sizeof(*crtc_info)); + memset(crtc_info, 0, crtcs_n * sizeof(*crtc_info)); + for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++) + if (libcoopgamma_crtc_info_initialise(crtc_info + crtc_i) < 0) + goto cg_fail; + + if (libcoopgamma_set_nonblocking(&cg, 1) < 0) + goto fail; + + asyncs = alloca(filters_n * sizeof(*asyncs)); + memset(asyncs, 0, filters_n * sizeof(*asyncs)); + for (filter_i = 0; filter_i < filters_n; filter_i++) + if (libcoopgamma_async_context_initialise(asyncs + filter_i) < 0) + goto fail; + + switch (get_crtc_info()) + { + case 0: + break; + case -1: + goto fail; + case -2: + goto cg_fail; + } + + for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++) + { + if (explicit_crtcs && !(crtc_info[crtc_i].supported)) + fprintf(stderr, "%s: warning: gamma adjustments not supported on CRTC: %s\n", + argv0, crtcs[crtc_i]); + if (crtc_info[crtc_i].cooperative == 0) + fprintf(stderr, "%s: warning: cooperative gamma server not running for CRTC: %s\n", + argv0, crtcs[crtc_i]); + } + + crtc_updates = alloca(filters_n * sizeof(*crtc_updates)); + memset(crtc_updates, 0, filters_n * sizeof(*crtc_updates)); + for (filter_i = i = 0; i < classes_n; i++) + for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++, filter_i++) + { + if (libcoopgamma_filter_initialise(&(crtc_updates[filter_i].filter)) < 0) + goto fail; + if (libcoopgamma_error_initialise(&(crtc_updates[filter_i].error)) < 0) + goto fail; + crtc_updates[filter_i].crtc = crtc_i; + crtc_updates[filter_i].synced = 1; + crtc_updates[filter_i].failed = 0; + crtc_updates[filter_i].master = 1; + crtc_updates[filter_i].slaves = NULL; + crtc_updates[filter_i].filter.crtc = crtcs[crtc_i]; + crtc_updates[filter_i].filter.class = classes[i]; + crtc_updates[filter_i].filter.priority = priority; + crtc_updates[filter_i].filter.depth = crtc_info[crtc_i].depth; + crtc_updates[filter_i].filter.ramps.u8.red_size = crtc_info[crtc_i].red_size; + crtc_updates[filter_i].filter.ramps.u8.green_size = crtc_info[crtc_i].green_size; + crtc_updates[filter_i].filter.ramps.u8.blue_size = crtc_info[crtc_i].blue_size; + switch (crtc_updates[filter_i].filter.depth) + { +#define X(CONST, MEMBER, MAX, TYPE)\ + case CONST:\ + libcoopgamma_ramps_initialise(&(crtc_updates[filter_i].filter.ramps.MEMBER));\ + libclut_start_over(&(crtc_updates[filter_i].filter.ramps.MEMBER), MAX, TYPE, 1, 1, 1);\ + break; +LIST_DEPTHS +#undef X + default: + fprintf(stderr, "%s: internal error: gamma ramp type is unrecognised: %i\n", + argv0, crtc_updates[filter_i].filter.depth); + goto custom_fail; + } + } + + switch (start()) + { + case 0: + break; + case -1: + goto fail; + case -2: + goto cg_fail; + case -3: + goto custom_fail; + } + + for (filter_i = 0; filter_i < filters_n; filter_i++) + if (crtc_updates[filter_i].failed) + { + const char* side = cg.error.server_side ? "server" : "client"; + const char* crtc = crtc_updates[filter_i].filter.crtc; + if (cg.error.custom) + { + if ((cg.error.number != 0) && (cg.error.description != NULL)) + fprintf(stderr, "%s: %s-side error number %" PRIu64 " for CRTC %s: %s\n", + argv0, side, cg.error.number, crtc, cg.error.description); + else if (cg.error.number != 0) + fprintf(stderr, "%s: %s-side error number %" PRIu64 " for CRTC %s\n", + argv0, side, cg.error.number, crtc); + else if (cg.error.description != NULL) + fprintf(stderr, "%s: %s-side error for CRTC %s: %s\n", argv0, side, crtc, cg.error.description); + } + else if (cg.error.description != NULL) + fprintf(stderr, "%s: %s-side error for CRTC %s: %s\n", argv0, side, crtc, cg.error.description); + else + fprintf(stderr, "%s: %s-side error for CRTC %s: %s\n", argv0, side, crtc, strerror(cg.error.number)); + } + + done: + if (dealloc_crtcs) + free(crtcs); + if (crtc_info != NULL) + for (crtc_i = 0; crtc_i < crtcs_n; crtc_i++) + libcoopgamma_crtc_info_destroy(crtc_info + crtc_i); + if (asyncs != NULL) + for (filter_i = 0; filter_i < filters_n; filter_i++) + libcoopgamma_async_context_destroy(asyncs + filter_i); + if (stage >= 1) + libcoopgamma_context_destroy(&cg, stage >= 2); + if (crtc_updates != NULL) + for (filter_i = 0; filter_i < filters_n; filter_i++) + { + if (crtc_updates[filter_i].master == 0) + memset(&(crtc_updates[filter_i].filter.ramps.u8), 0, sizeof(crtc_updates[filter_i].filter.ramps.u8)); + crtc_updates[filter_i].filter.crtc = NULL; + crtc_updates[filter_i].filter.class = NULL; + libcoopgamma_filter_destroy(&(crtc_updates[filter_i].filter)); + libcoopgamma_error_destroy(&(crtc_updates[filter_i].error)); + free(crtc_updates[filter_i].slaves); + } + return rc; + + custom_fail: + rc = 1; + goto done; + + fail: + rc = 1; + if (errno) + perror(argv0); + goto done; + + cg_fail: + rc = 1; + { + const char* side = cg.error.server_side ? "server" : "client"; + if (cg.error.custom) + { + if ((cg.error.number != 0) && (cg.error.description != NULL)) + fprintf(stderr, "%s: %s-side error number %" PRIu64 ": %s\n", + argv0, side, cg.error.number, cg.error.description); + else if (cg.error.number != 0) + fprintf(stderr, "%s: %s-side error number %" PRIu64 "\n", argv0, side, cg.error.number); + else if (cg.error.description != NULL) + fprintf(stderr, "%s: %s-side error: %s\n", argv0, side, cg.error.description); + } + else if (cg.error.description != NULL) + fprintf(stderr, "%s: %s-side error: %s\n", argv0, side, cg.error.description); + else + fprintf(stderr, "%s: %s-side error: %s\n", argv0, side, strerror(cg.error.number)); + } + goto done; +} |