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 | + | 
| M | TODO |  |  | 5 | +++++ | 
| M | ltkc.c |  |  | 33 | +++++++++++++++++++++++++++++++-- | 
| M | ltkd.c |  |  | 157 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- | 
| M | test.sh |  |  | 17 | ++++------------- | 
| M | util.c |  |  | 40 | ++++++++++++++++++++++++++++++++++++++++ | 
| M | util.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);