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 a57cf5fb31150459013031565c1fd026e03901de
parent 5d5aaf08dc19d798f31cab139e1030918c1f62ac
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 27 Dec 2020 22:03:51 +0100

Cleanup

Diffstat:
MMakefile | 4++--
Mltk.h | 25-------------------------
Mltkc.c | 57++++++++++++++++++++++++++++++++++-----------------------
Mltkd.c | 760++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mutil.c | 15+++++++++++++++
Mutil.h | 1+
6 files changed, 455 insertions(+), 407 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,8 +7,8 @@ all: ltkd ltkc ltkd: $(OBJ) $(COMPATOBJ) $(CC) -o $@ $(OBJ) $(COMPATOBJ) $(LDFLAGS) -ltkc: ltkc.o - $(CC) -o $@ ltkc.o +ltkc: ltkc.o util.o + $(CC) -o $@ ltkc.o util.o %.o: %.c $(CC) -c -o $@ $< $(CFLAGS) diff --git a/ltk.h b/ltk.h @@ -137,44 +137,19 @@ typedef struct ltk_window { } ltk_window; void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect); - void ltk_fatal(const char *msg); - int ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col); - void ltk_queue_event(ltk_window *window, ltk_event_type type, const char *id, const char *data); - -int ltk_mainloop(ltk_window *window); - -ltk_window *ltk_create_window( - const char *theme_path, const char *title, - int x, int y, unsigned int w, unsigned int h); - -void ltk_redraw_window(ltk_window *window); - -void ltk_destroy_window(ltk_window *window); - -void ltk_window_other_event(ltk_window *window, XEvent event); - int ltk_collide_rect(ltk_rect rect, int x, int y); - void ltk_remove_active_widget(ltk_widget *widget); - void ltk_set_active_widget(ltk_window *window, ltk_widget *widget); - void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window * window, void (*draw) (void *), void (*change_state) (void *), void (*destroy) (void *, int), unsigned int needs_redraw, ltk_widget_type type); - void ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event); - void ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event); - void ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event); - -void ltk_handle_event(ltk_window *window, XEvent event); - int ltk_check_widget_id_free(ltk_window *window, const char *id, const char *caller); ltk_widget *ltk_get_widget(ltk_window *window, const char *id, diff --git a/ltkc.c b/ltkc.c @@ -24,7 +24,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <stdint.h> #include <stddef.h> #include <unistd.h> #include <time.h> @@ -32,24 +31,15 @@ #include <sys/select.h> #include <sys/socket.h> #include <sys/un.h> +#include "util.h" #define BLK_SIZE 128 -/* If `needed` is larger than `*alloc_size`, resize `*str` to `*alloc_size * 2`. */ -static int -grow_string(char **str, int *alloc_size, int needed) { - if (needed <= *alloc_size) return 0; - char *new = realloc(*str, *alloc_size * 2); - if (!new) return 1; - *str = new; - *alloc_size = *alloc_size * 2; - return 0; -} static struct { - char *in_buffer; + char *in_buffer; /* text that is read from stdin and written to the socket */ int in_len; int in_alloc; - char *out_buffer; + char *out_buffer; /* text that is read from the socket and written to stdout */ int out_len; int out_alloc; } io_buffers; @@ -97,7 +87,10 @@ int main(int argc, char *argv[]) { maxwfd = sockfd > outfd ? sockfd : outfd; while (1) { - if (!FD_ISSET(infd, &rallfds) && !FD_ISSET(sockfd, &rallfds) && io_buffers.in_len == 0 && io_buffers.out_len == 0) + if (!FD_ISSET(infd, &rallfds) && + !FD_ISSET(sockfd, &rallfds) && + io_buffers.in_len == 0 && + io_buffers.out_len == 0) break; rfds = rallfds; wfds = wallfds; @@ -105,9 +98,14 @@ int main(int argc, char *argv[]) { leading to the loop looping way too fast */ select(maxrfd + 1, &rfds, NULL, NULL, &tv); select(maxwfd + 1, NULL, &wfds, NULL, &tv); + if (FD_ISSET(sockfd, &rfds)) { - grow_string(&io_buffers.out_buffer, &io_buffers.out_alloc, io_buffers.out_len + BLK_SIZE); - int nread = read(sockfd, io_buffers.out_buffer + io_buffers.out_len, BLK_SIZE); + ltk_grow_string(&io_buffers.out_buffer, + &io_buffers.out_alloc, + io_buffers.out_len + BLK_SIZE); + int nread = read(sockfd, + io_buffers.out_buffer + io_buffers.out_len, + BLK_SIZE); if (nread < 0) { return 2; } else if (nread == 0) { @@ -117,9 +115,14 @@ int main(int argc, char *argv[]) { io_buffers.out_len += nread; } } + if (FD_ISSET(infd, &rfds)) { - grow_string(&io_buffers.in_buffer, &io_buffers.in_alloc, io_buffers.in_len + BLK_SIZE); - int nread = read(infd, io_buffers.in_buffer + io_buffers.in_len, BLK_SIZE); + ltk_grow_string(&io_buffers.in_buffer, + &io_buffers.in_alloc, + io_buffers.in_len + BLK_SIZE); + int nread = read(infd, + io_buffers.in_buffer + io_buffers.in_len, + BLK_SIZE); if (nread < 0) { return 2; } else if (nread == 0) { @@ -128,28 +131,36 @@ int main(int argc, char *argv[]) { io_buffers.in_len += nread; } } + if (FD_ISSET(sockfd, &wfds)) { - int maxwrite = BLK_SIZE > io_buffers.in_len ? io_buffers.in_len : BLK_SIZE; + int maxwrite = BLK_SIZE > io_buffers.in_len ? + io_buffers.in_len : BLK_SIZE; int nwritten = write(sockfd, io_buffers.in_buffer, maxwrite); if (nwritten < 0) { return 2; } else { - memmove(io_buffers.in_buffer, io_buffers.in_buffer + nwritten, io_buffers.in_len - nwritten); + memmove(io_buffers.in_buffer, + io_buffers.in_buffer + nwritten, + io_buffers.in_len - nwritten); io_buffers.in_len -= nwritten; } } + if (FD_ISSET(outfd, &wfds)) { - int maxwrite = BLK_SIZE > io_buffers.out_len ? io_buffers.out_len : BLK_SIZE; + int maxwrite = BLK_SIZE > io_buffers.out_len ? + io_buffers.out_len : BLK_SIZE; int nwritten = write(outfd, io_buffers.out_buffer, maxwrite); if (nwritten < 0) { return 2; } else { - memmove(io_buffers.out_buffer, io_buffers.out_buffer + nwritten, io_buffers.out_len - nwritten); + memmove(io_buffers.out_buffer, + io_buffers.out_buffer + nwritten, + io_buffers.out_len - nwritten); io_buffers.out_len -= nwritten; } } } - /* FIXME: close socket */ + close(sockfd); return 0; } diff --git a/ltkd.c b/ltkd.c @@ -25,7 +25,6 @@ #include <stdlib.h> #include <string.h> #include <stdint.h> -#include <fcntl.h> #include <unistd.h> #include <time.h> #include <errno.h> @@ -73,356 +72,43 @@ static struct ltk_sock_info { struct token_list tokens; /* current tokens */ } sockets[MAX_SOCK_CONNS]; +static int ltk_mainloop(ltk_window *window); +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_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); static void ltk_load_theme(ltk_window *window, const char *path); static void ltk_destroy_theme(ltk_theme *theme); static ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2); static int read_sock(struct ltk_sock_info *sock); +static int push_token(struct token_list *tl, char *token); +static int read_sock(struct ltk_sock_info *sock); +static int write_sock(struct ltk_sock_info *sock); +static int queue_sock_write(struct ltk_sock_info *sock, const char *str, int len); +static int tokenize_command(struct ltk_sock_info *sock); +static void ltk_set_root_widget_cmd(ltk_window *window, char **tokens, int num_tokens); +static void process_commands(ltk_window *window, struct ltk_sock_info *sock); +static ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2); +static int add_client(int fd); +static int listen_sock(const char *sock_path); +static int accept_sock(int listenfd); -static int running = 1; - -static int -push_token(struct token_list *tl, char *token) { - int new_size; - if (tl->num_tokens >= tl->num_alloc) { - new_size = (tl->num_alloc * 2) > (tl->num_tokens + 1) ? - (tl->num_alloc * 2) : (tl->num_tokens + 1); - char **new = realloc(tl->tokens, new_size * sizeof(char *)); - if (!new) return -1; - tl->tokens = new; - tl->num_alloc = new_size; - } - tl->tokens[tl->num_tokens++] = token; - - return 0; -} - -/* If `needed` is larger than `*alloc_size`, resize `*str` to `*alloc_size * 2`. */ -static int -grow_string(char **str, int *alloc_size, int needed) { - if (needed <= *alloc_size) return 0; - int new_size = needed > (*alloc_size * 2) ? needed : (*alloc_size * 2); - char *new = realloc(*str, new_size); - if (!new) return 1; - *str = new; - *alloc_size = new_size; - return 0; -} -/* FIXME: non-blocking io? */ -/* Read up to READ_BLK_SIZE bytes from the socket. - Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise. */ -static int -read_sock(struct ltk_sock_info *sock) { - int nread; - char *old = sock->read; - int ret = grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE); - if (ret) return -1; /* fixme: errno? */ - /* move tokens to new addresses - this was added as an - afterthought and really needs to be cleaned up */ - if (sock->read != old) { - for (int i = 0; i < sock->tokens.num_tokens; i++) { - sock->tokens.tokens[i] = sock->read + (sock->tokens.tokens[i] - old); - } - } - nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE); - if (nread == -1 || nread == 0) - return nread; - sock->read_len += nread; - - return 1; -} - -/* Write up to WRITE_BLK_SIZE bytes to the socket. - Returns -1 on error, 0 otherwise. */ -static int -write_sock(struct ltk_sock_info *sock) { - if (!sock->write_len) - return 0; - int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ? - sock->write_len - sock->write_cur : WRITE_BLK_SIZE; - int nwritten = write(sock->fd, sock->to_write + sock->write_cur, write_len); - if (nwritten == -1) - return nwritten; - sock->write_cur += nwritten; - return 0; -} - -/* Queue str to be written to the socket. If len is < 0, it is set to strlen(str). - Returns -1 on error, 0 otherwise. - Note: The string must include all '\n', etc. as defined in the protocol. This - function just adds the given string verbatim. */ -static int -queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) { - if (sock->write_cur > 0) { - memmove(sock->to_write, sock->to_write + sock->write_cur, - sock->write_len - sock->write_cur); - sock->write_len -= sock->write_cur; - sock->write_cur = 0; - } - - if (len < 0) - len = strlen(str); - - if (sock->write_alloc - sock->write_len < len && - grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len)) - return -1; - - (void)strncpy(sock->to_write + sock->write_len, str, len); - sock->write_len += len; - - return 0; -} - -/* Returns 0 if the end of a command was encountered, 1 otherwise */ -static int -tokenize_command(struct ltk_sock_info *sock) { - for (; sock->read_cur < sock->read_len; sock->read_cur++) { - if (!sock->in_token) { - push_token(&sock->tokens, sock->read + sock->read_cur - sock->offset); - sock->in_token = 1; - } - if (sock->read[sock->read_cur] == '\\') { - sock->bs++; - sock->bs %= 2; - sock->read[sock->read_cur-sock->offset] = '\\'; - } else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) { - sock->read[sock->read_cur-sock->offset] = '\0'; - sock->read_cur++; - sock->offset = 0; - sock->in_token = 0; - return 0; - } else if (sock->read[sock->read_cur] == '"') { - sock->offset++; - if (sock->bs) { - sock->read[sock->read_cur-sock->offset] = '"'; - sock->bs = 0; - } else { - sock->in_str = !sock->in_str; - } - } else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) { - sock->read[sock->read_cur-sock->offset] = '\0'; - sock->in_token = !sock->in_token; - } else { - sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur]; - sock->bs = 0; - } - } - - return 1; -} - -void -ltk_clean_up(ltk_window *window) { - ltk_destroy_theme(window->theme); - ltk_destroy_window(window); -} - -void -ltk_quit(ltk_window *window) { - ltk_clean_up(window); - running = 0; -} - -static void -ltk_set_root_widget_cmd( - ltk_window *window, - char **tokens, - int num_tokens) { - ltk_widget *widget; - if (num_tokens != 2) { - (void)fprintf(stderr, "set-root-widget: Invalid number of arguments.\n"); - return; - } - widget = ltk_get_widget(window, tokens[1], LTK_WIDGET, "set-root-widget"); - if (!widget) return; - window->root_widget = widget; - int w = widget->rect.w; - int h = widget->rect.h; - widget->rect.w = window->rect.w; - widget->rect.h = window->rect.h; - if (widget->resize) { - widget->resize(widget, w, h); - } -} - -static void -process_commands(ltk_window *window, struct ltk_sock_info *sock) { - char **tokens; - int num_tokens; - while (!tokenize_command(sock)) { - tokens = sock->tokens.tokens; - num_tokens = sock->tokens.num_tokens; - if (num_tokens < 1) - continue; - if (strcmp(tokens[0], "grid") == 0) { - ltk_grid_cmd(window, tokens, num_tokens); - } else if (strcmp(tokens[0], "button") == 0) { - ltk_button_cmd(window, tokens, num_tokens); - } else if (strcmp(tokens[0], "set-root-widget") == 0) { - ltk_set_root_widget_cmd(window, tokens, num_tokens); - } else if (strcmp(tokens[0], "draw") == 0) { - ltk_draw_cmd(window, tokens, num_tokens); - } else if (strcmp(tokens[0], "quit") == 0) { - ltk_quit(window); - } else { - /* FIXME... */ - (void)fprintf(stderr, "Invalid command.\n"); - } - sock->tokens.num_tokens = 0; - } - if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0] != sock->read) { - memmove(sock->read, sock->tokens.tokens[0], sock->read + sock->read_len - sock->tokens.tokens[0]); - ptrdiff_t offset = sock->tokens.tokens[0] - sock->read; - /* Hmm, seems a bit ugly... */ - for (int i = 0; i < sock->tokens.num_tokens; i++) { - sock->tokens.tokens[i] -= offset; - } - sock->read_len -= offset; - sock->read_cur -= offset; - } else if (sock->tokens.num_tokens == 0) { - sock->read_len = 0; - sock->read_cur = 0; - } -} - -static ltk_rect -ltk_rect_union(ltk_rect r1, ltk_rect r2) { - ltk_rect u; - u.x = r1.x < r2.x ? r1.x : r2.x; - u.y = r1.y < r2.y ? r1.y : r2.y; - int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w; - int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h; - u.w = x2 - u.x; - u.h = y2 - u.y; - return u; -} - -void -ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) { - if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0) - window->dirty_rect = rect; - else - window->dirty_rect = ltk_rect_union(rect, window->dirty_rect); -} - -void -ltk_fatal(const char *msg) { - (void)fprintf(stderr, msg); - /* FIXME: clean up first */ - exit(1); -}; - -int -ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col) { - if (!XParseColor(window->dpy, window->cm, hex, col)) { - (void)fprintf(stderr, "Invalid color: %s\n", hex); - return 0; - } - XAllocColor(window->dpy, window->cm, col); - - return 1; -} - -void -ltk_queue_event(ltk_window *window, ltk_event_type type, const char *id, const char *data) { - /* FIXME: make it nicer and safer */ - struct ltk_event_queue *new = malloc(sizeof(struct ltk_event_queue)); - if (!new) ltk_fatal("Unable to queue event.\n"); - new->event_type = type; - int id_len = strlen(id); - int data_len = strlen(data); - new->data = malloc(id_len + data_len + 3); - if (!new->data) ltk_fatal("Unable to queue event.\n"); - strcpy(new->data, id); - new->data[id_len] = ' '; - strcpy(new->data + id_len + 1, data); - new->data[id_len + data_len + 1] = '\n'; - new->data[id_len + data_len + 2] = '\0'; - new->next = window->first_event; - window->first_event = new; - new->prev = NULL; - if (!window->last_event) - window->last_event = new; -} - -static int -add_client(int fd) { - for (int i = 0; i < MAX_SOCK_CONNS; i++) { - if (sockets[i].fd == -1) { - sockets[i].fd = fd; - sockets[i].event_mask = ~0; /* FIXME */ - sockets[i].read_len = 0; - sockets[i].write_len = 0; - sockets[i].write_cur = 0; - sockets[i].offset = 0; - sockets[i].in_str = 0; - sockets[i].read_cur = 0; - sockets[i].bs = 0; - sockets[i].tokens.num_tokens = 0; - return i; - } - } - - return -1; -} - -/* largely copied from APUE */ -static int -listen_sock(const char *sock_path) { - int fd, len, err, rval; - struct sockaddr_un un; - - if (strlen(sock_path) >= sizeof(un.sun_path)) { - errno = ENAMETOOLONG; - return -1; - } - - if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) - return -2; - - unlink(sock_path); - - memset(&un, 0, sizeof(un)); - un.sun_family = AF_UNIX; - strcpy(un.sun_path, sock_path); - len = offsetof(struct sockaddr_un, sun_path) + strlen(sock_path); - if (bind(fd, (struct sockaddr *)&un, len) < 0) { - rval = -3; - goto errout; - } - - if (listen(fd, 10) < 0) { - rval = -4; - goto errout; - } - - return fd; +static int maxsocket = -1; +static char running = 1; +static char sock_write_available = 0; -errout: - err = errno; - close(fd); - errno = err; - return rval; +int main(int argc, char *argv[]) { + ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500); + return ltk_mainloop(window); } static int -accept_sock(int listenfd) { - int clifd; - socklen_t len; - struct sockaddr_un un; - - len = sizeof(un); - if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) { - return -1; - } - - return clifd; -} - -int ltk_mainloop(ltk_window *window) { XEvent event; fd_set rfds, wfds, rallfds, wallfds; - int maxi, maxfd, listenfd; + int maxfd, listenfd; int rretval, wretval; int clifd; struct timeval tv; @@ -448,7 +134,6 @@ ltk_mainloop(ltk_window *window) { FD_SET(listenfd, &rallfds); maxfd = listenfd; - maxi = -1; while (running) { rfds = rallfds; @@ -461,11 +146,8 @@ ltk_mainloop(ltk_window *window) { XNextEvent(window->dpy, &event); ltk_handle_event(window, event); } - /* FIXME: somehow keep track of whether anything has to be written, - otherwise it always has to loop over all fds to check - the writing - fds are usually always set, so this is really run on every loop - iteration, which is bad */ - if (rretval > 0 || wretval > 0) { + + 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"); @@ -476,11 +158,11 @@ ltk_mainloop(ltk_window *window) { FD_SET(clifd, &wallfds); if (clifd > maxfd) maxfd = clifd; - if (i > maxi) - maxi = i; + if (i > maxsocket) + maxsocket = i; continue; } - for (int i = 0; i <= maxi; i++) { + for (int i = 0; i <= maxsocket; i++) { if ((clifd = sockets[i].fd) < 0) continue; if (FD_ISSET(clifd, &rfds)) { @@ -499,17 +181,19 @@ ltk_mainloop(ltk_window *window) { } } } + if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) { ltk_redraw_window(window); window->dirty_rect.w = 0; window->dirty_rect.h = 0; } + if (window->last_event && running) { struct ltk_event_queue *cur = window->last_event; struct ltk_event_queue *last; do { int event_len = strlen(cur->data); - for (int i = 0; i <= maxi; i++) { + 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 */ @@ -524,7 +208,10 @@ ltk_mainloop(ltk_window *window) { } } + for (int i = 0; i < MAX_SOCK_CONNS; i++) { + if (sockets[i].fd >= 0) + close(sockets[i].fd); if (sockets[i].read) free(sockets[i].read); if (sockets[i].to_write) @@ -533,12 +220,108 @@ ltk_mainloop(ltk_window *window) { free(sockets[i].tokens.tokens); } + close(listenfd); + unlink("ltk.sock"); return 0; } -void +void +ltk_clean_up(ltk_window *window) { + ltk_destroy_theme(window->theme); + ltk_destroy_window(window); +} + +void +ltk_quit(ltk_window *window) { + ltk_clean_up(window); + running = 0; +} + +static void +ltk_set_root_widget_cmd( + ltk_window *window, + char **tokens, + int num_tokens) { + ltk_widget *widget; + if (num_tokens != 2) { + (void)fprintf(stderr, "set-root-widget: Invalid number of arguments.\n"); + return; + } + widget = ltk_get_widget(window, tokens[1], LTK_WIDGET, "set-root-widget"); + if (!widget) return; + window->root_widget = widget; + int w = widget->rect.w; + int h = widget->rect.h; + widget->rect.w = window->rect.w; + widget->rect.h = window->rect.h; + if (widget->resize) { + widget->resize(widget, w, h); + } +} + +static ltk_rect +ltk_rect_union(ltk_rect r1, ltk_rect r2) { + ltk_rect u; + u.x = r1.x < r2.x ? r1.x : r2.x; + u.y = r1.y < r2.y ? r1.y : r2.y; + int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w; + int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h; + u.w = x2 - u.x; + u.h = y2 - u.y; + return u; +} + +void +ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) { + if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0) + window->dirty_rect = rect; + else + window->dirty_rect = ltk_rect_union(rect, window->dirty_rect); +} + +void +ltk_fatal(const char *msg) { + (void)fprintf(stderr, msg); + /* FIXME: clean up first */ + exit(1); +}; + +int +ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col) { + if (!XParseColor(window->dpy, window->cm, hex, col)) { + (void)fprintf(stderr, "Invalid color: %s\n", hex); + return 0; + } + XAllocColor(window->dpy, window->cm, col); + + return 1; +} + +void +ltk_queue_event(ltk_window *window, ltk_event_type type, const char *id, const char *data) { + /* FIXME: make it nicer and safer */ + struct ltk_event_queue *new = malloc(sizeof(struct ltk_event_queue)); + if (!new) ltk_fatal("Unable to queue event.\n"); + new->event_type = type; + int id_len = strlen(id); + int data_len = strlen(data); + new->data = malloc(id_len + data_len + 3); + if (!new->data) ltk_fatal("Unable to queue event.\n"); + strcpy(new->data, id); + new->data[id_len] = ' '; + strcpy(new->data + id_len + 1, data); + new->data[id_len + data_len + 1] = '\n'; + new->data[id_len + data_len + 2] = '\0'; + new->next = window->first_event; + window->first_event = new; + new->prev = NULL; + if (!window->last_event) + window->last_event = new; +} + +static void ltk_redraw_window(ltk_window *window) { ltk_widget *ptr; if (!window) return; @@ -555,7 +338,7 @@ ltk_redraw_window(ltk_window *window) { ptr->draw(ptr); } -void +static void ltk_window_other_event(ltk_window *window, XEvent event) { ltk_widget *ptr = window->root_widget; if (event.type == ConfigureNotify) { @@ -587,7 +370,7 @@ ltk_window_other_event(ltk_window *window, XEvent event) { } } -ltk_window * +static ltk_window * ltk_create_window(const char *theme_path, const char *title, int x, int y, unsigned int w, unsigned int h) { ltk_window *window = malloc(sizeof(ltk_window)); if (!window) @@ -638,7 +421,7 @@ ltk_create_window(const char *theme_path, const char *title, int x, int y, unsig return window; } -void +static void ltk_destroy_window(ltk_window *window) { khint_t k; ltk_widget *ptr; @@ -832,7 +615,7 @@ ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) { widget->motion_notify(widget, event); } -void +static void ltk_handle_event(ltk_window *window, XEvent event) { ltk_widget *root_widget = window->root_widget; switch (event.type) { @@ -906,7 +689,270 @@ ltk_remove_widget(ltk_window *window, const char *id) { } } -int main(int argc, char *argv[]) { - ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500); - return ltk_mainloop(window); +/* Push a token onto `token_list`, resizing the buffer if necessary. + Returns -1 on error, 0 otherwise. + Note: The token is not copied, it is only added directly. */ +static int +push_token(struct token_list *tl, char *token) { + int new_size; + if (tl->num_tokens >= tl->num_alloc) { + new_size = (tl->num_alloc * 2) > (tl->num_tokens + 1) ? + (tl->num_alloc * 2) : (tl->num_tokens + 1); + char **new = realloc(tl->tokens, new_size * sizeof(char *)); + if (!new) return -1; + tl->tokens = new; + tl->num_alloc = new_size; + } + tl->tokens[tl->num_tokens++] = token; + + return 0; +} + +/* Add a new client to the socket list and return the index in `sockets`. + Returns -1 if there is no space for a new client. */ +static int +add_client(int fd) { + for (int i = 0; i < MAX_SOCK_CONNS; i++) { + if (sockets[i].fd == -1) { + sockets[i].fd = fd; + sockets[i].event_mask = ~0; /* FIXME */ + sockets[i].read_len = 0; + sockets[i].write_len = 0; + sockets[i].write_cur = 0; + sockets[i].offset = 0; + sockets[i].in_str = 0; + sockets[i].read_cur = 0; + sockets[i].bs = 0; + sockets[i].tokens.num_tokens = 0; + return i; + } + } + + return -1; +} + +/* largely copied from APUE */ +/* Listen on the socket at `sock_path`. + Returns the file descriptor of the opened socket on success. + Returns -1 if `sock_path` is too long + -2 if the socket could not be created + -3 if the socket could not be bound to the path + -4 if the socket could not be listened on */ +static int +listen_sock(const char *sock_path) { + int fd, len, err, rval; + struct sockaddr_un un; + + if (strlen(sock_path) >= sizeof(un.sun_path)) { + errno = ENAMETOOLONG; + return -1; + } + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + return -2; + + unlink(sock_path); + + memset(&un, 0, sizeof(un)); + un.sun_family = AF_UNIX; + strcpy(un.sun_path, sock_path); + len = offsetof(struct sockaddr_un, sun_path) + strlen(sock_path); + if (bind(fd, (struct sockaddr *)&un, len) < 0) { + rval = -3; + goto errout; + } + + if (listen(fd, 10) < 0) { + rval = -4; + goto errout; + } + + return fd; + +errout: + err = errno; + close(fd); + errno = err; + return rval; +} + +/* Accept a socket connection on the listening socket `listenfd`. + Returns the file descriptor of the accepted client on success. + Returns -1 if there was an error. */ +static int +accept_sock(int listenfd) { + int clifd; + socklen_t len; + struct sockaddr_un un; + + len = sizeof(un); + if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) { + return -1; + } + + return clifd; +} + +/* Read up to READ_BLK_SIZE bytes from the socket `sock`. + Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise. + Note: Returning 1 on success is weird, but it could also be confusing to + return 0 on success when `read` returns that to mean that the connection + was closed. */ +static int +read_sock(struct ltk_sock_info *sock) { + int nread; + char *old = sock->read; + int ret = ltk_grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE); + if (ret) return -1; /* fixme: errno? */ + /* move tokens to new addresses - this was added as an + afterthought and really needs to be cleaned up */ + if (sock->read != old) { + for (int i = 0; i < sock->tokens.num_tokens; i++) { + sock->tokens.tokens[i] = sock->read + (sock->tokens.tokens[i] - old); + } + } + nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE); + if (nread == -1 || nread == 0) + return nread; + sock->read_len += nread; + + return 1; +} + +/* Write up to WRITE_BLK_SIZE bytes to the socket. + Returns -1 on error, 0 otherwise. */ +static int +write_sock(struct ltk_sock_info *sock) { + if (!sock->write_len) + return 0; + int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ? + sock->write_len - sock->write_cur : WRITE_BLK_SIZE; + int nwritten = write(sock->fd, sock->to_write + sock->write_cur, write_len); + if (nwritten == -1) + return nwritten; + sock->write_cur += nwritten; + + /* check if any sockets have text to write */ + if (sock->write_cur == sock->write_len) { + int found = 0; + for (int i = 0; i < maxsocket; i++) { + if (sockets[i].fd != -1 && + sockets[i].write_cur != sockets[i].write_len) { + found = 1; + break; + } + } + if (!found) + sock_write_available = 0; + } + + return 0; +} + +/* Queue `str` to be written to the socket. If len is < 0, it is set to `strlen(str)`. + Returns -1 on error, 0 otherwise. + Note: The string must include all '\n', etc. as defined in the protocol. This + function just adds the given string verbatim. */ +static int +queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) { + if (sock->write_cur > 0) { + memmove(sock->to_write, sock->to_write + sock->write_cur, + sock->write_len - sock->write_cur); + sock->write_len -= sock->write_cur; + sock->write_cur = 0; + } + + if (len < 0) + len = strlen(str); + + if (sock->write_alloc - sock->write_len < len && + ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len)) + return -1; + + (void)strncpy(sock->to_write + sock->write_len, str, len); + sock->write_len += len; + + sock_write_available = 1; + + return 0; +} + +/* Tokenize the current read buffer in `sock`. + Returns 0 immediately if the end of a command was encountered, 1 otherwise. */ +static int +tokenize_command(struct ltk_sock_info *sock) { + for (; sock->read_cur < sock->read_len; sock->read_cur++) { + if (!sock->in_token) { + push_token(&sock->tokens, sock->read + sock->read_cur - sock->offset); + sock->in_token = 1; + } + if (sock->read[sock->read_cur] == '\\') { + sock->bs++; + sock->bs %= 2; + sock->read[sock->read_cur-sock->offset] = '\\'; + } else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) { + sock->read[sock->read_cur-sock->offset] = '\0'; + sock->read_cur++; + sock->offset = 0; + sock->in_token = 0; + return 0; + } else if (sock->read[sock->read_cur] == '"') { + sock->offset++; + if (sock->bs) { + sock->read[sock->read_cur-sock->offset] = '"'; + sock->bs = 0; + } else { + sock->in_str = !sock->in_str; + } + } else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) { + sock->read[sock->read_cur-sock->offset] = '\0'; + sock->in_token = !sock->in_token; + } else { + sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur]; + sock->bs = 0; + } + } + + return 1; +} + +/* Process the commands as they are read from the socket. */ +static void +process_commands(ltk_window *window, struct ltk_sock_info *sock) { + char **tokens; + int num_tokens; + while (!tokenize_command(sock)) { + tokens = sock->tokens.tokens; + num_tokens = sock->tokens.num_tokens; + if (num_tokens < 1) + continue; + if (strcmp(tokens[0], "grid") == 0) { + ltk_grid_cmd(window, tokens, num_tokens); + } else if (strcmp(tokens[0], "button") == 0) { + ltk_button_cmd(window, tokens, num_tokens); + } else if (strcmp(tokens[0], "set-root-widget") == 0) { + ltk_set_root_widget_cmd(window, tokens, num_tokens); + } else if (strcmp(tokens[0], "draw") == 0) { + ltk_draw_cmd(window, tokens, num_tokens); + } else if (strcmp(tokens[0], "quit") == 0) { + ltk_quit(window); + } else { + /* FIXME... */ + (void)fprintf(stderr, "Invalid command.\n"); + } + sock->tokens.num_tokens = 0; + } + if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0] != sock->read) { + memmove(sock->read, sock->tokens.tokens[0], sock->read + sock->read_len - sock->tokens.tokens[0]); + ptrdiff_t offset = sock->tokens.tokens[0] - sock->read; + /* Hmm, seems a bit ugly... */ + for (int i = 0; i < sock->tokens.num_tokens; i++) { + sock->tokens.tokens[i] -= offset; + } + sock->read_len -= offset; + sock->read_cur -= offset; + } else if (sock->tokens.num_tokens == 0) { + sock->read_len = 0; + sock->read_cur = 0; + } } diff --git a/util.c b/util.c @@ -46,3 +46,18 @@ ltk_read_file(const char *path, unsigned long *len) { return file_contents; } + +/* If `needed` is larger than `*alloc_size`, resize `*str` to + `max(needed, *alloc_size * 2)`. + Returns 1 on error, 0 otherwise. If an error occurs, `*str` and + `*alloc_size` are not modified. */ +int +ltk_grow_string(char **str, int *alloc_size, int needed) { + if (needed <= *alloc_size) return 0; + int new_size = needed > (*alloc_size * 2) ? needed : (*alloc_size * 2); + char *new = realloc(*str, new_size); + if (!new) return 1; + *str = new; + *alloc_size = new_size; + return 0; +} diff --git a/util.h b/util.h @@ -29,3 +29,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);