diff options
author | B. Stack <bgstack15@gmail.com> | 2022-09-30 16:38:40 -0400 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2022-09-30 16:40:46 -0400 |
commit | 29fa73d2f5013c9502934bc8252014a211d7f7a8 (patch) | |
tree | 33a0854ed05537ca4515f784f84889c5e0ee77b6 /experimental/status.c | |
parent | WIP: minimum viable product for status.c (diff) | |
download | keyboard-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/status.c')
-rw-r--r-- | experimental/status.c | 751 |
1 files changed, 0 insertions, 751 deletions
diff --git a/experimental/status.c b/experimental/status.c deleted file mode 100644 index f968af1..0000000 --- a/experimental/status.c +++ /dev/null @@ -1,751 +0,0 @@ -/* - * File: status.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: - * minimum viable product - * 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 - * Improvements: - * Write main keyboard-led-trayicons logic, which includes parsing config file (libconfig?) - * Write pipe connectivity to mktrayicon or integrate it entirely - * https://qnaplus.com/c-program-to-sleep-in-milliseconds/ - * Dependencies: libx11-dev - */ -#include<ctype.h> -#include<glib.h> -#include<gtk/gtk.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<fcntl.h> - -/* - * 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; -} - -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; -} - -int main(int argc, char **argv) { - FILE *stream; - dpy = XOpenDisplay( NULL ); - int status_capslock = get_indicator(dpy, "Caps Lock"); - int status_numlock = get_indicator(dpy, "Num Lock"); - printf("Capslock: %d\tNumlock: %d\n",status_capslock,status_numlock); - char *fifo_name_C = "/tmp/fifo1"; - char *fifo_name_N = "/tmp/fifo2"; - printf("done\n"); - char *_argv_C[] = { "fmain", fifo_name_C, NULL }; - char *_argv_N[] = { "fmain", fifo_name_N, NULL }; - int count_C = 0; - int count_N = 0; - int fd_C, fd_N; - struct stat st_C, st_N; - char *msg; - if (stat(fifo_name_C, &st_C) != 0) - mkfifo(fifo_name_C, 0666); - if (stat(fifo_name_N, &st_N) != 0) - mkfifo(fifo_name_N, 0666); - while (_argv_C[++count_C]); - while (_argv_N[++count_N]); - //fd_C = open(fifo_name_C, O_WRONLY); - if (fork() == 0) { - // parent1->child 1 - if (fork() == 0) { - // parent1->child1->child1 - fmain(count_C,_argv_C); - } - else { - // parent1->child1 - fd_C = open(fifo_name_C, O_WRONLY); - stream = fdopen(fd_C,"w"); - msg = "m quit,echo 'q' > /tmp/fifo1\n"; - printf("sending to fd_C %d message %s",fd_C,msg); - fprintf(stream,msg); - if (status_capslock) - msg = "i /home/bgstack15/dev/keyboard-leds-trayicons/src/usr/share/icons/hicolor/scalable/status/capslock-on.svg\n"; - else - msg = "i /home/bgstack15/dev/keyboard-leds-trayicons/src/usr/share/icons/hicolor/scalable/status/capslock-off.svg\n"; - printf("sending to fd_C %d message %s",fd_C,msg); - fprintf(stream,msg); - fclose(stream); - }; - } - else { - // parent 1 - if (fork() == 0) { - // parent1->child2 - fmain(count_N,_argv_N); - } - else { - // parent 1 - fd_N = open(fifo_name_N, O_WRONLY); - stream = fdopen(fd_N,"w"); - msg = "i battery\n"; - printf("sending to fd_N %d message %s",fd_N,msg); - fprintf(stream,msg); - msg = "m say hello,yad 'hello'|quit,echo 'q' > /tmp/fifo2\n"; - printf("sending to fd_N %d message %s",fd_N,msg); - fprintf(stream,msg); - fclose(stream); - }; - // still parent 1 - // main logic loop happens now - //while (access("/tmp/stop-keyboard-leds",F_OK) != 0) { - fd_C = open(fifo_name_C, O_WRONLY); - fd_N = open(fifo_name_N, O_WRONLY); - int status_caps_old = status_capslock; - int status_num_old = status_numlock; - while (1) { - //ueep(1000000); 1 second - //ueep( 1000); 1 millisecond - usleep( 100000); // WORKHERE: should be configurable - 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(status_capslock != status_caps_old) { - stream = fdopen(fd_C,"w"); - if (stream > 0) { - if(0 == status_capslock) { - msg = "i /home/bgstack15/dev/keyboard-leds-trayicons/src/usr/share/icons/hicolor/scalable/status/capslock-off.svg\n"; - //printf("sending to fd_C %d message %s",fd_C,msg); - printf("capslock off\n"); - fprintf(stream,msg); - } - else { - msg = "i /home/bgstack15/dev/keyboard-leds-trayicons/src/usr/share/icons/hicolor/scalable/status/capslock-on.svg\n"; - printf("capslock on\n"); - fprintf(stream,msg); - }; - status_caps_old = status_capslock; - fflush(stream); - //fclose(stream); - }; - }; - if(status_numlock != status_num_old) { - stream = fdopen(fd_N,"w"); - if (stream > 0) { - if(0 == status_numlock) { - msg = "i /home/bgstack15/dev/keyboard-leds-trayicons/src/usr/share/icons/hicolor/scalable/status/numlock-off.svg\n"; - //printf("sending to fd_N %d message %s",fd_N,msg); - printf("numlock off\n"); - fprintf(stream,msg); - } - else { - msg = "i /home/bgstack15/dev/keyboard-leds-trayicons/src/usr/share/icons/hicolor/scalable/status/numlock-on.svg\n"; - printf("numlock on\n"); - fprintf(stream,msg); - }; - status_num_old = status_numlock; - fflush(stream); - }; - }; - }; - }; - return 0; -} |