aboutsummaryrefslogtreecommitdiff
path: root/experimental/keyboard-leds-trayicons.c
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2022-10-08 16:34:20 -0400
committerB. Stack <bgstack15@gmail.com>2022-10-09 16:45:23 -0400
commitdcdbbc74d4ea4340b18b77369c7512bb167b43f1 (patch)
treee7b52877678abcc81243f0b016d2cacad33af2e8 /experimental/keyboard-leds-trayicons.c
parentchange #ifdef DEBUG to if(debug) to use -d (diff)
downloadkeyboard-leds-trayicons-dcdbbc74d4ea4340b18b77369c7512bb167b43f1.tar.gz
keyboard-leds-trayicons-dcdbbc74d4ea4340b18b77369c7512bb167b43f1.tar.bz2
keyboard-leds-trayicons-dcdbbc74d4ea4340b18b77369c7512bb167b43f1.zip
almost-production-worthy KLT.cexperimental
added sockets, fifos, click-to-switch, libxdo support
Diffstat (limited to 'experimental/keyboard-leds-trayicons.c')
-rw-r--r--experimental/keyboard-leds-trayicons.c881
1 files changed, 743 insertions, 138 deletions
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;
}
bgstack15