diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 32 | ||||
-rw-r--r-- | README | 21 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | config.mk | 5 | ||||
-rw-r--r-- | radharc-ipc.c | 147 | ||||
-rw-r--r-- | radharc.1 | 30 | ||||
-rw-r--r-- | radharc.c | 154 |
8 files changed, 377 insertions, 14 deletions
@@ -4,3 +4,4 @@ *.a *.su radharc +radharc-ipc @@ -3,34 +3,48 @@ CONFIGFILE = config.mk include $(CONFIGFILE) -OBJ =\ +OBJ_RADHARC =\ cg-base.o\ radharc.o -HDR =\ +OBJ_RADHARC_IPC =\ + radharc-ipc.o + +HDR_RADHARC =\ cg-base.h -all: radharc -$(OBJ): $(@:.o=.c) $(HDR) +CPPFLAGS_MACROS =\ + -D'PACKAGE_NAME="$(PACKAGE_NAME)"'\ + -D'COMMAND_NAME="$(COMMAND_NAME)"' + +all: radharc radharc-ipc +$(OBJ_RADHARC): $(HDR_RADHARC) .c.o: - $(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS) + $(CC) -c -o $@ $< $(CPPFLAGS_MACROS) $(CPPFLAGS) $(CFLAGS) + +radharc: $(OBJ_RADHARC) + $(CC) -o $@ $(OBJ_RADHARC) $(LDFLAGS) -radharc: $(OBJ) - $(CC) -o $@ $(OBJ) $(LDFLAGS) +radharc-ipc: $(OBJ_RADHARC_IPC) + $(CC) -o $@ $(OBJ_RADHARC_IPC) $(LDFLAGS) -install: radharc +install: radharc radharc-ipc mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1" cp -- radharc "$(DESTDIR)$(PREFIX)/bin/" + cp -- radharc-ipc "$(DESTDIR)$(PREFIX)/bin/" cp -- radharc.1 "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- radharc-ipc.1 "$(DESTDIR)$(MANPREFIX)/man1/" uninstall: -rm -f -- "$(DESTDIR)$(PREFIX)/bin/radharc" + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/radharc-ipc" -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/radharc.1" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/radharc-ipc.1" clean: - -rm -f -- radharc *.o + -rm -f -- radharc radharc-ipc *.o .SUFFIXES: .SUFFIXES: .c .o @@ -103,13 +103,30 @@ OPTIONS Supported options are: linear=value - If value is 'yes', the effect will be surrounded by an effect equivalent to that of cg-linear(1), the two effects will be applied as one. If value is 'no', the above will not be done. + socket=name + Select name (abstract address) of the socket created + for IPC functionality. + + if name is '-', the standard input will be used, and + must be a bound unix(7) datagram socket. + + If no specified, and no-socket has not been + specified, a default string will be used, being + defined by the pattern + + "/proc/%u/%s", <PID>, <class> + + Any comma (,) will be interpeted as part of name. + + no-socket + Do not create a socket for IPC functionality. + -x Remove the currently applied filter. @@ -145,4 +162,4 @@ SIGNALS and the full effect is immediately applied. SEE ALSO - coopgammad(1), cg-tools(7), redshift(1), blueshift(1) + radharc-ipc(1), coopgammad(1), cg-tools(7), redshift(1), blueshift(1) @@ -1,3 +1,2 @@ Use bus for changing settings and CRTCs online. -Add PF_UNIX SOCK_DGRAM socket with abstract address @/proc/<pid>/<class> for IPC Add support for non-sRGB, RGB monitors @@ -1,4 +1,4 @@ -PREFIX = /usr +PREFIX = /usr MANPREFIX = $(PREFIX)/share/man CC = c99 @@ -6,3 +6,6 @@ CC = c99 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE CFLAGS = -Wall -O2 LDFLAGS = -lcoopgamma -lred -lm -s + +PACKAGE_NAME = radharc +COMMAND_NAME = radharc diff --git a/radharc-ipc.c b/radharc-ipc.c new file mode 100644 index 0000000..e246375 --- /dev/null +++ b/radharc-ipc.c @@ -0,0 +1,147 @@ +/* See LICENSE file for copyright and license details. */ +#include <libsimple-arg.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +USAGE("[-R rule] (pid | @name | &fd | address) ..."); + + +struct message { + char *text; + size_t len; +}; + + +static int +parse_uint(const char *s, uintmax_t *value) +{ + uintmax_t digit; + if (!*s) + return 0; + *value = 0; + for (; isdigit(*s); s++) { + digit = (*s & 15); + if (*value > (UINTMAX_MAX - digit) / 10) + return 0; + *value = *value * 10 + digit; + } + return !*s; +} + + +int +main(int argc, char *argv[]) +{ + const char *rule = "standard"; + char *class, **addresses; + int ret = 0; + size_t i, j; + struct sockaddr_un addr; + size_t addrlen; + int sock; + struct message *messages = NULL; + size_t nmessages = 0; + + ARGBEGIN { + case 'R': + rule = ARG(); + break; + default: + usage(); + } ARGEND; + + if (!argc) + usage(); + + class = malloc(sizeof(PACKAGE_NAME"::"COMMAND_NAME"::") + strlen(rule)); + if (!class) + goto fail; + stpcpy(stpcpy(class, PACKAGE_NAME"::"COMMAND_NAME"::"), rule); + + addresses = calloc((size_t)argc, sizeof(*addresses)); + if (!addresses) + goto fail; + + for (i = 0; i < (size_t)argc; i++) { + const char *arg = argv[i]; + uintmax_t num; + if (*arg == '@' || strchr(arg, '/')) { + addresses[i] = strdup(arg); + if (addresses[i]) + goto fail; + if (*arg == '@') + addresses[i][0] = '\0'; + } else if (parse_uint(&arg[*arg == '&'], &num)) { + if (*arg == '&') { + if (num > (uintmax_t)INT_MAX) + usage(); + addresses[i] = malloc(sizeof("/proc/self/fd/") + strlen(&arg[1])); + if (!addresses[i]) + goto fail; + stpcpy(stpcpy(addresses[i], "/proc/self/fd/"), &arg[1]); + } else { + addresses[i] = malloc(sizeof("@/proc//") + strlen(arg) + strlen(class)); + if (!addresses[i]) + goto fail; + stpcpy(stpcpy(stpcpy(stpcpy(&addresses[i][1], "/proc/"), arg), "/"), class); + addresses[i][0] = '\0'; + } + } else { + usage(); + } + if (strlen(&addresses[i][1]) + 1U > sizeof(addr.sun_path)) { + fprintf(stderr, "%s: socket name '%s%s' too long\n", argv0, + addresses[i][0] ? "" : "@", &addresses[i][!addresses[i][0]]); + return 1; + } + } + + sock = socket(PF_UNIX, SOCK_DGRAM, 0); + if (sock < 0) + goto fail; + + for (j = 0; j < nmessages; j++) { + for (i = 0; i < (size_t)argc; i++) { + size_t len; + + if (!addresses[i]) + continue; + + len = strlen(&addresses[i][1]) + 1U; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, addresses[i], len); + addrlen = offsetof(struct sockaddr_un, sun_path) + len; + + if (sendto(sock, messages[j].text, messages[j].len, MSG_NOSIGNAL, + (void *)&addr, (socklen_t)addrlen) < 0) { + fprintf(stderr, "%s: error while sending to '%s': %s\n", + argv0, argv[i], strerror(errno)); + ret = 1; + addresses[i] = NULL; + } + } + free(messages[j].text); + } + + close(sock); + + for (i = 0; i < (size_t)argc; i++) + free(addresses[i]); + free(addresses); + free(messages); + free(class); + return ret; + +fail: + perror(argv0); + return 1; +} @@ -186,6 +186,35 @@ If is .RB ' no ', the above will not be done. +.TP +.BI socket= name +Select name (abstract address) of the socket created +for IPC functionality. + +If +.I name +is +.RB ' - ', +the standard input will be used, and must be a bound +.BR unix (7) +datagam socket. + +If not specified, and no-socket has not been +specified, a default string will be used, being +defined by the pattern +.RS +.nf +\fB\(dq/proc/%u/%s\(dq,\fP <\fIPID\fP>\fB,\fP <\fIclass\fP> +.fi +.RE + +Any comma +.RB ( , ) +will be interpeted as part of +.IR name . +.TP +.B no-socket +Do not create a socket for IPC functionality. .RE .TP .B -x @@ -245,6 +274,7 @@ where it is garanteed that is within [1000, 40000]. .SH SEE ALSO +.BR radharc-ipc (1), .BR coopgammad (1), .BR cg-tools (7), .BR redshift (1), @@ -1,8 +1,11 @@ /* See LICENSE file for copyright and license details. */ #include "cg-base.h" +#include <sys/socket.h> #include <sys/timerfd.h> +#include <sys/un.h> #include <errno.h> +#include <fcntl.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> @@ -13,6 +16,7 @@ #define IF_LINEARISING(...) do { if (linearise) { __VA_ARGS__; } } while (0) +#define SOCKLEN (socklen_t)sizeof /** @@ -23,7 +27,7 @@ const int64_t default_priority = (int64_t)7 << 61; /** * The default class for the program */ -char default_class[] = "radharc::radharc::standard"; +char default_class[] = PACKAGE_NAME"::"COMMAND_NAME"::standard"; /** * Class suffixes @@ -110,6 +114,16 @@ static int print_temperature = 0; */ static int linearise = 1; +/** + * The abstract address the socket shall have, + * if empty, no socket will be created, if + * `NULL` a default address derived from the + * process ID and the filter class will be used. + * If "-", standard input will be used and should + * already be bound. + */ +static const char *sockname = NULL; + /** * Set to 1 by `handle_args` if the used arguments @@ -228,6 +242,22 @@ vendor_options(char *arg) linearise = 0; else goto invalid_value; + } else if (!strcmp(arg, "socket")) { + if (next) { + next[-1] = ','; + next = NULL; + } + if (!value) + goto missing_value; + else if (!*value) + goto invalid_value; + else + sockname = value; + } else if (!strcmp(arg, "no-socket")) { + if (value) + goto unexpected_value; + else + sockname = ""; } else { fprintf(stderr, "%s: invalid -W option: %s\n", argv0, arg); exit(1); @@ -236,6 +266,10 @@ vendor_options(char *arg) return; +unexpected_value: + fprintf(stderr, "%s: -W option '%s' has an associated value, but must not\n", argv0, arg); + exit(1); + missing_value: fprintf(stderr, "%s: invalid -W option '%s' is missing associated value\n", argv0, arg); exit(1); @@ -651,6 +685,41 @@ sigusr2_handler(int sig) /** + * Called to pull all messages from the IPC socket + * + * @param sock The socket's file descriptor, or -1 if there is none + */ +static void +read_socket(int sock) +{ + char buffer[64]; + ssize_t r; + + if (sock < 0) + return; + + for (;;) { + r = recv(sock, buffer, sizeof(buffer), MSG_DONTWAIT | MSG_TRUNC); + if (r < 0) { + if (errno == EAGAIN) + break; + if (errno == EINTR || errno == ECONNREFUSED) + continue; + perror(argv0); + exit(1); + } + + if (!r || (size_t)r > sizeof(buffer)) { + fprintf(stderr, "%s: invalid length of received message: %zi\n", argv0, r); + continue; + } + + /* TODO parse message */ + } +} + + +/** * The main function for the program-specific code * * @return 0: Success @@ -674,6 +743,10 @@ start(void) size_t fade_cs; double cs; sigset_t empty_sigset; + int sock = -1; + struct sockaddr_un addr; + size_t addrlen; + struct f_owner_ex owner; sigemptyset(&empty_sigset); @@ -716,6 +789,78 @@ start(void) if (xflag) return set_ramps(1, 1, 1); + memset(&addr, 0, sizeof(addr)); + addrlen = offsetof(struct sockaddr_un, sun_path); + addr.sun_family = AF_UNIX; + if (!sockname) { + size_t len = strlen(crtc_updates[0].filter.class); + size_t off = 1U; + off += (size_t)sprintf(&addr.sun_path[off], "/proc/%ju/", (uintmax_t)getpid()); + if (len > sizeof(addr.sun_path) - 1U) { + fprintf(stderr, "%s: socket name is too long; you can may -R shorter," + "select a name with -W socket, or use -W no-socket to skip the socket\n", argv0); + return -3; + } + memcpy(&addr.sun_path[off], crtc_updates[0].filter.class, len); + addrlen += off + len; + goto create_socket; + } else if (!strcmp(sockname, "-")) { + int opt; + socklen_t len = SOCKLEN(opt); + sock = STDIN_FILENO; + if (getsockopt(sock, SOL_SOCKET, SO_DOMAIN, &opt, &len)) { + if (errno != ENOTSOCK) + return -1; + fprintf(stderr, "%s: -W socket=- used but standard input is not socket\n", argv0); + return -3; + } + if (opt != PF_UNIX) { + fprintf(stderr, "%s: -W socket=- used but standard input is not a unix socket\n", argv0); + return -3; + } + if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &opt, &len)) + return -1; + if (opt != SOCK_DGRAM) { + fprintf(stderr, "%s: -W socket=- used but standard input is not a datagram socket\n", argv0); + return -3; + } + len = SOCKLEN(addr); + if (getsockname(sock, &addr, &len)) + return -1; + if (len <= offsetof(struct sockaddr_un, sun_path) || addr.sun_family != AF_UNIX) { + fprintf(stderr, "%s: -W socket=- used but standard input is not bound to a unix socket address\n", argv0); + return -3; + } + if (len > SOCKLEN(addr)) { + fprintf(stderr, "%s: stanard input is bound to an overly long address\n", argv0); + return -3; + } + addrlen = len; + } else if (*sockname) { + size_t len = strlen(sockname); + if (len > sizeof(addr.sun_path) - 1U) { + fprintf(stderr, "%s: selected socket name is too long\n", argv0); + return -3; + } + memcpy(&addr.sun_path[1U], sockname, len); + addrlen += len + 1U; + create_socket: + sock = socket(PF_UNIX, SOCK_DGRAM, 0); + if (sock < 0) + return -1; + if (bind(sock, (void *)&addr, (socklen_t)addrlen)) + return -1; + } else { + goto no_socket; + } + if (fcntl(sock, F_SETSIG, SIGIO) < 0) + return -1; + owner.type = F_OWNER_TID; + owner.pid = gettid(); + if (fcntl(sock, F_SETOWN_EX, &owner) < 0) + return -1; +no_socket: + if ((r = make_slaves()) < 0) return r; @@ -740,6 +885,7 @@ fade_in: original_temperature = 6500; } for (i = 0; i < fade_cs;) { + read_socket(sock); if (sigint_received) { goto reverse_fade_in; } else if (sigusr2_received) { @@ -778,6 +924,7 @@ skip_fade_in: faded_in: for (;;) { + read_socket(sock); if (sigint_received) { goto fade_out; } else if (sighup_received) { @@ -806,6 +953,7 @@ faded_in: sleep_timeout.tv_nsec = 0; while ((errno = clock_nanosleep(CLOCK_BOOTTIME, 0, &sleep_timeout, &sleep_timeout))) { if (errno == EINTR) { + read_socket(sock); if (sigint_received || sighup_received || sigusr1_received || sigusr2_received) break; continue; @@ -829,6 +977,7 @@ fade_out: fade_out_have_timer: original_temperature = current_temperature; for (i = 0; i < fade_cs;) { + read_socket(sock); if (sigint_received > 1 || sigusr1_received) { goto skip_fade_out; } else if (!sigint_received && !sighup_received) { @@ -868,10 +1017,13 @@ faded_out: for (i = 0; i < filters_n; i++) crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_UNTIL_REMOVAL; for (;;) { + read_socket(sock); sigusr1_received = 0; if (sigint_received || sighup_received) { for (i = 0; i < filters_n; i++) crtc_updates[i].filter.lifespan = LIBCOOPGAMMA_REMOVE; + if (sock >= 0) + close(sock); return set_ramps(1, 1, 1); } else if (sigusr2_received) { sigusr2_received -= 1; |