ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit c437b901b8ed5a6ebbc4c46806ee215adad4373c
parent a57cf5fb31150459013031565c1fd026e03901de
Author: lumidify <nobody@lumidify.org>
Date:   Mon, 28 Dec 2020 23:23:21 +0100

Read sockets from common directory so multiple ltk programs can be running at the same time

Diffstat:
M.gitignore | 1+
MTODO | 5+++++
Mltkc.c | 33+++++++++++++++++++++++++++++++--
Mltkd.c | 157++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mtest.sh | 17++++-------------
Mutil.c | 40++++++++++++++++++++++++++++++++++++++++
Mutil.h | 1+
7 files changed, 213 insertions(+), 41 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -3,3 +3,4 @@ ltkc ltk.sock *.o *.core +.ltk diff --git a/TODO b/TODO @@ -1,6 +1,11 @@ Convert points to pixels for stb rendering (currently, the size between pango and stb is completely different). +Implement broadcast command for communication between clients +-> Maybe also some sort of client storage? (probably overkill) + +Catch signals in ltkc to send quit command to ltkd. + Random stuff: * This is not really a general-purpose GUI toolkit - imagine building a complex GUI with this. Especially things like having to respond diff --git a/ltkc.c b/ltkc.c @@ -53,6 +53,28 @@ int main(int argc, char *argv[]) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 15; + char *ltk_dir = NULL, *sock_path = NULL; + int path_size; + + if (argc != 2) { + (void)fprintf(stderr, "USAGE: ltkc <socket id>\n"); + return 1; + } + + ltk_dir = ltk_setup_directory(); + if (!ltk_dir) { + (void)fprintf(stderr, "Unable to setup ltk directory.\n"); + return 1; + } + + /* 7 because of "/", ".sock", and '\0' */ + path_size = strlen(ltk_dir) + strlen(argv[1]) + 7; + sock_path = malloc(path_size); + if (!sock_path) { + (void)fprintf(stderr, "Unable to allocate memory for socket path.\n"); + return 1; + } + snprintf(sock_path, path_size, "%s/%s.sock", ltk_dir, argv[1]); if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { perror("Socket error"); @@ -60,8 +82,12 @@ int main(int argc, char *argv[]) { } memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; - strcpy(un.sun_path, "ltk.sock"); - if (connect(sockfd, (struct sockaddr *)&un, offsetof(struct sockaddr_un, sun_path) + 9) < 0) { + if (path_size > sizeof(un.sun_path)) { + (void)fprintf(stderr, "Socket path too long.\n"); + return 1; + } + strcpy(un.sun_path, sock_path); + if (connect(sockfd, (struct sockaddr *)&un, offsetof(struct sockaddr_un, sun_path) + path_size) < 0) { perror("Socket error"); return -2; } @@ -162,5 +188,8 @@ int main(int argc, char *argv[]) { } close(sockfd); + free(ltk_dir); + free(sock_path); + return 0; } diff --git a/ltkd.c b/ltkd.c @@ -25,9 +25,11 @@ #include <stdlib.h> #include <string.h> #include <stdint.h> +#include <fcntl.h> #include <unistd.h> #include <time.h> #include <errno.h> +#include <signal.h> #include <sys/stat.h> #include <sys/select.h> #include <sys/socket.h> @@ -73,9 +75,14 @@ static struct ltk_sock_info { } sockets[MAX_SOCK_CONNS]; static int ltk_mainloop(ltk_window *window); +static char *get_sock_path(char *basedir, Window id); +static FILE *open_log(char *dir); +static void daemonize(void); static ltk_window *ltk_create_window(const char *theme_path, const char *title, int x, int y, unsigned int w, unsigned int h); static void ltk_destroy_window(ltk_window *window); +static void ltk_cleanup_gui(ltk_window *window); +static void ltk_cleanup_nongui(void); static void ltk_redraw_window(ltk_window *window); static void ltk_window_other_event(ltk_window *window, XEvent event); static void ltk_handle_event(ltk_window *window, XEvent event); @@ -95,9 +102,13 @@ static int add_client(int fd); static int listen_sock(const char *sock_path); static int accept_sock(int listenfd); -static int maxsocket = -1; -static char running = 1; -static char sock_write_available = 0; +static short maxsocket = -1; +static short running = 1; +static short sock_write_available = 0; +static int listenfd = -1; +static char *ltk_dir = NULL; +static FILE *ltk_logfile = NULL; +static char *sock_path = NULL; int main(int argc, char *argv[]) { ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500); @@ -108,13 +119,20 @@ static int ltk_mainloop(ltk_window *window) { XEvent event; fd_set rfds, wfds, rallfds, wallfds; - int maxfd, listenfd; + int maxfd; int rretval, wretval; int clifd; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 10; + ltk_dir = ltk_setup_directory(); + if (!ltk_dir) ltk_fatal("Unable to setup ltk directory.\n"); + ltk_logfile = open_log(ltk_dir); + if (!ltk_logfile) ltk_fatal("Unable to open log file.\n"); + sock_path = get_sock_path(ltk_dir, window->xwindow); + if (!sock_path) ltk_fatal("Unable to allocate memory for socket path.\n"); + /* Note: sockets should be initialized to 0 because it is static */ for (int i = 0; i < MAX_SOCK_CONNS; i++) { sockets[i].fd = -1; /* socket unused */ @@ -127,14 +145,15 @@ ltk_mainloop(ltk_window *window) { FD_ZERO(&rallfds); FD_ZERO(&wallfds); - if ((listenfd = listen_sock("ltk.sock")) < 0) { - fprintf(stderr, "Error listening on ltk.sock\n"); - exit(1); /* FIXME: proper error handling */ - } + if ((listenfd = listen_sock(sock_path)) < 0) + ltk_fatal("Error listening on socket.\n"); FD_SET(listenfd, &rallfds); maxfd = listenfd; + printf("%d", window->xwindow); + daemonize(); + while (running) { rfds = rallfds; wfds = wallfds; @@ -150,8 +169,8 @@ ltk_mainloop(ltk_window *window) { if (rretval > 0 || (sock_write_available && wretval > 0)) { if (FD_ISSET(listenfd, &rfds)) { if ((clifd = accept_sock(listenfd)) < 0) { - fprintf(stderr, "Error accepting socket connection\n"); - exit(1); /* FIXME: proper error handling */ + /* FIXME: Just log this! */ + ltk_fatal("Error accepting socket connection.\n"); } int i = add_client(clifd); FD_SET(clifd, &rallfds); @@ -196,7 +215,7 @@ ltk_mainloop(ltk_window *window) { for (int i = 0; i <= maxsocket; i++) { if (sockets[i].fd != -1 && sockets[i].event_mask & cur->event_type) { if (queue_sock_write(&sockets[i], cur->data, event_len) < 0) - exit(1); /* FIXME: error handling */ + ltk_fatal("Unable to queue event.\n"); } } free(cur->data); @@ -209,6 +228,97 @@ ltk_mainloop(ltk_window *window) { } + ltk_cleanup_gui(window); + ltk_cleanup_nongui(); + + return 0; +} + +/* largely copied from APUE... */ +/* am I breaking copyright here? */ +static void +daemonize(void) { + pid_t pid; + struct sigaction sa; + + fflush(stdout); + + if ((pid = fork()) < 0) + ltk_fatal("Can't fork.\n"); + else if (pid != 0) + exit(0); + setsid(); + + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGHUP, &sa, NULL) < 0) + ltk_fatal("Unable to ignore SIGHUP.\n"); + if ((pid = fork()) < 0) + ltk_fatal("Can't fork.\n"); + else if (pid != 0) + exit(0); + + if (chdir("/") < 0) + ltk_fatal("Can't change directory to root.\n"); + + close(fileno(stdin)); + close(fileno(stdout)); + close(fileno(stderr)); + open("/dev/null", O_RDWR); + dup(0); + dup(0); + + /* FIXME: Is it guaranteed that this will work? Will these fds + always be opened on the lowest numbers? */ +} + +static char * +get_sock_path(char *basedir, Window id) { + int len; + char *path; + + len = strlen(basedir); + /* FIXME: MAKE SURE THIS IS ACTUALLY BIG ENOUGH! */ + path = malloc(len + 20); + if (!path) + return NULL; + if (snprintf(path, len + 20, "%s/%d.sock", basedir, id) >= len + 20) + ltk_fatal("Tell lumidify to fix his code.\n"); + + return path; +} + +static FILE * +open_log(char *dir) { + int len; + FILE *f; + char *path; + + len = strlen(dir); + path = malloc(len + 10); + if (!path) + return NULL; + snprintf(path, len + 10, "%s/ltkd.log", dir); + f = fopen(path, "a"); + free(path); + + return f; +} + +static void +ltk_cleanup_nongui(void) { + if (listenfd >= 0) + close(listenfd); + if (ltk_dir) + free(ltk_dir); + if (ltk_logfile) + fclose(ltk_logfile); + if (sock_path) { + unlink(sock_path); + free(sock_path); + } + for (int i = 0; i < MAX_SOCK_CONNS; i++) { if (sockets[i].fd >= 0) close(sockets[i].fd); @@ -219,23 +329,16 @@ ltk_mainloop(ltk_window *window) { if (sockets[i].tokens.tokens) free(sockets[i].tokens.tokens); } - - close(listenfd); - - unlink("ltk.sock"); - - return 0; } -void -ltk_clean_up(ltk_window *window) { +static void +ltk_cleanup_gui(ltk_window *window) { ltk_destroy_theme(window->theme); ltk_destroy_window(window); } void ltk_quit(ltk_window *window) { - ltk_clean_up(window); running = 0; } @@ -283,8 +386,12 @@ ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) { void ltk_fatal(const char *msg) { - (void)fprintf(stderr, msg); - /* FIXME: clean up first */ + FILE *errfile = ltk_logfile; + if (!errfile) + errfile = stderr; + (void)fprintf(errfile, msg); + /* FIXME: cleanup gui stuff too (need window for that) */ + ltk_cleanup_nongui(); exit(1); }; @@ -365,8 +472,7 @@ ltk_window_other_event(ltk_window *window, XEvent event) { ltk_window_invalidate_rect(window, r); } else if (event.type == ClientMessage && event.xclient.data.l[0] == window->wm_delete_msg) { - ltk_destroy_window(window); - exit(0); /* FIXME */ + ltk_quit(window); } } @@ -474,8 +580,7 @@ ltk_load_theme(ltk_window *window, const char *path) { if (!window->theme->window) ltk_fatal("Unable to allocate memory for window theme.\n"); window->theme->button = NULL; if (ini_parse(path, ltk_ini_handler, window) < 0) { - (void)fprintf(stderr, "ERROR: Can't load theme %s\n.", path); - exit(1); + ltk_fatal("Can't load theme.\n"); } } diff --git a/test.sh b/test.sh @@ -5,23 +5,14 @@ # All events are still printed to the terminal curerntly because # the second './ltkc' still prints everything - event masks aren't # supported yet. -# -# Currently, everything just uses the socket 'ltk.sock'. My idea -# was to have a directory containing sockets for all instances of -# ltks, each of them named after the X Window ID used. This would -# allow other tools (screenreader, etc.) to determine which socket -# to use to communicate with a window. Probably ltkd would just -# print out its window ID when starting and then daemonize itself, -# so the calling script can just use save the output of ltkd and -# use that to communicate with the socket using ltkc. -./ltkd& -sleep 0.2 +export LTKDIR="`pwd`/.ltk" +ltk_id=`./ltkd` -cat test.gui | ./ltkc | while read cmd +cat test.gui | ./ltkc $ltk_id | while read cmd do case "$cmd" in "btn1 button_click") echo "quit" ;; esac -done | ./ltkc +done | ./ltkc $ltk_id diff --git a/util.c b/util.c @@ -21,8 +21,11 @@ * SOFTWARE. */ +#include <pwd.h> +#include <errno.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> void ltk_err(const char *msg) { @@ -61,3 +64,40 @@ ltk_grow_string(char **str, int *alloc_size, int needed) { *alloc_size = new_size; return 0; } + +/* Get the directory to store ltk files in and create it if it doesn't exist yet. + This first checks the environment variable LTKDIR and, if that doesn't + exist, the home directory with "/.ltk" appended. + Returns NULL on error. */ +char * +ltk_setup_directory(void) { + char *dir, *dir_orig; + struct passwd *pw; + uid_t uid; + int len; + + dir_orig = getenv("LTKDIR"); + if (dir_orig) { + dir = strdup(dir_orig); + if (!dir) + return NULL; + } else { + uid = getuid(); + pw = getpwuid(uid); + if (!pw) + return NULL; + len = strlen(pw->pw_dir); + dir = malloc(len + 6); + if (!dir) + return NULL; + strcpy(dir, pw->pw_dir); + strcpy(dir + len, "/.ltk"); + } + + if (mkdir(dir, 0770) < 0) { + if (errno != EEXIST) + return NULL; + } + + return dir; +} diff --git a/util.h b/util.h @@ -30,3 +30,4 @@ strtonum(const char *numstr, long long minval, long long maxval, void ltk_err(const char *msg); char *ltk_read_file(const char *path, unsigned long *len); int ltk_grow_string(char **str, int *alloc_size, int needed); +char *ltk_setup_directory(void);