From a4e73470e28bed34f014aeb613252d56909ddeab Mon Sep 17 00:00:00 2001 From: MCorange Date: Fri, 31 May 2024 02:11:05 +0300 Subject: [PATCH 1/2] Hot reloading + state saving --- Makefile | 6 +- src/include/plug.h | 19 +++-- src/include/socket.h | 19 +++++ src/include/util.h | 9 ++ src/main.c | 57 ++++++++++++- src/modules/battery.c | 47 +++++++---- src/modules/clock.c | 34 ++++++-- src/modules/example.c | 35 ++++++++ src/plug.c | 79 ++++++++++++++---- src/socket.c | 189 ++++++++++++++++++++++++++++++++++++++++++ src/util.c | 14 ++++ 11 files changed, 455 insertions(+), 53 deletions(-) create mode 100644 src/include/socket.h create mode 100644 src/include/util.h create mode 100644 src/modules/example.c create mode 100644 src/socket.c create mode 100644 src/util.c diff --git a/Makefile b/Makefile index e92cdaa..347ab7f 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,13 @@ CC=gcc CCARGS=-Isrc/include -Wall -pedantic MODULES= \ - modules/clock.dim \ - modules/battery.dim\ + modules/clock.dim \ + modules/battery.dim all: $(BIN) $(MODULES) compile_commands.json -$(BIN): src/main.c src/plug.c +$(BIN): src/main.c src/plug.c src/socket.c src/util.c $(CC) -o $@ $^ $(CCARGS) -lX11 -ldl diff --git a/src/include/plug.h b/src/include/plug.h index 9b467ad..dec072f 100644 --- a/src/include/plug.h +++ b/src/include/plug.h @@ -5,28 +5,31 @@ #include #include -typedef void (*plug_init_f) (void); -typedef void (*plug_reload_f) (void); -typedef void (*plug_poll_f) (char*, size_t); -typedef void (*plug_free_f) (void); +typedef void* (*plug_init_f) (void); +typedef void* (*plug_pre_reload_f) (void); +typedef void (*plug_post_reload_f) (void*); +typedef void (*plug_poll_f) (char*, size_t); +typedef void (*plug_free_f) (void); typedef struct plug_t { char* name; char* version; - bool is_ready; } plug_t; typedef struct plug_int_t { - plug_init_f f_init; - plug_reload_f f_reload; + char* path; + plug_init_f f_init; + plug_pre_reload_f f_pre_reload; + plug_post_reload_f f_post_reload; plug_poll_f f_poll; plug_free_f f_free; - plug_t* s_plug_info; + plug_t* state; } plug_int_t; void setup_plugins(char* plugin_path); char* poll_plugins(char* sep); void free_plugins(void); +void reload_plugins(void); #endif diff --git a/src/include/socket.h b/src/include/socket.h new file mode 100644 index 0000000..edd2579 --- /dev/null +++ b/src/include/socket.h @@ -0,0 +1,19 @@ + +#ifndef _H_SOCKET +#define _H_SOCKET + +#include +#include +#include + +typedef enum sock_msg_t{ + SM_NONE = 0, + SM_RELOAD = 1, +} sock_msg_t; + +int socket_init(void); +sock_msg_t socket_poll(void); +void socket_close(void); +void socket_send(char*); + +#endif diff --git a/src/include/util.h b/src/include/util.h new file mode 100644 index 0000000..75d34fd --- /dev/null +++ b/src/include/util.h @@ -0,0 +1,9 @@ + +#ifndef _H_UTIL +#define _H_UTIL + +void to_lowercase(char* str); +void to_uppercase(char* str); + +#endif + diff --git a/src/main.c b/src/main.c index 8a240d4..a332738 100644 --- a/src/main.c +++ b/src/main.c @@ -2,9 +2,39 @@ #include #include #include -#include "plug.h" +#include +#include + +#include "plug.h" +#include "socket.h" + +void interrupt_handler(int); +void print_help(void); + +int main(int argc, char** argv) { + signal(SIGINT, interrupt_handler); + + if (argc > 1) { + if (strcmp(argv[1], "send") == 0) { + if (argc == 3) { + socket_send(argv[2]); + return 0; + } else { + print_help(); + return 1; + } + } else { + print_help(); + printf("ERROR: Unknown subcommand\n"); + return 1; + } + } + + + if (socket_init() != 0) { + return 1; + } -int main(void) { setup_plugins("./modules"); char* buffer; @@ -12,6 +42,15 @@ int main(void) { Window window = DefaultRootWindow(display); while (1) { + sock_msg_t msg = socket_poll(); + + switch (msg) { + case SM_RELOAD: { + reload_plugins(); + } break; + case SM_NONE: break; + } + buffer = poll_plugins("|"); XStoreName(display, window, buffer); XFlush(display); @@ -24,3 +63,17 @@ int main(void) { return 0; } + +void interrupt_handler(int sig) { + (void) sig; + printf("\nCaught SIGINT, exiting\n"); + free_plugins(); + socket_close(); + exit(0); +} + +void print_help(void) { + printf("Usage: dim [command]\n"); + printf("Commands:\n"); + printf(" send [MESSAGE] - Sends a message, available messages: [reload]\n"); +} diff --git a/src/modules/battery.c b/src/modules/battery.c index 74b2753..08dd0e8 100644 --- a/src/modules/battery.c +++ b/src/modules/battery.c @@ -1,44 +1,61 @@ -#include "plug.h" #include +#include #include -plug_t PLUG = {0}; +#include +#include + +typedef struct plug_t { + char* name; + char* version; + FILE* fp; +} plug_t; + + +static plug_t* p = {0}; -static FILE* fp = NULL; char* batt_files[] = { "/sys/class/power_supply/BAT0/capacity", "/sys/class/power_supply/BAT1/capacity" }; -void plug_init(void) { - PLUG.name = "Battery"; - PLUG.version = "0.0.1"; +void* plug_init(void) { + p = malloc(sizeof(plug_t)); + assert(p); + p->name = "Battery"; + p->version = "0.0.1"; for (size_t i = 0; i < sizeof(batt_files)/sizeof(batt_files[0]); i++) { if (access(batt_files[i], F_OK) == 0) { - fp = fopen(batt_files[i], "r"); - if (fp) { + p->fp = fopen(batt_files[i], "r"); + if (p->fp) { printf("Opened %s\n", batt_files[i]); break; } } } - if (!fp) { + if (!p->fp) { printf("MODULE: ERROR: Unable to find battery\n"); - return; + return NULL; } - PLUG.is_ready = true; + return p; } -void plug_reload(void) { /* Unused */ } +void* plug_pre_reload(void) { + return p; +} + +void plug_post_reload(void* pp) { + p = pp; +} void plug_poll(char* buf, size_t len) { - if (!fp) return; + if (!p->fp) return; int perc = 0; - fseek(fp, 0, SEEK_SET); - fscanf(fp, "%d", &perc); + fseek(p->fp, 0, SEEK_SET); + fscanf(p->fp, "%d", &perc); snprintf(buf, len, "Batt: %d%%", perc); } diff --git a/src/modules/clock.c b/src/modules/clock.c index d46e37a..4d83896 100644 --- a/src/modules/clock.c +++ b/src/modules/clock.c @@ -1,15 +1,33 @@ -#include "plug.h" #include +#include +#include +#include -plug_t PLUG = {0}; +typedef struct plug_t { + char* name; + char* version; + int i; +} plug_t; -void plug_init(void) { - PLUG.name = "time"; - PLUG.version = "0.0.1"; - PLUG.is_ready = true; + +plug_t* p = {0}; + +void* plug_init(void) { + p = malloc(sizeof(plug_t)); + p->name = "time"; + p->version = "0.0.1"; + p->i = 0; + return p; } -void plug_reload(void) { /* Unused */ } +void* plug_pre_reload(void) { + return p; +} + +void plug_post_reload(void* pp) { + p = pp; + p->i += 1; +} void plug_poll(char* buf, size_t len) { time_t rawtime; @@ -17,7 +35,7 @@ void plug_poll(char* buf, size_t len) { time(&rawtime); timeinfo = localtime(&rawtime); - + strftime(buf, len, "Time: %H:%M (%Y-%m-%d)", timeinfo); } diff --git a/src/modules/example.c b/src/modules/example.c new file mode 100644 index 0000000..9b43516 --- /dev/null +++ b/src/modules/example.c @@ -0,0 +1,35 @@ +#include +#include + +typedef struct plug_t { + char* name; // Always keep these on top + char* version; +} plug_t; + + +static plug_t* p = {0}; + + +void* plug_init(void) { + p = malloc(sizeof(plug_t)); + assert(p); + p->name = "example"; + p->version = "0.0.1"; + return p; +} + +void* plug_pre_reload(void) { + return p; // send state to dim +} + +void plug_post_reload(void* pp) { + p = pp; // get back state +} + +void plug_poll(char* buf, size_t len) { + // print text to `buf` with max len `len` +} + +void plug_free(void) { + free(p); // free state +} diff --git a/src/plug.c b/src/plug.c index 592f8d2..97540a6 100644 --- a/src/plug.c +++ b/src/plug.c @@ -21,7 +21,7 @@ typedef struct PlugMan { static plugman_t PLUGMAN = {0}; void* load_plug_sym(void* plug_f, char* fn_name); -plug_int_t* load_plugin(char* path); +plug_int_t* load_plugin(char* path, bool); void load_plugins(const char* mod_dir_p) { DIR* dir = {0}; @@ -56,12 +56,11 @@ void load_plugins(const char* mod_dir_p) { printf("INFO: Found plugin: %s/%s\n", mod_dir_p, dir_entry->d_name); - char full_path[PLUG_MAX_COUNT] = {0}; + char* full_path = (char*)malloc(sizeof(char)*1028); snprintf(full_path, PLUG_MAX_COUNT, "%s/%s", mod_dir_p, dir_entry->d_name); - plug_int_t* plug = load_plugin(full_path); + plug_int_t* plug = load_plugin(full_path, true); if (!plug) { - printf("ERROR: Failed to load plugin %s, skipping\n", full_path); continue; } PLUGMAN.plugs[PLUGMAN.plug_count++] = plug; @@ -69,8 +68,19 @@ void load_plugins(const char* mod_dir_p) { } -plug_int_t* load_plugin(char* path) { +plug_int_t* load_plugin(char* path, bool check_duplicates) { + if (check_duplicates) { + for (size_t i = 0; i < PLUGMAN.plug_count; i++) { + plug_int_t* plug = PLUGMAN.plugs[i]; + if (strcmp(plug->path, path) == 0) { + return NULL; // plugin exists + } + } + } + plug_int_t* plug = malloc(sizeof(plug_int_t)); + plug->path = path; + void* plug_f = dlopen(path, RTLD_NOW); if (!plug_f) { @@ -82,18 +92,19 @@ plug_int_t* load_plugin(char* path) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" - plug->f_init = (plug_init_f)load_plug_sym(plug_f, "plug_init"); - plug->f_reload = (plug_reload_f)load_plug_sym(plug_f, "plug_reload"); - plug->f_poll = (plug_poll_f)load_plug_sym(plug_f, "plug_poll"); - plug->f_free = (plug_free_f)load_plug_sym(plug_f, "plug_free"); - plug->s_plug_info = (plug_t*)load_plug_sym(plug_f, "PLUG"); + plug->f_init = (plug_init_f) load_plug_sym(plug_f, "plug_init"); + plug->f_pre_reload = (plug_pre_reload_f) load_plug_sym(plug_f, "plug_pre_reload"); + plug->f_post_reload = (plug_post_reload_f) load_plug_sym(plug_f, "plug_post_reload"); + plug->f_poll = (plug_poll_f) load_plug_sym(plug_f, "plug_poll"); + plug->f_free = (plug_free_f) load_plug_sym(plug_f, "plug_free"); #pragma GCC diagnostic pop - if (!plug->f_init || !plug->f_reload || + if (!plug->f_init || !plug->f_pre_reload || !plug->f_poll || !plug->f_free || - !plug->s_plug_info) { + !plug->f_post_reload) { dlclose(plug_f); + printf("ERROR: Failed to find all functions in plugin"); return NULL; } @@ -119,8 +130,14 @@ void setup_plugins(char* plugin_path) { for (size_t i = 0; i < PLUGMAN.plug_count; i++) { plug_int_t* plug = PLUGMAN.plugs[i]; - (plug->f_init)(); - printf(" - %s (%s)\n", plug->s_plug_info->name, plug->s_plug_info->version); + plug->state = (plug->f_init)(); + + if (!plug->state) { + printf("Plugin '%s' did not initialise, skipping\n", plug->path); + continue; + } + + printf(" - %s (%s)\n", plug->state->name, plug->state->version); } } @@ -147,10 +164,10 @@ char* poll_plugins(char* sep) { for (size_t i = 0; i < PLUGMAN.plug_count; i++) { plug_int_t* plug = PLUGMAN.plugs[i]; - if (!plug->s_plug_info->is_ready) continue; + if (!plug->state) continue; - printf("Polling plug id %zu\n", i); + // printf("Polling plug id %zu\n", i); size_t len = strlen(buf); (plug->f_poll)(buf + len, PLUG_POLL_BUF_SZ - len); @@ -160,10 +177,38 @@ char* poll_plugins(char* sep) { } } - printf("Setting title: '%s'\n", buf); + // printf("Setting title: '%s'\n", buf); return buf; } +void reload_plugins(void) { + void* plug_states[PLUG_MAX_COUNT]; + size_t plug_count = 0; + + printf("INFO: Reloading plugins\n"); + // save states + for (size_t i = 0; i < PLUGMAN.plug_count; i++) { + plug_int_t* plug = PLUGMAN.plugs[i]; + void* state = (plug->f_pre_reload)(); + plug_states[plug_count++] = state; + } + + // unload plugins + for (size_t i = 0; i < PLUGMAN.plug_obj_count; i++) { + dlclose(PLUGMAN.plug_objs[i]); + } + + PLUGMAN.plug_obj_count = 0; + + for (size_t i = 0; i < PLUGMAN.plug_count; i++) { + plug_int_t* plug = PLUGMAN.plugs[i]; + plug = load_plugin(plug->path, false); + (plug->f_post_reload)(plug_states[i]); + } + + load_plugins("./modules"); +} + diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..2d155c3 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "socket.h" +#include "util.h" + +#define SOCKET_PATH "/tmp/dim.sock" +#define BUFFER_SIZE 255 + +typedef struct sock_server_t { + int s_fd; + int c_fd; + struct sockaddr_un s_addr; + char buf[BUFFER_SIZE]; + int max_fd; + fd_set read_fds; +} sock_server_t; + +sock_server_t SOCK_SERVER = {0}; + + +int socket_init(void) { + + // Check if socket exists + if (access(SOCKET_PATH, F_OK) == 0) { + remove(SOCKET_PATH); + } + + + // Create the socket + SOCK_SERVER.s_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (SOCK_SERVER.s_fd == -1) { + perror("ERROR: Failed to create socket"); + return 1; + } + + int flags = fcntl(SOCK_SERVER.s_fd, F_GETFL, 0); + if (fcntl(SOCK_SERVER.s_fd, F_SETFL, flags | O_NONBLOCK) == -1) { + perror("ERROR: Failed to set socket to non blocking"); + close(SOCK_SERVER.s_fd); + return 1; + } + + // Set up the server address structure + memset(&SOCK_SERVER.s_addr, 0, sizeof(struct sockaddr_un)); + SOCK_SERVER.s_addr.sun_family = AF_UNIX; + strncpy(SOCK_SERVER.s_addr.sun_path, SOCKET_PATH, sizeof(SOCK_SERVER.s_addr.sun_path) - 1); + + // Bind the socket to the address + int berr = bind( + SOCK_SERVER.s_fd, + (struct sockaddr *)&SOCK_SERVER.s_addr, + sizeof(struct sockaddr_un)); + + if (berr == -1) { + perror("ERROR: Failed to bind to socket"); + close(SOCK_SERVER.s_fd); + return 1; + } + + // Listen for incoming connections + if (listen(SOCK_SERVER.s_fd, 5) == -1) { + perror("ERRROR: Failed to start listening for messages in the socket"); + close(SOCK_SERVER.s_fd); + return 1; + } + + printf("INFO: Server is listening on %s\n", SOCKET_PATH); + return 0; +} + +struct timeval timeout = {0}; + +sock_msg_t socket_poll(void) { + FD_ZERO(&SOCK_SERVER.read_fds); + FD_SET(SOCK_SERVER.s_fd, &SOCK_SERVER.read_fds); + SOCK_SERVER.max_fd = SOCK_SERVER.s_fd; + + // Add client sockets to the set + if (SOCK_SERVER.c_fd > 0) { + FD_SET(SOCK_SERVER.c_fd, &SOCK_SERVER.read_fds); + if (SOCK_SERVER.c_fd > SOCK_SERVER.max_fd) { + SOCK_SERVER.max_fd = SOCK_SERVER.c_fd; + } + } + // Wait for an activity on one of the sockets + int activity = select(SOCK_SERVER.max_fd + 1, &SOCK_SERVER.read_fds, NULL, NULL, &timeout); + + if (activity < 0 && errno != EINTR) { + perror("select error"); + } + + + // If something happened on the server socket, it's an incoming connection + if (FD_ISSET(SOCK_SERVER.s_fd, &SOCK_SERVER.read_fds)) { + if ((SOCK_SERVER.c_fd = accept(SOCK_SERVER.s_fd, NULL, NULL)) == -1) { + if (errno != EWOULDBLOCK && errno != EAGAIN) { + perror("accept error"); + } + } else { + //printf("Accepted new connection\n"); + } + } + + + // If something happened on the client socket, it's incoming data + if (SOCK_SERVER.c_fd > 0 && FD_ISSET(SOCK_SERVER.c_fd, &SOCK_SERVER.read_fds)) { + int num_bytes = read(SOCK_SERVER.c_fd, SOCK_SERVER.buf, BUFFER_SIZE - 1); + if (num_bytes > 0) { + SOCK_SERVER.buf[num_bytes] = '\0'; // Null-terminate the buffer + return (sock_msg_t)atoi(SOCK_SERVER.buf); + //printf("Received message: %s\n", SOCK_SERVER.buf); + } else if (num_bytes == 0) { + // Client disconnected + //printf("Client disconnected\n"); + close(SOCK_SERVER.c_fd); + SOCK_SERVER.c_fd = -1; + } else { + if (errno != EWOULDBLOCK && errno != EAGAIN) { + perror("read error"); + } + } + } + return SM_NONE; +} + +void socket_close(void) { + close(SOCK_SERVER.s_fd); + unlink(SOCKET_PATH); +} + +void socket_send(char* msg_str) { + to_lowercase(msg_str); + + sock_msg_t msg_t = SM_NONE; + + if (strcmp(msg_str, "reload") == 0) { + msg_t = SM_RELOAD; + } else { + printf("ERROR: Unknown message type '%s'\n", msg_str); + printf("NOTE: Available messages:\n"); + printf(" - reload\n"); + return; + } + + int client_fd; + struct sockaddr_un server_addr; + + // Create the socket + if ((client_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("ERROR: Failed to create error"); + return; + } + + // Set up the server address structure + memset(&server_addr, 0, sizeof(struct sockaddr_un)); + server_addr.sun_family = AF_UNIX; + strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1); + + // Connect to the server + if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_un)) == -1) { + perror("ERROR: Failed to connect to server"); + close(client_fd); + return; + } + + char msg[255]; + + snprintf(msg, 255, "%d", msg_t); + + // Send a message to the server + if (write(client_fd, msg, strlen(msg)) == -1) { + perror("ERROR: Failed to write message to server"); + close(client_fd); + return; + } + + // Clean up + close(client_fd); +} + + diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..9fb3b58 --- /dev/null +++ b/src/util.c @@ -0,0 +1,14 @@ + +#include + +void to_lowercase(char* str) { + for(int i = 0; str[i]; i++){ + str[i] = tolower(str[i]); + } +} + +void to_uppercase(char* str) { + for(int i = 0; str[i]; i++){ + str[i] = tolower(str[i]); + } +} From 7e2cbbe370817db26240e20881276b7c68b5b544 Mon Sep 17 00:00:00 2001 From: MCorange Date: Fri, 31 May 2024 02:12:58 +0300 Subject: [PATCH 2/2] remove build.sh --- build.sh | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 build.sh diff --git a/build.sh b/build.sh deleted file mode 100644 index 641002a..0000000 --- a/build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/bash - -set -xe -mkdir -p modules - -CCARGS="-Isrc/include -Wall -pedantic" - -cc -o dim src/main.c src/plug.c $CCARGS -lX11 -ldl -cc -o modules/clocky.dim src/modules/clock.c -rdynamic -shared -fPIC $CCARGS -cc -o modules/battery.dim src/modules/battery.c -rdynamic -shared -fPIC $CCARGS - -