diff --git a/configure.ac b/configure.ac index 7c4bdf1e..8ce6318d 100644 --- a/configure.ac +++ b/configure.ac @@ -152,6 +152,20 @@ AS_IF([test "x$enable_geoclue" != xno], [ AM_CONDITIONAL([ENABLE_GEOCLUE], [test "x$enable_geoclue" = xyes]) +# Check for interprocess communication +AC_MSG_CHECKING([whether to enable interprocess communication]) +AC_ARG_ENABLE([ipc], [AC_HELP_STRING([--enable-ipc], + [enable interprocess communication])], + [enable_ipc=$enableval],[enable_ipc=yes]) +AS_IF([test "x$enable_ipc" != xno], [ + AC_MSG_RESULT([yes]) +], [ + AC_MSG_RESULT([no]) +]) +AM_CONDITIONAL([ENABLE_IPC], [test "x$enable_ipc" = xyes]) + + + # Check for GUI status icon AC_MSG_CHECKING([whether to enable GUI status icon]) AC_ARG_ENABLE([gui], [AC_HELP_STRING([--enable-gui], @@ -243,6 +257,7 @@ echo " Location providers: Geoclue: ${enable_geoclue} + IPC: ${enable_ipc} GUI: ${enable_gui} Ubuntu icons: ${enable_ubuntu} systemd units: ${enable_systemd} ${systemduserunitdir} diff --git a/src/Makefile.am b/src/Makefile.am index e83073bc..6287387c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,10 +22,13 @@ EXTRA_redshift_SOURCES = \ gamma-randr.c gamma-randr.h \ gamma-vidmode.c gamma-vidmode.h \ gamma-w32gdi.c gamma-w32gdi.h \ - location-geoclue.c location-geoclue.h + location-geoclue.c location-geoclue.h \ + server.c server.h \ + service.c service.h AM_CFLAGS = redshift_LDADD = @LIBINTL@ +redshift_LDFLAGS = EXTRA_DIST = if ENABLE_DRM @@ -63,3 +66,9 @@ AM_CFLAGS += $(GEOCLUE_CFLAGS) $(GEOCLUE_LIBS) redshift_LDADD += \ $(GEOCLUE_LIBS) $(GEOCLUE_CFLAGS) endif + +if ENABLE_IPC +redshift_SOURCES += server.c server.h service.c service.h +AM_CFLAGS += -DENABLE_IPC +redshift_LDFLAGS += -pthread +endif diff --git a/src/ipc-client.py b/src/ipc-client.py new file mode 100755 index 00000000..5176da93 --- /dev/null +++ b/src/ipc-client.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# Simple manual IPC client for debugging + +import os +import socket +import threading + +display = os.environ["DISPLAY"] if "DISPLAY" in os.environ else "" +redshift_id = os.environ["REDSHIFT_ID"] if "REDSHIFT_ID" in os.environ else "" +path = "/dev/shm/.redshift-socket-%i-%s:%s" % (os.getuid(), display, redshift_id) + +socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +socket.connect(path) + +def recv_loop(): + got = socket.recv(1024) + if len(got) > 0: + print(got.decode("utf-8")); + +thread = threading.Thread(target = recv_loop) +thread.setDaemon(True) +thread.start() + +while True: + line = input() + if line == "": + break + socket.send((line + "\n").encode("utf-8")) + +socket.close() + diff --git a/src/redshift.c b/src/redshift.c index beecd353..debd12c3 100644 --- a/src/redshift.c +++ b/src/redshift.c @@ -76,6 +76,10 @@ # include "location-geoclue.h" #endif +#ifdef ENABLE_IPC +# include "service.h" +#endif + /* Union of state data for gamma adjustment methods */ typedef union { @@ -263,6 +267,12 @@ static int disable = 0; #endif /* ! HAVE_SIGNAL_H || __WIN32__ */ +int temp_day = -1; +int temp_night = -1; +float brightness_day = NAN; +float brightness_night = NAN; + + /* Print which period (night, day or transition) we're currently in. */ static void print_period(double elevation) @@ -661,11 +671,7 @@ main(int argc, char *argv[]) char *config_filepath = NULL; int temp_set = -1; - int temp_day = -1; - int temp_night = -1; float gamma[3] = { NAN, NAN, NAN }; - float brightness_day = NAN; - float brightness_night = NAN; const gamma_method_t *method = NULL; char *method_args = NULL; @@ -1220,6 +1226,9 @@ main(int argc, char *argv[]) printf("Status: %s\n", "Enabled"); } +#ifdef ENABLE_IPC + service_start(); +#endif /* Continuously adjust color temperature */ int done = 0; int disabled = 0; @@ -1254,6 +1263,10 @@ main(int argc, char *argv[]) ongoing transition */ short_trans = 0; } else { +#ifdef ENABLE_IPC + service_close(); +#endif + if (!disabled) { /* Make a short transition back to 6500K */ diff --git a/src/server.c b/src/server.c new file mode 100644 index 00000000..43a43349 --- /dev/null +++ b/src/server.c @@ -0,0 +1,257 @@ +/* server.c -- Interprocess communication server + This file is part of Redshift. + + Redshift 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. + + Redshift 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 Redshift. If not, see . + + Copyright (c) 2014 Mattias Andrée +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" + + + +#ifndef SOCKET_MODE +#define SOCKET_MODE 0700 +#endif + +#ifndef SOCKET_BACKLOG +#define SOCKET_BACKLOG 5 +#endif + +#ifndef SOCKET_BUFFER_SIZE +#define SOCKET_BUFFER_SIZE 2024 +#endif + +#define MAX_SOCKET_PATH 4096 + + + +static int server_fd = -1; +static char path[MAX_SOCKET_PATH]; +static pthread_t master_thread; +static volatile int running = 0; +static volatile int running_slaves = 0; +static pthread_mutex_t slave_mutex; +static pthread_cond_t slave_cond; +static socket_callback_func *callback_function; + + + +static void* +slave_loop(void *data) +{ + /* Ordered half-duplex communication between server and client. */ + + int socket_fd = (int)(long)data; + char *buffer = malloc(SOCKET_BUFFER_SIZE); + ssize_t got; + char *response; + int nresponse; + ssize_t sent; + + if (buffer == NULL) { + perror("malloc"); + } else { + while (running == 1) { + got = recv(socket_fd, buffer, SOCKET_BUFFER_SIZE, 0); + if (got == -1) { + if (errno != EINTR) perror("recv"); + break; + } + if (got == 0) + break; + + response = callback_function(socket_fd, buffer, (size_t)got); + if (response != NULL) { + nresponse = strlen(response); + while (nresponse > 0) { + sent = send(socket_fd, response, + nresponse * sizeof(char), 0); + if (sent == -1) break; + response += sent; + nresponse -= sent; + } + } + } + } + + callback_function(socket_fd, NULL, 0); + + close(socket_fd); + if (buffer != NULL) free(buffer); + + pthread_mutex_lock(&slave_mutex); + running_slaves--; + pthread_cond_signal(&slave_cond); + pthread_mutex_unlock(&slave_mutex); + + return NULL; +} + + +static void* +master_loop(void *data) +{ + int socket_fd; + pthread_t _slave_thread; /* Do not know if this is necessary, the man page does not specify. */ + + (void) data; + + running = 1; + while (running == 1) { + socket_fd = accept(server_fd, NULL, NULL); + if (socket_fd == -1) { + switch (errno) { + case EINTR: + break; + + case ECONNABORTED: + case EINVAL: + running = -1; + break; + + default: + perror("accept"); + break; + } + continue; + } + + pthread_mutex_lock(&slave_mutex); + running_slaves++; + pthread_mutex_unlock(&slave_mutex); + + errno = pthread_create(&_slave_thread, NULL, slave_loop, (void*)(long)socket_fd); + if (errno) { + perror("pthread_create"); + close(socket_fd); + running = -1; + } + } + + pthread_mutex_lock(&slave_mutex); + while (running_slaves > 0) + pthread_cond_wait(&slave_cond, &slave_mutex); + pthread_mutex_unlock(&slave_mutex); + + return NULL; +} + + +int +server_start(socket_callback_func *callback) +{ + struct sockaddr_un address; + int r; + + callback_function = callback; + + address.sun_family = AF_UNIX; + snprintf(path, MAX_SOCKET_PATH, + "/dev/shm/.redshift-socket-%i-%s:%s", getuid(), + getenv("DISPLAY") ? getenv("DISPLAY") : ""), + getenv("REDSHIFT_ID") ? getenv("REDSHIFT_ID") : ""); + strcpy(address.sun_path, path); + unlink(path); + + /* Create mutex and condition for slave counter. */ + pthread_mutex_init(&slave_mutex, NULL); + pthread_cond_init(&slave_cond, NULL); + + /* Create domain socket. */ + server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd == -1) { + perror("socket"); + return -1; + } + + /* Restrict access to socket to only the owner. + Since file does not exists yet the permissions should + be set atomically with the creation of the file. */ + r = fchmod(server_fd, SOCKET_MODE); + if (r == -1) { + perror("chmod"); + close(server_fd); + server_fd = -1; + return -1; + } + + /* Bind socket to a filename. */ + r = bind(server_fd, (struct sockaddr*)(&address), sizeof(address)); + if (r == -1) { + perror("bind"); + close(server_fd); + server_fd = -1; + unlink(path); + return -1; + } + + /* Start accepting connections. */ + r = listen(server_fd, SOCKET_BACKLOG); + if (r == -1) { + perror("listen"); + close(server_fd); + server_fd = -1; + unlink(path); + return -1; + } + + /* Start server thread. */ + errno = pthread_create(&master_thread, NULL, master_loop, NULL); + if (errno) { + perror("pthread_create"); + close(server_fd); + server_fd = -1; + unlink(path); + return -1; + } + + return 0; +} + + +void +server_close(void) +{ + /* Cooperative shutdown of service. */ + + if (server_fd != -1) { + shutdown(server_fd, SHUT_RDWR); + close(server_fd); + unlink(path); + } + + if (running) { + running = 0; + errno = pthread_join(master_thread, NULL); + if (errno) perror("pthread_join"); + } + + server_fd = -1; + + pthread_mutex_destroy(&slave_mutex); + pthread_cond_destroy(&slave_cond); +} + diff --git a/src/server.h b/src/server.h new file mode 100644 index 00000000..40b6e3f9 --- /dev/null +++ b/src/server.h @@ -0,0 +1,32 @@ +/* server.h -- Interprocess communication server + This file is part of Redshift. + + Redshift 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. + + Redshift 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 Redshift. If not, see . + + Copyright (c) 2014 Mattias Andrée +*/ + +#ifndef REDSHIFT_SERVER_H +#define REDSHIFT_SERVER_H + + +typedef char *socket_callback_func(int socket_fd, char *message, size_t length); + + +int server_start(socket_callback_func *callback); + +void server_close(void); + + +#endif diff --git a/src/service.c b/src/service.c new file mode 100644 index 00000000..3cfa1acc --- /dev/null +++ b/src/service.c @@ -0,0 +1,224 @@ +/* service.c -- Interprocess communication service + This file is part of Redshift. + + Redshift 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. + + Redshift 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 Redshift. If not, see . + + Copyright (c) 2014 Mattias Andrée +*/ + +#include +#include +#include +#include +#include + +#ifdef ENABLE_NLS +# include +# define _(s) gettext(s) +#else +# define _(s) s +#endif + +#include "server.h" +#include "service.h" + + + +#define BUFFER_SIZE 2048 + +extern int temp_day; +extern int temp_night; +extern float brightness_day; +extern float brightness_night; + + +typedef struct _service_linkedlist_t { + struct { + char *buffer; + int length; + } value; + int key; + struct _service_linkedlist_t *next; + struct _service_linkedlist_t *prev; +} service_linkedlist_t; + + +static service_linkedlist_t *edge; +static pthread_mutex_t list_mutex; + + + +static char* +service_interpret(char *message) +{ + char* rc = NULL; + int r = 1; + + if (strstr(message, "GET ") == message) { + rc = malloc(10 * sizeof(char)); + if (rc == NULL) return NULL; + } + + if (!strcmp(message, "GET TEMP DAY")) + snprintf(rc, 10, "%i", temp_day); + else if (!strcmp(message, "GET TEMP NIGHT")) + snprintf(rc, 10, "%i", temp_night); + else if (!strcmp(message, "GET BRIGHTNESS DAY")) + snprintf(rc, 10, "%.6f", brightness_day); + else if (!strcmp(message, "GET BRIGHTNESS NIGHT")) + snprintf(rc, 10, "%.6f", brightness_night); + else if (strstr(message, "SET TEMP DAY ") == message) + r = sscanf(message + strlen("SET TEMP DAY "), "%i", &temp_day); + else if (strstr(message, "SET TEMP NIGHT ") == message) + r = sscanf(message + strlen("SET TEMP NIGHT "), "%i", &temp_night); + else if (strstr(message, "SET BRIGHTNESS DAY ") == message) + r = sscanf(message + strlen("SET BRIGHTNESS DAY "), "%f", &brightness_day); + else if (strstr(message, "SET BRIGHTNESS NIGHT ") == message) + r = sscanf(message + strlen("SET BRIGHTNESS NIGHT "), "%f", &brightness_night); + else { + if (rc != NULL) free(rc); + rc = NULL; + fprintf(stderr, _("Unrecognized IPC command: %s\n"), message); + } + + if (r != 1) { + fprintf(stderr, _("Malformated value in IPC command: %s\n"), message); + } + + return rc; +} + + +static char* +service_callback(int socket_fd, char *message, size_t length) +{ + service_linkedlist_t *node = edge; + char *rc = NULL; + size_t rc_size = 0; + size_t rc_ptr = 0; + size_t i; + + do { + node = node->next; + if (node->key == socket_fd) + break; + } while (node != edge); + + if (message == NULL) { + if (node == edge) + return NULL; + pthread_mutex_lock(&list_mutex); + node->prev->next = node->next; + node->next->prev = node->prev; + pthread_mutex_unlock(&list_mutex); + if (node->value.buffer != NULL) + free(node->value.buffer); + free(node); + return NULL; + } + + if (node == edge) { + node = malloc(sizeof(service_linkedlist_t)); + if (node == NULL) { + perror("malloc"); + close(socket_fd); + return NULL; + } + node->key = socket_fd; + node->value.buffer = malloc(BUFFER_SIZE); + node->value.length = 0; + pthread_mutex_lock(&list_mutex); + node->prev = edge->next; + edge->prev = node; + node->next = edge; + node->prev->next = node; + pthread_mutex_unlock(&list_mutex); + } + + for (i = 0; i < length; i++) { + if (message[i] == '\n') { + char *r; + int n; + node->value.buffer[node->value.length] = '\0'; + r = service_interpret(node->value.buffer); + if (r != NULL) { + n = strlen(r); + if (rc == NULL) { + rc_size = n; + rc = malloc((rc_size + 1) * sizeof(char)); + if (rc == NULL) { + perror("malloc"); + close(socket_fd); + return NULL; + } + } else if (rc_ptr + n > rc_size) { + rc_size = rc_ptr + n; + rc = realloc(rc, (rc_size + 1) * sizeof(char)); + if (rc == NULL) { + perror("realloc"); + close(socket_fd); + return NULL; + } + } + memcpy(rc + rc_ptr, r, n * sizeof(char)); + rc_ptr += n; + free(r); + } + node->value.length = 0; + } else { + node->value.buffer[node->value.length++] = message[i]; + node->value.length %= BUFFER_SIZE; + } + } + + if (rc != NULL) + rc[rc_ptr] = '\0'; + return rc; +} + + +int +service_start(void) +{ + pthread_mutex_init(&list_mutex, NULL); + + edge = malloc(sizeof(service_linkedlist_t)); + if (edge == NULL) { + perror("malloc"); + return -1; + } + + edge->next = edge; + edge->prev = edge; + edge->value.buffer = NULL; + edge->value.length = 0; + edge->key = -1; + + return server_start(service_callback); +} + + +void +service_close(void) +{ + server_close(); + + if (edge != NULL) { + free(edge); + edge = NULL; + } + + pthread_mutex_destroy(&list_mutex); +} + diff --git a/src/service.h b/src/service.h new file mode 100644 index 00000000..033fb441 --- /dev/null +++ b/src/service.h @@ -0,0 +1,29 @@ +/* service.h -- Interprocess communication service + This file is part of Redshift. + + Redshift 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. + + Redshift 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 Redshift. If not, see . + + Copyright (c) 2014 Mattias Andrée +*/ + +#ifndef REDSHIFT_SERVICE_H +#define REDSHIFT_SERVICE_H + + +int service_start(void); + +void service_close(void); + + +#endif