/* See LICENSE file for copyright and license details. */ #include "arg.h" #include #include #include #include #include #include #include #include /** * The libcoopgamma context */ static libcoopgamma_context_t cg; /** * Print usage information and exit */ static void usage(void) { fprintf(stderr, "usage: %s [-M method] [-S site] [-c crtc]... class...\n", argv0); exit(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) 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) return -2; for (i = 0; list[i]; i++) printf("%s\n", list[i]); free(list); if (fflush(stdout) < 0) return -1; return 0; } /** * Remove selected filters from selected CRTC:s * * @param crtcs `NULL`-terminated list of CRTC names * @param classes `NULL`-terminated list of filter classes * @return Zero on success, -1 on error, -2 on * libcoopgamma error */ static int remove_filters(char *const *restrict crtcs, char *const *restrict classes) { size_t n = 0, unsynced = 0, selected, i, j; char *synced = NULL; libcoopgamma_async_context_t *asyncs = NULL; int saved_errno, need_flush = 0, ret = 0; struct pollfd pollfd; libcoopgamma_filter_t command; for (i = 0; crtcs[i]; i++); for (j = 0; classes[j]; j++); synced = calloc(i, j * sizeof(*synced)); if (!synced) goto fail; asyncs = calloc(i, j * sizeof(*asyncs)); if (!asyncs) goto fail; i = j = 0; command.lifespan = LIBCOOPGAMMA_REMOVE; pollfd.fd = cg.fd; pollfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI; while (unsynced > 0 || crtcs[i]) { wait: if (crtcs[i]) 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 (; crtcs[i]; i++, j = 0) { command.crtc = crtcs[i]; while (classes[j]) { command.class = classes[j++]; unsynced++; if (libcoopgamma_set_gamma_send(&command, &cg, asyncs + n++) < 0) goto send_fail; } } goto send_done; send_fail: switch (errno) { case EINTR: case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif need_flush = 1; if (!classes[j]) i++, j = 0; break; default: goto fail; } } send_done: if (!unsynced && !crtcs[i]) break; if (pollfd.revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) { while (unsynced > 0) { switch (libcoopgamma_synchronise(&cg, asyncs, n, &selected)) { case 0: if (synced[selected]) { libcoopgamma_skip_message(&cg); break; } synced[selected] = 1; unsynced -= 1; if (libcoopgamma_set_gamma_recv(&cg, asyncs + selected) < 0) goto cg_fail; break; default: switch (errno) { case 0: break; case EINTR: case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif goto wait; default: goto fail; } break; } } } } done: saved_errno = errno; free(synced); free(asyncs); errno = saved_errno; return ret; fail: ret = -1; goto done; cg_fail: ret = -2; goto done; } /** * -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. * * Can be used multiple times. If not used, all * CRTC:s are selected. * * @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 rc = 0; char *method = NULL; char *site = NULL; char **crtcs_ = NULL; char **crtcs; size_t i, crtcs_n = 0; const char *side; crtcs = alloca((size_t)argc * sizeof(char *)); ARGBEGIN { case 'M': if (method) usage(); method = EARGF(usage()); break; case 'S': if (site) usage(); site = EARGF(usage()); break; case 'c': crtcs[crtcs_n++] = EARGF(usage()); break; default: usage(); } ARGEND; if (initialise_proc() < 0) goto fail; if (method && !strcmp(method, "?")) { if (site || crtcs_n > 0 || argc > 0) usage(); 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++; for (i = 0; i < crtcs_n; i++) { if (!strcmp(crtcs[i], "?")) { if (argc > 0) usage(); switch (list_crtcs()) { case 0: goto done; case -1: goto fail; default: goto cg_fail; } } } if (!argc) usage(); if (!crtcs_n) { crtcs = crtcs_ = libcoopgamma_get_crtcs_sync(&cg); if (!crtcs) goto cg_fail; } else { crtcs[crtcs_n] = NULL; } if (libcoopgamma_set_nonblocking(&cg, 1) < 0) goto fail; switch (remove_filters(crtcs, argv)) { case 0: break; case -1: goto fail; default: goto cg_fail; } done: if (stage >= 1) libcoopgamma_context_destroy(&cg, stage >= 2); free(crtcs_); return rc; custom_fail: rc = 1; goto done; fail: rc = 1; perror(argv0); goto done; cg_fail: rc = 1; side = cg.error.server_side ? "server" : "client"; if (cg.error.custom) { if (cg.error.number || cg.error.description) fprintf(stderr, "%s: %s-side error number %" PRIu64 ": %s\n", argv0, side, cg.error.number, cg.error.description); else if (cg.error.number) fprintf(stderr, "%s: %s-side error number %" PRIu64 "\n", argv0, side, cg.error.number); else if (cg.error.description) fprintf(stderr, "%s: %s-side error: %s\n", argv0, side, cg.error.description); } else if (cg.error.description) { 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((int)cg.error.number)); } goto done; }