diff options
Diffstat (limited to 'experimental')
-rw-r--r-- | experimental/Makefile | 14 | ||||
-rw-r--r-- | experimental/keyboard-leds-trayicons.c | 881 | ||||
-rw-r--r-- | experimental/keyboard-leds-trayicons.h | 49 |
3 files changed, 805 insertions, 139 deletions
diff --git a/experimental/Makefile b/experimental/Makefile index 17c9660..9d94f55 100644 --- a/experimental/Makefile +++ b/experimental/Makefile @@ -1,3 +1,4 @@ +# Startdate: 2022-09-28 # References: # BUILD_DIR and per-.cpp file https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ # https://bgstack15.ddns.net/blog/posts/2019/11/04/sample-makefile/ 2022-09-28-4 15:34 @@ -23,6 +24,7 @@ CCFLAGS = -g -Wno-deprecated-declarations \ `pkg-config --cflags gtk+-3.0` \ `pkg-config --cflags inih` \ $(DEBUGFLAGS) +# `pkg-config --cflags xapp` \ CCFLAGS2 = -g -Wsign-conversion -Werror \ `pkg-config --cflags inih` @@ -32,12 +34,22 @@ LDFLAGS = -g \ `pkg-config --libs x11` \ `pkg-config --libs gtk+-3.0` \ `pkg-config --libs inih` + +ifneq (,$(ENABLE_LIBXDO)) +CCFLAGS += -DENABLE_LIBXDO=1 +LDFLAGS += -lxdo +# -lxdo is not available in pkg-config. +endif + LDFLAGS2 = -g \ `pkg-config --libs inih` ifneq (,$(DEBUG)) CCFLAGS+=-DDEBUG=1 endif +ifneq (,$(ENABLE_SOCKETS)) +CCFLAGS+=-DENABLE_SOCKETS=1 +endif src = $(wildcard *.c) src := $(filter-out readconf.c, $(src)) @@ -73,7 +85,7 @@ $(OUTEXE): $(obj) .PHONY: clean cleanall list list: - @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | ${awkbin} -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | ${sortbin} | ${grepbin} -E -v -e '^[^[:alnum:]]' -e '^$@$$' -e '\.(cp*|o)' + @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | ${awkbin} -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | ${sortbin} | ${grepbin} -E -v -e '^[^[:alnum:]]' -e '^$@$$' -e '\.(cp*|o)' -e '^$$' clean: rm -f $(obj) $(obj2) diff --git a/experimental/keyboard-leds-trayicons.c b/experimental/keyboard-leds-trayicons.c index 4608b18..f516994 100644 --- a/experimental/keyboard-leds-trayicons.c +++ b/experimental/keyboard-leds-trayicons.c @@ -5,7 +5,7 @@ * Startdate: 2022-09-28-4 13:30 * SPDX-License-Identifier: GPL-3.0 * Title: Proof of Concept C utility for polling capslock and numlock - * Purpose: Demonstrate these can be done in C, with the eventual goal of rewriting keyboard-leds-trayicons entirely in C to avoid the "sleep 0.75" in ps output + * Purpose: Capslock and numlock indicators for system tray, in C * History: * Usage: * References: @@ -22,29 +22,29 @@ * https://stackoverflow.com/questions/108183/how-to-prevent-sigpipes-or-handle-them-properly * https://cboard.cprogramming.com/c-programming/150795-signal-sigaction-conversion-printable-thread.html * http://maemo.cloud-7.de/irclogs/freenode/_devuan-dev/_devuan-dev.2022-10-01.log.html + * https://stackoverflow.com/questions/190229/where-is-the-itoa-function-in-linux/29544416#29544416 + * https://www.geeksforgeeks.org/socket-programming-cc/ * Improvements: - * Use getopts, -h, -c conffile, -d (debug) - * fix all return values at end - * Dependencies: libx11-dev, libinih-dev, libgtk-3-dev + * Maybe someday use connect_wait from https://monoxid.net/c/socket-connection-timeout/ when it works + * Fix the occasional "malloc(): invalid next size (unsorted)" when pressing capslock. + * Dependencies: + * build: libx11-dev, libinih-dev, libgtk-3-dev, libxdo-dev * Documentation: * features include: - * operate when only one trayicon has exited + * still operate when only one trayicon has exited * exit when both trayicons have exited + * use sockets to communicate to trayicons + * use signals to clean up when trayicons exit + * combined sockets and fifos: run with '-s' to use Unix socket, if compiled with ENABLE_SOCKETS=1 + * left-click the icon to toggle state using xdotool (or libxdo if ENABLE_LIBXDO), disable with -n + * show tooltips */ -#include <ctype.h> -#include <fcntl.h> -#include <glib.h> -#include <gtk/gtk.h> -#include <ini.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <X11/XKBlib.h> -#include <X11/Xlib.h> -#include <signal.h> +#include "keyboard-leds-trayicons.h" + +#ifdef ENABLE_SOCKETS +int use_sockets = 0; +#endif int debug = 0; Display* dpy; @@ -58,8 +58,78 @@ typedef struct { const char* num_fifo; } configuration; char* conffile = "none"; +const int BUFLEN = 1000; char msg[1000]; +#ifdef ENABLE_SOCKETS +char child_pid_str[1000]; +#endif int closed_icons = 0; +configuration config; +struct stat st_C, st_N; +pid_t pid, pid_C, pid_N; +int fd_C, fd_N; +int click_to_switch = 1; // invoke with -n + +#ifdef ENABLE_SOCKETS +void remove_first_newline(char *string) { + // ref: https://stackoverflow.com/a/9628684 + int ii; + for(ii = 0;;ii++) { + if (string[ii] == '\n') { + string[ii] = '\0'; + break; + } + } +} +#endif + +// useful only when using fifo +void sigfun_main(int sig) { + // pressed CTRL+C, which was set with sigaction() in Step 7 + printf("Pressed CTRL+C\n"); +#ifdef ENABLE_SOCKETS + if (use_sockets) { } else { +#endif + unlink(config.caps_fifo); + unlink(config.num_fifo); +#ifdef ENABLE_SOCKETS + } +#endif + (void) signal(SIGINT, SIG_DFL); + exit(0); +} + +void sigfun_child(int sig) { + // killed + kill(pid,SIGUSR1); + (void) signal(SIGTERM, SIG_DFL); + exit(0); +} + +/* reverse: reverse string s in place */ +void klt_reverse(char s[]) { + int i, j; + char c; + for (i = 0, j = strlen(s)-1; i<j; i++, j--) { + c = s[i]; + s[i] = s[j]; + s[j] = c; + } +} +/* itoa: convert n to characters in s */ +void itoa(int n, char s[]) { + int i, sign; + if ((sign = n) < 0) /* record sign */ + n = -n; /* make n positive */ + i = 0; + do { /* generate digits in reverse order */ + s[i++] = n % 10 + '0'; /* get next digit */ + } while ((n /= 10) > 0); /* delete it */ + if (sign < 0) + s[i++] = '-'; + s[i] = '\0'; + klt_reverse(s); +} /* START inclusion of mktrayicon.c, 536 lines */ /* @@ -111,19 +181,32 @@ GtkStatusIcon *icon; char *onclick = NULL; void tray_icon_on_click(GtkStatusIcon *status_icon, gpointer user_data) { - if (onclick != NULL && fork() == 0) { - execl("/bin/sh", "sh", "-c", onclick, (char *)NULL); - } + if (onclick != NULL) { +#ifdef ENABLE_LIBXDO + if (strcmp(onclick,"CAPS") == 0) { + // xdo_new() does not like being initialized into a global variable? + xdo_t *xdo_context = xdo_new(NULL); + xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Caps_Lock", 0); + xdo_free(xdo_context); + } else if (strcmp(onclick,"NUM") == 0) { + xdo_t *xdo_context = xdo_new(NULL); + xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Num_Lock", 0); + xdo_free(xdo_context); + } else +#endif + if (fork() == 0) + execl("/bin/sh", "sh", "-c", onclick, (char *)NULL); + } } void tray_icon_on_middleclick(GtkStatusIcon *status_icon, GdkEventButton *event, gpointer user_data) { if (2 == event->button) { if (onmiddleclick == NULL) { - if(debug) + if (debug) printf("middleclick, but no command specified\n"); } else { - if(debug) + if (debug) printf("middleclick\n"); if (onmiddleclick != NULL && fork() == 0) { execl("/bin/sh", "sh", "-c", onmiddleclick, (char *)NULL); @@ -147,7 +230,7 @@ void click_menu_item(GtkMenuItem *menuitem, gpointer user_data) { void tray_icon_on_menu(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer user_data) { - if(debug) + if (debug) printf("Popup menu\n"); if (menusize) { gtk_menu_popup_at_pointer((GtkMenu *)menu, NULL); @@ -170,7 +253,7 @@ void tray_icon_on_scroll(GtkStatusIcon *status_icon, GdkEventScroll *event, break; } if (i != NULL) { - if(debug) + if (debug) printf("scroll %s\n",i); } } @@ -178,13 +261,13 @@ void tray_icon_on_scroll(GtkStatusIcon *status_icon, GdkEventScroll *event, gboolean set_tooltip(gpointer data) { char *p = (char *)data; if (*p == '\0') { - if(debug) - printf("Removing tooltip\n"); + if (debug) + printf("Removing tooltip\n"); gtk_status_icon_set_has_tooltip(icon, FALSE); return FALSE; } - if(debug) + if (debug) printf("Setting tooltip to '%s'\n", p); gtk_status_icon_set_tooltip_text(icon, p); free(data); @@ -193,7 +276,7 @@ gboolean set_tooltip(gpointer data) { gboolean set_icon(gpointer data) { char *p = (char *)data; - if(debug) + if (debug) printf("Setting icon to '%s'\n", p); if (strchr(p, '/')) { gtk_status_icon_set_from_file(icon, p); @@ -214,6 +297,367 @@ gboolean do_quit(gpointer data) { return FALSE; } +#ifdef ENABLE_SOCKETS +gpointer watch_socket(gpointer data) { + char buffer[BUFLEN]; + int enable = 1; + int thispid = getpid(); + char *sockfile_local = (char *)data; + int server_sockfd, client_sockfd; + socklen_t client_len; + ssize_t nbytes_read; + struct sockaddr_un client_address, server_address; + char *rread, *param; + char cmd, quote; + size_t len, i; + char *tmp = malloc(1024 * sizeof(char)); + char *buf = malloc(1024 * sizeof(char)); + + server_sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (server_sockfd == -1) { + perror("server socket"); + exit(EXIT_FAILURE); + } + + if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) { + perror("server setsockopt(SO_REUSEADDR) failed"); + exit(EXIT_FAILURE); + } + + server_address.sun_family = AF_LOCAL; + strncpy(server_address.sun_path,sockfile_local,strlen(sockfile_local)); + if (bind(server_sockfd,(struct sockaddr*)&server_address, sizeof(server_address) + ) == -1 + ) { + perror("server bind"); + exit(EXIT_FAILURE); + } + + if (listen(server_sockfd, 5) == -1) { + perror("server listen"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "trayicon %d listening on socket %s\n", thispid, sockfile_local); + signal(SIGPIPE, SIG_IGN); + + while (1) { + client_len = sizeof(client_address); + client_sockfd = accept( + server_sockfd, + (struct sockaddr*)&client_address, + &client_len + ); + while ((nbytes_read = read(client_sockfd, buffer, BUFLEN)) > 0) { + rread = buffer; + //printf("received:\n"); + //write(STDOUT_FILENO, buffer, nbytes_read); + + // START copy-pasted in from watch_fifo + /* trim string */ + while ((*rread == '\n' || *rread == ' ' || *rread == '\t') && *rread != '\0') { + rread++; + } + + if (*rread == '\0') { + /* empty command */ + continue; + } + + cmd = *rread; + len = strlen(rread); + if (len < 3) { + param = NULL; + } else if (*(rread + 2) != '\'' && *(rread + 2) != '"') { + // unquoted string + rread += 2; + len -= 2; + // trim trailing whitespace + i = len - 1; + while (i > 0) { + if (!isspace(rread[i])) { + len = i + 1; + rread[len] = '\0'; + break; + } + i -= 1; + } + if (debug >= 2){ + fprintf(stderr,"malloc1: %i: ",(len + 1)); + }; + param = malloc((len + 1) * sizeof(char)); + if (debug >= 2){fprintf(stderr,"DONE-malloc1\n");}; + strncpy(param, rread, len + 1); + } else { + // quoted string + quote = *(rread + 2); + rread += 3; + len -= 3; + *tmp = '\0'; + *(tmp + 1024 - 1) = '\0'; + // keep track of what we have so far + strncpy(tmp, rread, 1023); + + // now keep reading until we have the end quote + while (1) { + // check for terminating ' + if (len != 0) { + // search backwards past whitespace + i = len - 1; + while (i > 0) { + if (!isspace(tmp[i])) { + break; + } + i -= 1; + } + if (tmp[i] == quote) { + // maybe the end! + // let's make sure it isn't escaped + if (i >= 2 && tmp[i - 2] == '\\') { + } else { + // it's not! + // we're done. + // trim off the ' and + // any whitespace we walked past + len = i; + tmp[len] = '\0'; + break; + } + } + } + + if (len == 1023) { + // we can't read any more + // but also haven't found the end + // forcibly terminate the string + fprintf(stderr, "Quoted string too long (max 1023 chars)\n"); + break; + } + + // FIXME unfinished imported logic + // we don't have the end of the string yet + //read = fgets(buf, 1024 * sizeof(char), fifo); + // nbytes_read = read(client_sockfd, buffer, BUFLEN) + //if (read == NULL) { + // /* no more data in pipe, reopen and block */ + // fclose(fifo); + // goto outer; + //} + // note that we don't trim here, because we're + // in a quoted string. + strncpy(tmp + len, rread, 1023 - len); + len += strlen(tmp + len); + } + + // quoted string is now in param[0:len] + param = malloc((len + 1) * sizeof(char)); + strncpy(param, tmp, len + 1); + } + remove_first_newline(param); + + switch (cmd) { + case 'q': + gdk_threads_add_idle(do_quit, param); + if (param != NULL) { + free(param); + } + break; + case 't': /* tooltip */ + gdk_threads_add_idle(set_tooltip, param); + break; + case 'i': /* icon */ + gdk_threads_add_idle(set_icon, param); + break; + case 'h': /* hide */ + gdk_threads_add_idle(set_visible, (void *)0); + if (param != NULL) { + free(param); + } + break; + case 's': /* show */ + gdk_threads_add_idle(set_visible, (void *)1); + if (param != NULL) { + free(param); + } + break; + case 'c': /* click */ + if (onclick != NULL) { + free(onclick); + onclick = NULL; + } + + if (param != NULL && *param == '\0') { + if (debug) + printf("Removing onclick handler\n"); + free(param); + break; + } + + onclick = param; + if (debug) + printf("Setting onclick handler to '%s'\n", onclick); + break; + case 'm': /* menu */ + if (debug >= 2) + printf("Building menu from string: \"%s\"\n", param); + if (onmenu != NULL) { + // destroy the previous menu + for (int i = 0; i < menusize; i++) { + free(onmenu[i].name); + free(onmenu[i].action); + onmenu[i].name = NULL; + onmenu[i].action = NULL; + } + free(onmenu); + onmenu = NULL; + gtk_widget_destroy(menu); + menu = NULL; + } + + menusize = 0; + + if (!param) { + break; + } else if (*param == '\0') { + if (debug) + printf("Removing onmenu handler\n"); + free(param); + break; + } + + // This block makes sure that the parameter after 'm' is ready to be + // processed We can't accept 2 straight commas, as it becomes ambiguous + int straight = 0; + int bars = 0; + for (int i = 0; i < len; i++) { + if (param[i] == ',' && param[i - 1] != '\\') { + straight++; + if (straight == 2) { + break; + } + } else if (param[i] == '|' && param[i - 1] != '\\') { + straight = 0; + bars++; + } + } + if (straight == 2) { + printf("Two straight ',' found. Use '\\' to escape\n"); + free(param); + break; + } + // End of block that checks the parameter + + // Create the onmenu array which stores structs with name, action + // properties + menusize = bars + 1; + onmenu = malloc(menusize * sizeof(struct item)); + menu = gtk_menu_new(); + int last = -1; + int item = 0; + char lastFound = '|'; // what was the last delimiter processed + for (int i = 0; i < len; i++) { + if (param[i] == ',' && param[i - 1] != '\\') { + onmenu[item].name = save_word(param, i, last); + last = i; + lastFound = ','; + } else if (param[i] == '|' && param[i - 1] != '\\') { + if (lastFound == ',') { // we have found a ',' so we read an action + onmenu[item].action = save_word(param, i, last); + } else { // this is a label-only entry + onmenu[item].name = save_word(param, i, last); + onmenu[item].action = malloc(1); // pointer has to be freeable + *onmenu[item].action = '\0'; + } + last = i; + lastFound = '|'; + item++; + } + } + if (item < menusize) { // haven't read all actions because last one + // didn't end with a '|' + if (lastFound == ',') { + onmenu[item].action = save_word(param, len, last); + } else { + onmenu[item].name = save_word(param, len, last); + onmenu[item].action = malloc(1); + *onmenu[item].action = '\0'; + } + } + + // Now create the menu item widgets and attach them on the menu + for (int i = 0; i < menusize; i++) { + GtkWidget *w; + if (0 == strlen(onmenu[i].name) || (!strncmp(onmenu[i].name, "-----", 5))) { + w = gtk_separator_menu_item_new() ; + } else { + w = gtk_menu_item_new_with_label(onmenu[i].name); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(click_menu_item), NULL); + } + gtk_menu_shell_append(GTK_MENU_SHELL(menu), w); + } + gtk_widget_show_all(menu); + free(param); + break; + case 'R': /* mouse scroll up */ + if (onscrollup != NULL) { + free(onscrollup); + } + if (!param || (*param == '\0')) { + if (debug) + printf("Removing scrollup command\n"); + onscrollup = NULL; + } else { + if (debug) + printf("Setting scrollup command\n"); + onscrollup = malloc(strlen(param)); + strncpy(onscrollup, param, len + 1); + } + break; + case 'r': /* mouse scroll down */ + if (onscrolldown != NULL) { + free(onscrolldown); + } + if (!param || (*param == '\0')) { + if (debug) + printf("Removing scrolldown command\n"); + onscrolldown = NULL; + } else { + if (debug) + printf("Setting scrolldown command\n"); + onscrolldown = malloc(strlen(param)); + strncpy(onscrolldown, param, len + 1); + } + break; + case 'S': /* mouse middle click */ + if (onmiddleclick != NULL) { + free(onmiddleclick); + } + if (!param || (*param == '\0')) { + if (debug) + printf("Removing middle click command\n"); + onmiddleclick = NULL; + } else { + if (debug) + printf("Setting middleclick command\n"); + onmiddleclick = malloc(strlen(param)); + strncpy(onmiddleclick, param, len + 1); + } + break; + default: + fprintf(stderr, "Unknown command: '%c'\n", *buf); + if (param != NULL) { + free(param); + } + } + + gdk_flush(); + // STOP copy-pasted in from watch_fifo + } + close(client_sockfd); + } + return EXIT_SUCCESS; +} +#endif + gpointer watch_fifo(gpointer argv) { char *buf = malloc(1024 * sizeof(char)); char cmd; @@ -380,14 +824,14 @@ outer: } if (param != NULL && *param == '\0') { - if(debug) + if (debug) printf("Removing onclick handler\n"); free(param); break; } onclick = param; - if(debug) + if (debug) printf("Setting onclick handler to '%s'\n", onclick); break; case 'm': /* menu */ @@ -410,7 +854,7 @@ outer: if (!param) { break; } else if (*param == '\0') { - if(debug) + if (debug) printf("Removing onmenu handler\n"); free(param); break; @@ -495,11 +939,11 @@ outer: free(onscrollup); } if (!param || (*param == '\0')) { - if(debug) + if (debug) printf("Removing scrollup command\n"); onscrollup = NULL; } else { - if(debug) + if (debug) printf("Setting scrollup command\n"); onscrollup = malloc(strlen(param)); strncpy(onscrollup, param, len + 1); @@ -510,11 +954,11 @@ outer: free(onscrolldown); } if (!param || (*param == '\0')) { - if(debug) + if (debug) printf("Removing scrolldown command\n"); onscrolldown = NULL; } else { - if(debug) + if (debug) printf("Setting scrolldown command\n"); onscrolldown = malloc(strlen(param)); strncpy(onscrolldown, param, len + 1); @@ -525,11 +969,11 @@ outer: free(onmiddleclick); } if (!param || (*param == '\0')) { - if(debug) + if (debug) printf("Removing middle click command\n"); onmiddleclick = NULL; } else { - if(debug) + if (debug) printf("Setting middleclick command\n"); onmiddleclick = malloc(strlen(param)); strncpy(onmiddleclick, param, len + 1); @@ -582,34 +1026,16 @@ int print_usage(char **argv) { return 0; } -int fmain(int argc, char **argv) { +int fmain(void* item) { char *start_icon = "none"; char *tooltip = NULL; - char *pipe = NULL; + void *pipe = NULL; GThread *reader; XInitThreads(); /* see http://stackoverflow.com/a/18690540/472927 */ - gtk_init(&argc, &argv); - - if (argc == 1) { - return print_usage(argv); - } - - int c; - while ((c = getopt(argc, argv, "i:t:h")) != -1) - switch (c) { - case 'i': - start_icon = optarg; - break; - case 't': - tooltip = optarg; - break; - case 'h': - return print_usage(argv); - case '?': - fprintf(stderr, "Unknown option: %c\n", optopt); - return 1; - } + //char *argv[] = {}; + char **_argv[] = { NULL }; + gtk_init(0, _argv); icon = create_tray_icon(start_icon); @@ -622,10 +1048,18 @@ int fmain(int argc, char **argv) { * argv array, without parsing them */ /* so if there were only non-positional arguments, all arguments have been * parsed and optind will be equal to argc */ - if (optind < argc) { - pipe = argv[optind]; + //pipe = argv[optind]; +#ifdef ENABLE_SOCKETS + if (use_sockets) { + pipe = (int *) item; + reader = g_thread_new("watch_socket", watch_socket, pipe); + } else { +#endif + pipe = (FILE *) item; reader = g_thread_new("watch_fifo", watch_fifo, pipe); +#ifdef ENABLE_SOCKETS } +#endif gtk_main(); return 0; @@ -658,11 +1092,70 @@ static int handler(void* user, const char* section, const char* name, const char return 1; } +int send_message(char *message, const int sock, FILE *stream, const pid_t child_pid, const char *fifo, const int quit) { + /* smart handler to simplify the Step 6 logic that mostly repeats a bunch of ifdefs */ + // using a local variable makes the rest of this function operate way better! + char sm_message[1000]; + strcpy(sm_message, message); + // if this is a menu with a quit statement at the end (manually passed; not calculated), then + // we need to add the right type of ending based on if it is a socket or fifo + if(quit) { +#ifdef ENABLE_SOCKETS + if (use_sockets) { + strcat(sm_message, "kill "); + itoa(child_pid, child_pid_str); + strcat(sm_message, child_pid_str); + strcpy(child_pid_str,""); // very important because we will use it again + } else { +#endif + // no socket + strcat(sm_message, "echo 'q' > "); + strcat(sm_message, fifo); +#ifdef ENABLE_SOCKETS + } + strcat(sm_message, "\n"); +#endif + } +#ifdef ENABLE_SOCKETS + if (use_sockets) { + send_sock_message(sock, sm_message); + } else { +#endif + send_fifo_message(stream, sm_message); +#ifdef ENABLE_SOCKETS + } +#endif + return 0; +} + +#ifdef ENABLE_SOCKETS +void *send_sock_message(int socket, const char *message) { + signal(SIGPIPE, SIG_IGN); + if (debug >= 2) + printf("Msg: %s", message); + int response; + if ((response=write(socket, message, strlen(message))) == -1) { + printf("socket %d, msg %s\n", socket, message); + perror("cannot send to socket"); //exit(EXIT_FAILURE); + } + if (debug >= 3) + printf("response: %d\n", response); +} + +void *send_sock_icon_message(int socket, const char *iconfile) { + // msg is global buffer for messages to dump into a pipe + strcpy(msg, "i "); + strcat(msg, iconfile); + strcat(msg, "\n"); // very important for mktrayicon + send_sock_message(socket, msg); +} +#endif + void *send_fifo_message(FILE *stream, const char *message) { - if(debug) + if (debug >= 2) printf("Msg: %s", message); int response = fprintf(stream, message); - if(debug) + if (debug >= 3) printf("response: %d\n", response); fflush(stream); } @@ -675,8 +1168,20 @@ void *send_fifo_icon_message(FILE *stream, const char *iconfile) { send_fifo_message(stream, msg); } +void *send_icon_message(const char *iconfile, const int sock, FILE *stream) { +#ifdef ENABLE_SOCKETS + if (use_sockets) { + send_sock_icon_message(sock, iconfile); + } else { +#endif + send_fifo_icon_message(stream, iconfile); +#ifdef ENABLE_SOCKETS + } +#endif +} + void sig_usr(int signo) { - if(signo == SIGUSR1) { + if (signo == SIGUSR1) { printf("an icon has exited!\n"); closed_icons++; }; @@ -684,12 +1189,14 @@ void sig_usr(int signo) { } int fprint_usage(char **argv) { - printf("Usage: %s [-c CONFFILE] [-d] [-h]\n", *argv); - printf("Create a system tray icon as specified\n"); + printf("Usage: %s [-c CONFFILE] [-d] [-h] [-s] [-n]\n", *argv); + printf("Displays Capslock and Numlock status as trayicons\n"); printf("\n"); printf(" -c CONFFILE\tUse the specified config file (required)\n"); printf(" -d \tTurn debug messages on\n"); printf(" -h \tDisplay this help message\n"); + printf(" -s \tUse socket instead of fifo (if compiled)\n"); + printf(" -n \tNo click-to-switch Caps/Num lock\n"); printf("\n"); return 0; } @@ -697,13 +1204,24 @@ int fprint_usage(char **argv) { int main(int argc, char **argv) { // Step 1: parse parameters int c; - while ((c = getopt(argc, argv, "c:dh")) != -1) + while ((c = getopt(argc, argv, "c:dhsn")) != -1) switch (c) { case 'c': conffile = optarg; break; case 'd': - debug = 1; + debug++; + break; + case 's': +#ifdef ENABLE_SOCKETS + use_sockets = 1; +#else + printf("this binary was not compiled with socket support. Aborted.\n"); + exit(1); +#endif + break; + case 'n': + click_to_switch = 0; break; case 'h': return fprint_usage(argv); @@ -711,17 +1229,17 @@ int main(int argc, char **argv) { case '?': fprintf(stderr, "Unknown option: %c\n", optopt); return 1; - }; - // must reset this for the inner getopt/optint operations to proceed. This is somehow a globally-scoped variable. + } + // must reset this for any second getopt/optint operations to proceed. This is somehow a globally-scoped variable. optind = 0; + if (debug) { printf("Debug level: %d\n", debug); }; // Step 2: load config - configuration config; if (ini_parse(conffile, handler, &config) < 0) { printf("Error: cannot load %s\n",conffile); return 1; } - if(debug) { + if (debug) { printf("Config loaded from %s.\n", conffile); printf("caps_on=%s\n", config.caps_on); printf("caps_off=%s\n", config.caps_off); @@ -736,69 +1254,143 @@ int main(int argc, char **argv) { // hardcoded min/max for microseconds, 0 and 10 seconds if (config.sleep_microseconds < 0 || config.sleep_microseconds >= 10000000) { printf("Warning: invalid sleep_microseconds: %d\n", config.sleep_microseconds); - }; + } // Step 4: initialize variables FILE *stream_N, *stream_C; dpy = XOpenDisplay( NULL ); int status_capslock = get_indicator(dpy, "Caps Lock"); int status_numlock = get_indicator(dpy, "Num Lock"); - char *_argv_C[] = { "fmain", (char*)config.caps_fifo, NULL }; - char *_argv_N[] = { "fmain", (char*)config.num_fifo, NULL }; - int count_C = 0; - int count_N = 0; - while (_argv_C[++count_C]); - while (_argv_N[++count_N]); - int fd_C, fd_N; - struct stat st_C, st_N; - pid_t pid, pid_C, pid_N; - printf("Capslock: %d\tNumlock: %d\n",status_capslock,status_numlock); - - // Step 5: initiate fifo files - if (stat(config.caps_fifo, &st_C) != 0) - mkfifo(config.caps_fifo, 0666); - if (stat(config.num_fifo, &st_N) != 0) - mkfifo(config.num_fifo, 0666); - - // Step 6: start child proceses (tray icons) - // parent 1 + //printf("Capslock: %d\tNumlock: %d\n",status_capslock,status_numlock); + +#ifdef ENABLE_SOCKETS + if (!use_sockets) { +#endif + if (stat(config.caps_fifo, &st_C) != 0) + mkfifo(config.caps_fifo, 0666); + if (stat(config.num_fifo, &st_N) != 0) + mkfifo(config.num_fifo, 0666); +#ifdef ENABLE_SOCKETS + } +#endif + + // Step 5: start child proceses (tray icons) + // parent1 pid = getpid(); if ((pid_C = fork()) == 0) { // parent1->child 1 - fmain(count_C,_argv_C); + strcpy(msg, config.caps_fifo); + struct sigaction san_C; + memset(&san_C,0,sizeof(san_C)); + san_C.sa_handler = sigfun_child; + sigaction(SIGTERM,&san_C,NULL); + fmain(msg); kill(pid, SIGUSR1); + printf("pid_C exiting...\n"); _exit(0); } else { - // parent 1 + // parent1 if ((pid_N = fork()) == 0) { // parent1->child2 - fmain(count_N,_argv_N); + strcpy(msg, config.num_fifo); + struct sigaction san_N; + memset(&san_N,0,sizeof(san_N)); + san_N.sa_handler = sigfun_child; + sigaction(SIGTERM,&san_N,NULL); + fmain(msg); kill(pid, SIGUSR1); + printf("pid_N exiting...\n"); _exit(0); } else { - // parent 1 - /* prevents breakage when C or N tray icon is closed. - However, now this daemon will never quit without the closed_icon counter! - */ - fd_C = open(config.caps_fifo, O_RDWR); - stream_C = fdopen(fd_C,"w"); - fd_N = open(config.num_fifo, O_RDWR); - stream_N = fdopen(fd_N,"w"); - strcpy(msg, "m quit,echo 'q' > "); - strcat(msg, config.caps_fifo); - strcat(msg, "\n"); - send_fifo_message(stream_C, msg); - strcpy(msg, "m say hello,yad 'hello'|quit,echo 'q' > "); - strcat(msg, config.num_fifo); - strcat(msg, "\n"); - send_fifo_message(stream_N, msg); - // still parent 1 + // parent1 + // catch CTRL+C, useful only for fifo + +#ifdef ENABLE_SOCKETS + if (use_sockets) { } else { +#endif + struct sigaction san_main; + memset(&san_main,0,sizeof(san_main)); + san_main.sa_handler = sigfun_main; + sigaction(SIGINT,&san_main,NULL); +#ifdef ENABLE_SOCKETS + } +#endif + // Step 6 prepare icons at beginning + int sockfd_C, sockfd_N; + ssize_t nbytes_read, i, user_input_len; + usleep(500000); // wait 0.5 seconds to let server open the socket + /* Get socket. */ +#ifdef ENABLE_SOCKETS + if (use_sockets) { + struct sockaddr_un sockaddr_C, sockaddr_N; + sockfd_C = socket(AF_LOCAL, SOCK_STREAM, 0); + sockfd_N = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd_C == -1) { perror("client C socket"); exit(EXIT_FAILURE); } + if (sockfd_N == -1) { perror("client N socket"); exit(EXIT_FAILURE); } + /* Prepare sockaddr_C. */ + sockaddr_C.sun_family = AF_LOCAL; + strcpy(sockaddr_C.sun_path,config.caps_fifo); + sockaddr_N.sun_family = AF_LOCAL; + strcpy(sockaddr_N.sun_path,config.num_fifo); + /* Do the actual connection. */ + if (connect(sockfd_C, (struct sockaddr*)&sockaddr_C, sizeof(sockaddr_C)) == -1) { + unlink(config.caps_fifo); + printf("File: %s\n", sockaddr_C.sun_path); perror("client C connect"); return EXIT_FAILURE; + } + unlink(config.caps_fifo); // suggested to do after bind, but the client needs to delete the file after it connects. + if (connect(sockfd_N, (struct sockaddr*)&sockaddr_N, sizeof(sockaddr_N)) == -1) { + unlink(config.num_fifo); + printf("File: %s\n", sockaddr_N.sun_path); perror("client N connect"); return EXIT_FAILURE; + } + unlink(config.num_fifo); + } else { +#endif + // no sockets + fd_C = open(config.caps_fifo, O_RDWR); + stream_C = fdopen(fd_C,"w"); + fd_N = open(config.num_fifo, O_RDWR); + stream_N = fdopen(fd_N,"w"); +#ifdef ENABLE_SOCKETS + } +#endif + signal(SIGPIPE, SIG_IGN); + // Prepare menus and initial icons + usleep(5000); + send_message("m quit,", sockfd_C, stream_C, pid_C, config.caps_fifo, 1); + usleep(5000); + if (click_to_switch) { +#ifdef ENABLE_LIBXDO + send_message("c CAPS\n", sockfd_C, stream_C, 0, "", 0); + send_message("c NUM\n", sockfd_N, stream_N, 0, "", 0); +#else + send_message("c xdotool key Caps_Lock\n", sockfd_C, stream_C, -1, "", 0); + send_message("c xdotool key Num_Lock\n", sockfd_N, stream_N, -1, "", 0); +#endif + usleep(5000); + } + send_message("m say hello,yad 'hello'|quit,", sockfd_N, stream_N, pid_N, config.num_fifo, 1); + if (status_capslock) { + send_icon_message(config.caps_on, sockfd_C, stream_C); + } else { + send_icon_message(config.caps_off, sockfd_C, stream_C); + } + if (status_numlock) { + send_icon_message(config.num_on, sockfd_N, stream_N); + } else { + send_icon_message(config.num_off, sockfd_N, stream_N); + } + // still parent1 // Step 7: loop monitoring logic int status_caps_old = -1; int status_num_old = -1; - int kill_result = -1; + //int kill_result = -1; // disabled below + // call handler that counts how many child processes have quit + struct sigaction san; + memset(&san,0,sizeof(san)); + san.sa_handler = sig_usr; + sigaction(SIGUSR1,&san,NULL); while (closed_icons < 2) { // (1000000); 1 second // ( 1000); 1 millisecond @@ -807,40 +1399,53 @@ int main(int argc, char **argv) { status_capslock = get_indicator(dpy, "Caps Lock"); status_numlock = get_indicator(dpy, "Num Lock"); //printf("Capslock: %d\tNumlock: %d\n",status_capslock,status_numlock); - struct sigaction san; - memset(&san,0,sizeof(san)); - san.sa_handler = sig_usr; - sigaction(SIGUSR1,&san,NULL); - if(status_capslock != status_caps_old) { - kill_result = kill(pid_C, 0); + if (status_capslock != status_caps_old) { // unfortunately, kill does not operate as expected. I expected kill_result <> 0 if the pid is already gone. - //printf("Checking for capslock change, pid %d, result %d\n", pid_C, kill_result); - if(0 == status_capslock) { - send_fifo_icon_message(stream_C, config.caps_off); + //kill_result = kill(pid_C, 0); + if (status_capslock) { + send_icon_message(config.caps_on, sockfd_C, stream_C); + usleep(5000); + send_message("t capslock on\n", sockfd_C, stream_C, 0, "", 0); + printf("capslock on\n"); + } else { + send_icon_message(config.caps_off, sockfd_C, stream_C); + usleep(5000); + send_message("t capslock off\n", sockfd_C, stream_C, 0, "", 0); printf("capslock off\n"); } - else { - send_fifo_icon_message(stream_C, config.caps_on); - printf("capslock on\n"); - }; status_caps_old = status_capslock; }; - if(status_numlock != status_num_old) { - if(0 == status_numlock) { - send_fifo_icon_message(stream_N, config.num_off); + if (status_numlock != status_num_old) { + if (status_numlock) { + send_icon_message(config.num_on, sockfd_N, stream_N); + usleep(5000); + send_message("t numlock on\n", sockfd_N, stream_N, 0, "", 0); + printf("numlock on\n"); + } else { + send_icon_message(config.num_off, sockfd_N, stream_N); + usleep(5000); + send_message("t numlock off\n", sockfd_N, stream_N, 0, "", 0); printf("numlock off\n"); } - else { - send_fifo_icon_message(stream_N, config.num_on); - printf("numlock on\n"); - }; status_num_old = status_numlock; }; - }; // end while(1) + }; // end main while loop // Either CTRL+C was run twice, or both icons closed + // just in case + kill(pid_C,SIGTERM); + kill(pid_N,SIGTERM); +#ifdef ENABLE_SOCKETS + if (use_sockets) { } else { +#endif + // important to delete these ONLY if they are fifos + unlink(config.caps_fifo); + unlink(config.num_fifo); +#ifdef ENABLE_SOCKETS + } +#endif return 0; - }; // end parent 1 loop + }; // end parent1 loop return 0; - }; // end parent 1 loop + }; // end parent1 loop return 0; } diff --git a/experimental/keyboard-leds-trayicons.h b/experimental/keyboard-leds-trayicons.h new file mode 100644 index 0000000..bafb06f --- /dev/null +++ b/experimental/keyboard-leds-trayicons.h @@ -0,0 +1,49 @@ +// startdate: 2022-10-07-6 20:45 +// goal: header for KLT + +// protection header +#ifndef HEADER_KLT +#define HEADER_LKT 20221007204837 + +#include <ctype.h> +#include <fcntl.h> +#include <glib.h> +#include <gtk/gtk.h> +#include <ini.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <X11/XKBlib.h> +#include <X11/Xlib.h> +#include <signal.h> +#ifdef ENABLE_SOCKETS +#include <sys/un.h> +#include <netinet/in.h> +#include <sys/socket.h> +#endif +#ifdef ENABLE_LIBXDO +#include <xdo.h> +#endif + +void *send_fifo_message(FILE *stream, const char *message); +void *send_fifo_icon_message(FILE *stream, const char *iconfile); +void sigfun_main(int sig); +void sigfun_child(int sig); +void klt_reverse(char s[]); +void itoa(int n, char s[]); +int get_indicator(Display* dpy, char* indicator); +static int handler(void* user, const char* section, const char* name, const char* value); +int send_message(char *message, const int sock, FILE *stream, const pid_t child_pid, const char *fifo, const int quit); +void *send_fifo_message(FILE *stream, const char *message); +void *send_fifo_icon_message(FILE *stream, const char *iconfile); +void sig_usr(int signo); +#ifdef ENABLE_SOCKETS +void remove_first_newline(char *string); +void *send_sock_message(int socket, const char *message); +void *send_sock_icon_message(int socket, const char *iconfile); +#endif + +// protection header +#endif |