aboutsummaryrefslogtreecommitdiff
path: root/experimental/keyboard-leds-trayicons.c
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2022-09-30 16:38:40 -0400
committerB. Stack <bgstack15@gmail.com>2022-09-30 16:40:46 -0400
commit29fa73d2f5013c9502934bc8252014a211d7f7a8 (patch)
tree33a0854ed05537ca4515f784f84889c5e0ee77b6 /experimental/keyboard-leds-trayicons.c
parentWIP: minimum viable product for status.c (diff)
downloadkeyboard-leds-trayicons-29fa73d2f5013c9502934bc8252014a211d7f7a8.tar.gz
keyboard-leds-trayicons-29fa73d2f5013c9502934bc8252014a211d7f7a8.tar.bz2
keyboard-leds-trayicons-29fa73d2f5013c9502934bc8252014a211d7f7a8.zip
added major C improvements
* use libinih to read config file * handle SIGPIPE gracefully (when a trayicon exits) * exit when both trayicons have exited I wanted to try using libini-config5 but it was not documented at all, and I need useful examples.
Diffstat (limited to 'experimental/keyboard-leds-trayicons.c')
-rw-r--r--experimental/keyboard-leds-trayicons.c833
1 files changed, 833 insertions, 0 deletions
diff --git a/experimental/keyboard-leds-trayicons.c b/experimental/keyboard-leds-trayicons.c
new file mode 100644
index 0000000..f2c6a4a
--- /dev/null
+++ b/experimental/keyboard-leds-trayicons.c
@@ -0,0 +1,833 @@
+/*
+ * File: keyboard-leds-trayicons.c
+ * Location: https://bgstack15.ddns.net/cgit/keyboard-leds-trayicons/
+ * Author: bgstack15
+ * 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
+ * History:
+ * Usage:
+ * References:
+ * https://github.com/Cairo-Dock/cairo-dock-plug-ins/blob/master/keyboard-indicator/src/applet-xklavier.c#L124
+ * https://github.com/oco2000/xfce4-kbdleds-plugin/blob/fe753d9d0f8a720a35a32f5f556b8fbead798d20/panel-plugin/xkbleds.c
+ * https://stackoverflow.com/questions/60897833/programmatically-calling-mainint-argc-char-argv-in-c
+ * https://stackoverflow.com/questions/18649547/how-to-find-the-length-of-argv-in-c
+ * https://www.geeksforgeeks.org/fork-system-call/
+ * https://stackoverflow.com/questions/43326857/multiple-fork-in-c-program
+ * https://stackoverflow.com/questions/14173268/fifo-files-and-read-write
+ * https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_chapter/libc_15.html
+ * https://qnaplus.com/c-program-to-sleep-in-milliseconds/
+ * https://stackoverflow.com/questions/47107311/sending-signal-from-child-to-parent
+ * https://stackoverflow.com/questions/108183/how-to-prevent-sigpipes-or-handle-them-properly
+ * Improvements:
+ * Use getopts, -h, -c conffile, -d (debug)
+ * fix all return values at end
+ * Dependencies: libx11-dev, libinih-dev, libgtk-3-dev
+ * Documentation:
+ * features include:
+ * operate when only one trayicon has exited
+ * exit when both trayicons have exited
+ */
+
+#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>
+
+/* START inclusion of mktrayicon.c, 536 lines */
+/*
+ * This function is made because it's needed to escape the '\' character
+ * The basic code has been taken from the man pages of the strncpy function
+ */
+char *strncpy_esc(char *dest, const char *src, size_t n) {
+ size_t i = 0;
+ size_t index = 0;
+ while (i < n && src[i] != '\0') {
+ if (src[i] == '\\' && src[i + 1] != '\0') {
+ dest[index] = src[i + 1];
+ index++;
+ i += 2;
+ } else {
+ dest[index] = src[i];
+ index++;
+ i++;
+ }
+ }
+ for (; index < n; index++) {
+ dest[index] = '\0';
+ }
+ return dest;
+}
+
+char *save_word(char *src, int i_del, int last) {
+ char *dest = malloc((i_del - last) * sizeof(char));
+ strncpy_esc(dest, src + last + 1, i_del - last - 1);
+ dest[i_del - last - 1] = '\0';
+ return dest;
+}
+
+/*
+ * Struct that stores the label names on the menu and
+ * their corresponding actions when the user selects them
+ */
+struct item {
+ char *name;
+ char *action;
+} * onmenu;
+char *onscrollup = NULL;
+char *onscrolldown = NULL;
+char *onmiddleclick = NULL;
+int menusize = 0; // number of menu entries
+GtkWidget *menu = NULL;
+
+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);
+ }
+}
+
+void tray_icon_on_middleclick(GtkStatusIcon *status_icon, GdkEventButton *event,
+ gpointer user_data) {
+ if (2 == event->button) {
+ if (onmiddleclick == NULL) {
+#ifdef DEBUG
+ printf("middleclick, but no command specified\n");
+#endif
+ } else {
+#ifdef DEBUG
+ printf("middleclick\n");
+#endif
+ if (onmiddleclick != NULL && fork() == 0) {
+ execl("/bin/sh", "sh", "-c", onmiddleclick, (char *)NULL);
+ }
+ }
+ }
+}
+
+/*
+ * Callback function for when an entry is selected from the menu
+ * We loop over all entry names to find what action to execute
+ */
+void click_menu_item(GtkMenuItem *menuitem, gpointer user_data) {
+ const char *label = gtk_menu_item_get_label(menuitem);
+ for (int i = 0; i < menusize; i++) {
+ if (strcmp(label, onmenu[i].name) == 0 && fork() == 0) {
+ execl("/bin/sh", "sh", "-c", onmenu[i].action, (char *)NULL);
+ }
+ }
+}
+
+void tray_icon_on_menu(GtkStatusIcon *status_icon, guint button,
+ guint activate_time, gpointer user_data) {
+#ifdef DEBUG
+ printf("Popup menu\n");
+#endif
+ if (menusize) {
+ gtk_menu_popup_at_pointer((GtkMenu *)menu, NULL);
+ }
+}
+
+void tray_icon_on_scroll(GtkStatusIcon *status_icon, GdkEventScroll *event,
+ gpointer user_data) {
+ char *i = NULL;
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ i = "up";
+ if (onscrollup != NULL && fork() == 0)
+ execl("/bin/sh", "sh", "-c", onscrollup, (char *)NULL);
+ break;
+ case GDK_SCROLL_DOWN:
+ i = "down";
+ if (onscrolldown != NULL && fork() == 0)
+ execl("/bin/sh", "sh", "-c", onscrolldown, (char *)NULL);
+ break;
+ }
+ if (i != NULL) {
+#ifdef DEBUG
+ printf("scroll %s\n",i);
+#endif
+ }
+}
+
+gboolean set_tooltip(gpointer data) {
+ char *p = (char *)data;
+ if (*p == '\0') {
+#ifdef DEBUG
+ printf("Removing tooltip\n");
+#endif
+ gtk_status_icon_set_has_tooltip(icon, FALSE);
+ return FALSE;
+ }
+
+#ifdef DEBUG
+ printf("Setting tooltip to '%s'\n", p);
+#endif
+ gtk_status_icon_set_tooltip_text(icon, p);
+ free(data);
+ return FALSE;
+}
+
+gboolean set_icon(gpointer data) {
+ char *p = (char *)data;
+#ifdef DEBUG
+ printf("Setting icon to '%s'\n", p);
+#endif
+ if (strchr(p, '/')) {
+ gtk_status_icon_set_from_file(icon, p);
+ } else {
+ gtk_status_icon_set_from_icon_name(icon, p);
+ }
+ free(data);
+ return FALSE;
+}
+
+gboolean set_visible(gpointer data) {
+ gtk_status_icon_set_visible(icon, data == 0 ? FALSE : TRUE);
+ return FALSE;
+}
+
+gboolean do_quit(gpointer data) {
+ gtk_main_quit();
+ return FALSE;
+}
+
+gpointer watch_fifo(gpointer argv) {
+ char *buf = malloc(1024 * sizeof(char));
+ char cmd;
+ char quote;
+ char *param;
+ char *tmp = malloc(1024 * sizeof(char));
+ char *read;
+ size_t len, i;
+ char *fifo_path = (char *)argv;
+ FILE *fifo;
+ struct stat fifo_st;
+
+/* outer is for open */
+outer:
+ while (1) {
+ if (stat(fifo_path, &fifo_st) != 0) {
+ perror("FIFO does not exist, exiting\n");
+ gdk_threads_add_idle(do_quit, fifo);
+ return NULL;
+ }
+
+ fifo = fopen(fifo_path, "r");
+
+ if (fifo == NULL) {
+ perror("FIFO went away, exiting\n");
+ gdk_threads_add_idle(do_quit, fifo);
+ return NULL;
+ }
+
+ /* inner is for read */
+ while (1) {
+ read = fgets(buf, 1024 * sizeof(char), fifo);
+
+ if (read == NULL) {
+ /* no more data in pipe, reopen and block */
+ fclose(fifo);
+ goto outer;
+ }
+
+ /* trim string */
+ while ((*read == '\n' || *read == ' ' || *read == '\t') &&
+ *read != '\0') {
+ read++;
+ }
+
+ if (*read == '\0') {
+ /* empty command */
+ continue;
+ }
+
+ cmd = *read;
+ len = strlen(read);
+ if (len < 3) {
+ param = NULL;
+ } else if (*(read + 2) != '\'' && *(read + 2) != '"') {
+ // unquoted string
+ read += 2;
+ len -= 2;
+ // trim trailing whitespace
+ i = len - 1;
+ while (i > 0) {
+ if (!isspace(read[i])) {
+ len = i + 1;
+ read[len] = '\0';
+ break;
+ }
+ i -= 1;
+ }
+ param = malloc((len + 1) * sizeof(char));
+ strncpy(param, read, len + 1);
+ } else {
+ // quoted string
+ quote = *(read + 2);
+ read += 3;
+ len -= 3;
+ *tmp = '\0';
+ *(tmp + 1024 - 1) = '\0';
+ // keep track of what we have so far
+ strncpy(tmp, read, 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;
+ }
+
+ // we don't have the end of the string yet
+ read = fgets(buf, 1024 * sizeof(char), fifo);
+ 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, read, 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);
+ }
+
+ 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') {
+#ifdef DEBUG
+ printf("Removing onclick handler\n");
+#endif
+ free(param);
+ break;
+ }
+
+ onclick = param;
+#ifdef DEBUG
+ printf("Setting onclick handler to '%s'\n", onclick);
+#endif
+ break;
+ case 'm': /* menu */
+ 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') {
+#ifdef DEBUG
+ printf("Removing onmenu handler\n");
+#endif
+ 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')) {
+#ifdef DEBUG
+ printf("Removing scrollup command\n");
+ onscrollup = NULL;
+#endif
+ } else {
+#ifdef DEBUG
+ printf("Setting scrollup command\n");
+#endif
+ onscrollup = malloc(strlen(param));
+ strncpy(onscrollup, param, len + 1);
+ }
+ break;
+ case 'r': /* mouse scroll down */
+ if (onscrolldown != NULL) {
+ free(onscrolldown);
+ }
+ if (!param || (*param == '\0')) {
+#ifdef DEBUG
+ printf("Removing scrolldown command\n");
+ onscrolldown = NULL;
+#endif
+ } else {
+#ifdef DEBUG
+ printf("Setting scrolldown command\n");
+#endif
+ onscrolldown = malloc(strlen(param));
+ strncpy(onscrolldown, param, len + 1);
+ }
+ break;
+ case 'S': /* mouse middle click */
+ if (onmiddleclick != NULL) {
+ free(onmiddleclick);
+ }
+ if (!param || (*param == '\0')) {
+#ifdef DEBUG
+ printf("Removing middle click command\n");
+ onmiddleclick = NULL;
+#endif
+ } else {
+#ifdef DEBUG
+ printf("Setting middleclick command\n");
+#endif
+ 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();
+ }
+ }
+ return NULL;
+}
+
+static GtkStatusIcon *create_tray_icon(char *start_icon) {
+ GtkStatusIcon *tray_icon;
+
+ if (strchr(start_icon, '/')) {
+ tray_icon = gtk_status_icon_new_from_file(start_icon);
+ } else {
+ tray_icon = gtk_status_icon_new_from_icon_name(start_icon);
+ }
+ g_signal_connect(G_OBJECT(tray_icon), "activate",
+ G_CALLBACK(tray_icon_on_click), NULL);
+ g_signal_connect(G_OBJECT(tray_icon), "popup-menu",
+ G_CALLBACK(tray_icon_on_menu), NULL);
+ g_signal_connect(G_OBJECT(tray_icon), "scroll-event",
+ G_CALLBACK(tray_icon_on_scroll), NULL);
+ g_signal_connect(G_OBJECT(tray_icon), "button-release-event",
+ G_CALLBACK(tray_icon_on_middleclick), NULL);
+ gtk_status_icon_set_visible(tray_icon, TRUE);
+
+ return tray_icon;
+}
+
+int print_usage(char **argv) {
+ printf("Usage: %s [-i ICON] [-t TOOLTIP] [-h] [FIFO]\n", *argv);
+ printf("Create a system tray icon as specified\n");
+ printf("\n");
+ printf(" -i ICON\tUse the specified ICON when initializing\n");
+ printf(" -t TOOLTIP\tUse the specified TOOLTIP when initializing\n");
+ printf(" -h \t\tDisplay this help message\n");
+ printf("\n");
+ printf("If a FIFO is not provided, mktrayicon will run until killed\n");
+ printf("Report bugs at https://github.com/jonhoo/mktrayicon\n");
+ return 0;
+}
+
+int fmain(int argc, char **argv) {
+ char *start_icon = "none";
+ char *tooltip = NULL;
+ char *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;
+ }
+
+ icon = create_tray_icon(start_icon);
+
+ if (tooltip) {
+ gtk_status_icon_set_tooltip_text(icon, tooltip);
+ }
+
+ /* optind holds the index of the next argument to be parsed */
+ /* getopt moved positional arguments (if there were any) to the end of the
+ * 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];
+ reader = g_thread_new("watch_fifo", watch_fifo, pipe);
+ }
+
+ gtk_main();
+ return 0;
+}
+/* STOP inclusion of mktrayicon.c */
+
+Display* dpy;
+
+int get_indicator(Display* dpy, char* indicator) {
+ // where indicator is one of ["Num Lock", "Caps Lock"]
+ Atom lockIndicator = XInternAtom(dpy, indicator, False);
+ int st;
+ XkbGetNamedIndicator(dpy, lockIndicator, NULL, &st, NULL, NULL);
+ return st;
+}
+
+typedef struct {
+ const char* caps_on;
+ const char* caps_off;
+ const char* num_on;
+ const char* num_off;
+ int sleep_microseconds;
+ const char* caps_fifo;
+ const char* num_fifo;
+} configuration;
+
+// this should be configurable with -c parameter (getopts?)
+char* conffile = "/home/bgstack15/keyboard-leds-trayicons.conf";
+
+// slightly modified from example https://github.com/benhoyt/inih
+static int handler(void* user, const char* section, const char* name, const char* value) {
+ // KLT does not need section information at all.
+ configuration *pconfig = (configuration*)user;
+ #define MATCH(n, m, func) if (strcmp(name, n) == 0) { pconfig->m = func(value); }
+ MATCH("KLT_CAPS_ON_ICON", caps_on, strdup)
+ else MATCH("KLT_CAPS_OFF_ICON", caps_off, strdup)
+ else MATCH("KLT_NUM_ON_ICON", num_on, strdup)
+ else MATCH("KLT_NUM_OFF_ICON", num_off, strdup)
+ else MATCH("KLT_SLEEP_MICROSECONDS", sleep_microseconds, atoi)
+ else MATCH("KLT_CAPS_FIFO", caps_fifo, strdup)
+ else MATCH("KLT_NUM_FIFO", num_fifo, strdup)
+ else {
+ return 0; /* unknown section/name, error */
+ }
+ return 1;
+}
+
+char msg[1000];
+
+void *send_fifo_message(FILE *stream, const char *message) {
+#ifdef DEBUG
+ printf("Msg: %s", message);
+#endif
+ int response = fprintf(stream, message);
+#ifdef DEBUG
+ printf("response: %d\n", response);
+#endif
+ fflush(stream);
+}
+
+void *send_fifo_icon_message(FILE *stream, 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_fifo_message(stream, msg);
+}
+
+int closed_icons = 0;
+void sig_usr(int signo) {
+ if(signo == SIGUSR1) {
+ printf("an icon has exited!\n");
+ closed_icons++;
+ };
+ return;
+}
+
+int main(int argc, char **argv) {
+
+ // Step 1: load config
+ configuration config;
+ if (ini_parse(conffile, handler, &config) < 0) {
+ printf("Error: cannot load %s\n",conffile);
+ return 1;
+ }
+ #ifdef DEBUG
+ printf("Config loaded from %s.\n", conffile);
+ printf("caps_on=%s\n", config.caps_on);
+ printf("caps_off=%s\n", config.caps_off);
+ printf("num_on=%s\n", config.num_on);
+ printf("num_off=%s\n", config.num_off);
+ printf("sleep_us=%d\n", config.sleep_microseconds);
+ printf("caps_fifo=%s\n", config.caps_fifo);
+ printf("num_fifo=%s\n", config.num_fifo);
+ #endif
+
+ // Step 2: validate config
+ // 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 3: 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 4: 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 5: start child proceses (tray icons)
+ // parent 1
+ pid = getpid();
+ if ((pid_C = fork()) == 0) {
+ // parent1->child 1
+ fmain(count_C,_argv_C);
+ kill(pid, SIGUSR1);
+ _exit(0);
+ }
+ else {
+ // parent 1
+ if ((pid_N = fork()) == 0) {
+ // parent1->child2
+ fmain(count_N,_argv_N);
+ kill(pid, SIGUSR1);
+ _exit(0);
+ }
+ else {
+ // parent 1
+ signal(SIGPIPE, SIG_IGN);
+ /* 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_WRONLY);
+ stream_C = fdopen(fd_C,"w");
+ fd_N = open(config.num_fifo, O_WRONLY);
+ 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
+ // Step 6: loop monitoring logic
+ int status_caps_old = -1;
+ int status_num_old = -1;
+ int kill_result = 500;
+ while (closed_icons < 2) {
+ // (1000000); 1 second
+ // ( 1000); 1 millisecond
+ // ( 100000); 0.1 seconds is a good number
+ usleep(config.sleep_microseconds);
+ status_capslock = get_indicator(dpy, "Caps Lock");
+ status_numlock = get_indicator(dpy, "Num Lock");
+ //printf("Capslock: %d\tNumlock: %d\n",status_capslock,status_numlock);
+ if(signal(SIGUSR1,sig_usr) == SIG_ERR)
+ printf("Signal processed.");
+ if(status_capslock != status_caps_old) {
+ kill_result = kill(pid_C, 0);
+ 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);
+ 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);
+ 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)
+ // Either CTRL+C was run twice, or both icons closed
+ return 15;
+ }; // end parent 1 loop
+ return 26;
+ }; // end parent 1 loop
+ return 37;
+}
bgstack15