commit d02312cfb88d3c52be0c9c4e421af28b224fd7b5
parent 55009a9b2fc3477235ddf5cae6c45446e0117802
Author: lumidify <nobody@lumidify.org>
Date: Thu, 24 Dec 2020 14:06:27 +0100
Use sockets instead of stdin and stdout
Diffstat:
M | .gitignore | | | 4 | +++- |
M | Makefile | | | 11 | ++++++++--- |
M | README.md | | | 7 | ++++--- |
A | TODO | | | 15 | +++++++++++++++ |
M | button.c | | | 2 | +- |
D | ltk.c | | | 646 | ------------------------------------------------------------------------------- |
M | ltk.h | | | 30 | ++++++++++++++++++------------ |
A | ltkc.c | | | 154 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ltkd.c | | | 912 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | socket_format.txt | | | 4 | ++-- |
A | test.sh | | | 27 | +++++++++++++++++++++++++++ |
D | test_anim.sh | | | 29 | ----------------------------- |
D | test_draw.gui | | | 10 | ---------- |
13 files changed, 1144 insertions(+), 707 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,3 +1,5 @@
-ltk
+ltkd
+ltkc
+ltk.sock
*.o
*.core
diff --git a/Makefile b/Makefile
@@ -1,14 +1,19 @@
include config.mk
-OBJ += color.o util.o ltk.o ini.o grid.o button.o draw.o
+OBJ += color.o util.o ltkd.o ini.o grid.o button.o draw.o
-ltk: $(OBJ) $(COMPATOBJ)
+all: ltkd ltkc
+
+ltkd: $(OBJ) $(COMPATOBJ)
$(CC) -o $@ $(OBJ) $(COMPATOBJ) $(LDFLAGS)
+ltkc: ltkc.o
+ $(CC) -o $@ ltkc.o
+
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
.PHONY: clean
clean:
- rm -f $(OBJ) ltk *.core
+ rm -f $(OBJ) ltkc.o ltkd ltkc ltk.sock *.core
diff --git a/README.md b/README.md
@@ -11,9 +11,10 @@ because it's a bit of a hack.
To test:
make
-./ltk < test.gui
-./ltk < test_draw.gui
-./test_anim.sh | ./ltk (this requires sleep(1) to support fractional seconds)
+./test.sh
+
+If you click the top button, it should exit. That's all it does now.
+Also read the comment in './test.sh'.
Note: you need to uncomment "COMPATOBJ = strtonum.c" in config.mk
if you're not using OpenBSD.
diff --git a/TODO b/TODO
@@ -0,0 +1,15 @@
+Convert points to pixels for stb rendering (currently, the size between
+pango and stb is completely different).
+
+Random stuff:
+* This is not really a general-purpose GUI toolkit - imagine building
+ a complex GUI with this. Especially things like having to respond
+ directly to mouse movement wouldn't be very efficient due to all the
+ protocol overhead (or would it maybe not be as bad as I think?).
+ One idea would be to have a C API and the socket API. The C program
+ works more or less like a regular GUI program but the GUI mainloop
+ still opens the socket as usual and allows commands to be sent that
+ way. That way, you would get the advantages of both sides - the C
+ program can do the normal GUI stuff, but everything can still be
+ edited on-the-fly, and text can be retrieved (e.g. for a screen-
+ reader).
diff --git a/button.c b/button.c
@@ -154,7 +154,7 @@ ltk_button_change_state(ltk_button *button) {
static void
ltk_button_mouse_release(ltk_button *button, XEvent event) {
- ltk_queue_event(button->widget.window, button->widget.id, "button_click");
+ ltk_queue_event(button->widget.window, LTK_EVENT_BUTTON, button->widget.id, "button_click");
}
static ltk_button *
diff --git a/ltk.c b/ltk.c
@@ -1,646 +0,0 @@
-/*
- * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <time.h>
-#include <sys/stat.h>
-#include <sys/select.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include "util.h"
-#include "khash.h"
-#include "ini.h"
-#include "text.h"
-#include "ltk.h"
-#include "grid.h"
-#include "button.h"
-#include "draw.h"
-
-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 running = 1;
-static char *cmd_input = NULL;
-static char **tokens = NULL;
-static size_t cmd_bufsize = 0;
-static size_t tokens_bufsize = 0;
-static size_t cmd_len = 0;
-static size_t tokens_len = 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_clean_up(ltk_window *window) {
- ltk_destroy_theme(window->theme);
- ltk_destroy_window(window);
- if (tokens) free(tokens);
- if (cmd_input) free(cmd_input);
-}
-
-void
-ltk_quit(ltk_window *window) {
- ltk_clean_up(window);
- running = 0;
-}
-
-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, const char *id, const char *name) {
- struct ltk_event_queue *new = malloc(sizeof(struct ltk_event_queue));
- if (!new) ltk_fatal("Unable to queue event.\n");
- new->id = strdup(id);
- new->name = strdup(name);
- new->next = window->first_event;
- window->first_event = new;
- new->prev = NULL;
- if (!window->last_event)
- window->last_event = new;
-}
-
-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);
- }
-}
-
-/* copied from suckless ii */
-static int
-read_cmdline(int fd) {
- char c = '\0';
- char *tmp = NULL;
- cmd_len = 0;
- do {
- if (cmd_len >= cmd_bufsize) {
- cmd_bufsize = !cmd_bufsize ? 1 : cmd_bufsize;
- tmp = realloc(cmd_input, cmd_bufsize * 2 * sizeof(char));
- if (!tmp) ltk_fatal("Out of memory while reading command.\n");
- cmd_input = tmp;
- cmd_bufsize *= 2;
- }
- if (read(fd, &c, sizeof(char)) != sizeof(char))
- return -1;
- cmd_input[cmd_len++] = c;
- } while (c != '\n');
- cmd_input[cmd_len - 1] = '\0'; /* eliminates '\n' */
- return 0;
-}
-
-/* FIXME: turn this into something that doesn't, you know,
- explode at the slightest touch... */
-static int
-tokenize(void) {
- char *c;
- tokens_len = 0;
- if (!cmd_input || cmd_input[0] == '\0') return 0;
- for (c = cmd_input; *c == ' '; c++)
- ;
- size_t cur_tok = 0;
- int in_str = 0;
- char **tmp;
- if (tokens_bufsize)
- tokens[0] = c;
- while (*c != '\0') {
- if (cur_tok >= tokens_bufsize) {
- if (!tokens_bufsize)
- tokens_bufsize = 1;
- tmp = realloc(tokens, tokens_bufsize * 2 * sizeof(char *));
- if (!tmp) ltk_fatal("Out of memory while parsing command.\n");
- tokens = tmp;
- tokens[cur_tok] = c;
- tokens_bufsize *= 2;
- }
- if (*c == '"') {
- if (!in_str)
- tokens[cur_tok] = c + 1;
- in_str = !in_str;
- *c = '\0';
- } else if (*c == ' ' && !in_str) {
- *c = '\0';
- cur_tok++;
- tokens[cur_tok] = c + 1;
- }
- c++;
- }
- tokens_len = cur_tok + 1;
-}
-
-static void
-proc_cmds(ltk_window *window) {
- if (!tokenize()) return;
- if (!tokens_len) return;
- if (strcmp(tokens[0], "grid") == 0) {
- ltk_grid_cmd(window, tokens, tokens_len);
- } else if (strcmp(tokens[0], "button") == 0) {
- ltk_button_cmd(window, tokens, tokens_len);
- } else if (strcmp(tokens[0], "set-root-widget") == 0) {
- ltk_set_root_widget_cmd(window, tokens, tokens_len);
- } else if (strcmp(tokens[0], "draw") == 0) {
- ltk_draw_cmd(window, tokens, tokens_len);
- } else if (strcmp(tokens[0], "quit") == 0) {
- ltk_quit(window);
- } else {
- (void)fprintf(stderr, "Invalid command.\n");
- }
-}
-
-int
-ltk_mainloop(ltk_window *window) {
- XEvent event;
- struct stat st;
- struct timespec tick;
- struct timeval tv;
- fd_set rfds;
- int fd_in = fileno(stdin);
- int retval;
- tick.tv_sec = 0;
- tick.tv_nsec = 10000;
- tv.tv_sec = 0;
- tv.tv_usec = 0;
-
- tokens = malloc(10 * sizeof(char *));
- cmd_input = malloc(200 * sizeof(char));
- if (!tokens || !cmd_input) ltk_fatal("Out of memory.\n");
- tokens_bufsize = 10;
- cmd_bufsize = 200;
-
- while (running) {
- FD_ZERO(&rfds);
- FD_SET(fd_in, &rfds);
- retval = select(fd_in + 1, &rfds, NULL, NULL, &tv);
- while (XPending(window->dpy)) {
- XNextEvent(window->dpy, &event);
- ltk_handle_event(window, event);
- }
- if (retval > 0 && !read_cmdline(fd_in)) {
- proc_cmds(window);
- } else 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 {
- printf("%s %s\n", cur->id, cur->name);
- free(cur->id);
- free(cur->name);
- last = cur;
- cur = cur->prev;
- free(last);
- } while (cur);
- window->first_event = window->last_event = NULL;
- }
- /* yes, this should be improved */
- nanosleep(&tick, NULL);
-
- }
-}
-
-void
-ltk_redraw_window(ltk_window *window) {
- ltk_widget *ptr;
- if (!window) return;
- if (window->dirty_rect.x >= window->rect.w) return;
- if (window->dirty_rect.y >= window->rect.h) return;
- if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
- window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
- if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
- window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
- XClearArea(window->dpy, window->xwindow, window->dirty_rect.x, window->dirty_rect.y, window->dirty_rect.w, window->dirty_rect.h, False);
- if (!window->root_widget) return;
- /* FIXME: actually respect the dirty rect... */
- ptr = window->root_widget;
- ptr->draw(ptr);
-}
-
-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)
- ltk_fatal("Not enough memory left for window!\n");
-
- window->dpy = XOpenDisplay(NULL);
- window->screen = DefaultScreen(window->dpy);
- window->cm = DefaultColormap(window->dpy, window->screen);
- /* FIXME: validate theme properly */
- ltk_load_theme(window, theme_path);
- window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False);
-
- ltk_window_theme *wtheme = window->theme->window;
- window->xwindow =
- XCreateSimpleWindow(window->dpy, DefaultRootWindow(window->dpy), x, y,
- w, h, wtheme->border_width,
- wtheme->fg.pixel, wtheme->bg.pixel);
- window->gc = XCreateGC(window->dpy, window->xwindow, 0, 0);
- XSetForeground(window->dpy, window->gc, wtheme->fg.pixel);
- XSetBackground(window->dpy, window->gc, wtheme->bg.pixel);
- XSetStandardProperties(window->dpy, window->xwindow, title, NULL, None,
- NULL, 0, NULL);
- XSetWMProtocols(window->dpy, window->xwindow, &window->wm_delete_msg, 1);
- window->root_widget = NULL;
-
- ltk_init_text(wtheme->font, window->dpy, window->screen, window->cm);
-
- window->other_event = <k_window_other_event;
-
- window->rect.w = 0;
- window->rect.h = 0;
- window->rect.x = 0;
- window->rect.y = 0;
- window->dirty_rect.w = 0;
- window->dirty_rect.h = 0;
- window->dirty_rect.x = 0;
- window->dirty_rect.y = 0;
-
- window->widget_hash = kh_init(widget);
-
- XClearWindow(window->dpy, window->xwindow);
- XMapRaised(window->dpy, window->xwindow);
- XSelectInput(window->dpy, window->xwindow,
- ExposureMask | KeyPressMask | KeyReleaseMask |
- ButtonPressMask | ButtonReleaseMask |
- StructureNotifyMask | PointerMotionMask);
-
- return window;
-}
-
-void
-ltk_destroy_window(ltk_window *window) {
- khint_t k;
- ltk_widget *ptr;
- XDestroyWindow(window->dpy, window->xwindow);
- for (k = kh_begin(window->widget_hash); k != kh_end(window->widget_hash); k++) {
- if (kh_exist(window->widget_hash, k)) {
- ptr = kh_value(window->widget_hash, k);
- ptr->destroy(ptr, 1);
- }
- }
- kh_destroy(widget, window->widget_hash);
- ltk_cleanup_text();
- XCloseDisplay(window->dpy);
- free(window);
-}
-
-void
-ltk_window_other_event(ltk_window *window, XEvent event) {
- ltk_widget *ptr = window->root_widget;
- int retval = 0;
- if (event.type == ConfigureNotify) {
- unsigned int w, h;
- w = event.xconfigure.width;
- h = event.xconfigure.height;
- int orig_w = window->rect.w;
- int orig_h = window->rect.h;
- if (orig_w != w || orig_h != h) {
- window->rect.w = w;
- window->rect.h = h;
- if (ptr && ptr->resize) {
- ptr->rect.w = w;
- ptr->rect.h = h;
- ptr->resize(ptr, orig_w, orig_h);
- }
- }
- } else if (event.type == Expose && event.xexpose.count == 0) {
- ltk_rect r;
- r.x = event.xexpose.x;
- r.y = event.xexpose.y;
- r.w = event.xexpose.width;
- r.h = event.xexpose.height;
- 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 */
- }
-}
-
-void
-ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
- /* FIXME: A whole lot more error checking! */
- if (strcmp(prop, "border_width") == 0) {
- window->theme->window->border_width = atoi(value);
- } else if (strcmp(prop, "bg") == 0) {
- ltk_create_xcolor(window, value, &window->theme->window->bg);
- } else if (strcmp(prop, "fg") == 0) {
- ltk_create_xcolor(window, value, &window->theme->window->fg);
- } else if (strcmp(prop, "font") == 0) {
- window->theme->window->font = strdup(value);
- } else if (strcmp(prop, "font_size") == 0) {
- window->theme->window->font_size = atoi(value);
- }
-}
-
-int
-ltk_ini_handler(ltk_window *window, const char *widget, const char *prop, const char *value) {
- if (strcmp(widget, "window") == 0) {
- ltk_window_ini_handler(window, prop, value);
- } else if (strcmp(widget, "button") == 0) {
- ltk_button_ini_handler(window, prop, value);
- }
-}
-
-static void
-ltk_load_theme(ltk_window *window, const char *path) {
- window->theme = malloc(sizeof(ltk_theme));
- if (!window->theme) ltk_fatal("Unable to allocate memory for theme.\n");
- window->theme->window = malloc(sizeof(ltk_window_theme));
- 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);
- }
-}
-
-static void
-ltk_destroy_theme(ltk_theme *theme) {
- free(theme->button);
- free(theme->window);
- free(theme);
-}
-
-int
-ltk_collide_rect(ltk_rect rect, int x, int y) {
- return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y
- && (rect.y + rect.h) >= y);
-}
-
-void
-ltk_window_remove_active_widget(ltk_window *window) {
- ltk_widget *widget = window->active_widget;
- if (!widget) return;
- while (widget) {
- widget->state = LTK_NORMAL;
- widget->active_widget = NULL;
- if (widget->change_state)
- widget->change_state(widget);
- if (widget->needs_redraw)
- ltk_window_invalidate_rect(window, widget->rect);
- widget = widget->parent;
- }
- window->active_widget = NULL;
-}
-
-void
-ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
- window->active_widget = widget;
- ltk_widget *parent = widget->parent;
- widget->state = LTK_ACTIVE;
- while (parent) {
- widget->state = LTK_ACTIVE;
- parent->active_widget = widget;
- widget = parent;
- parent = widget->parent;
- }
-}
-
-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) {
- widget->id = strdup(id);
- widget->window = window;
- widget->active_widget = NULL;
- widget->parent = NULL;
- widget->type = type;
-
- widget->key_press = NULL;
- widget->key_release = NULL;
- widget->mouse_press = NULL;
- widget->mouse_release = NULL;
- widget->motion_notify = NULL;
- widget->mouse_enter = NULL;
- widget->mouse_leave = NULL;
-
- widget->resize = NULL;
- widget->draw = draw;
- widget->change_state = change_state;
- widget->destroy = destroy;
-
- widget->needs_redraw = needs_redraw;
- widget->state = LTK_NORMAL;
- widget->row = 0;
- widget->rect.x = 0;
- widget->rect.y = 0;
- widget->rect.w = 100;
- widget->rect.h = 100;
-
- widget->row = NULL;
- widget->column = NULL;
- widget->row_span = NULL;
- widget->column_span = NULL;
- widget->sticky = 0;
-}
-
-void
-ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
- if (!widget || widget->state == LTK_DISABLED)
- return;
- if (event.xbutton.button == 1) {
- ltk_widget *parent = widget->parent;
- widget->state = LTK_PRESSED;
- if (widget->change_state)
- widget->change_state(widget);
- if (widget->needs_redraw)
- ltk_window_invalidate_rect(widget->window, widget->rect);
- }
- if (widget->mouse_press) {
- widget->mouse_press(widget, event);
- }
-}
-
-void
-ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
- if (!widget || widget->state == LTK_DISABLED)
- return;
- if (widget->state == LTK_PRESSED) {
- widget->state = LTK_ACTIVE;
- if (widget->change_state)
- widget->change_state(widget);
- if (widget->needs_redraw)
- ltk_window_invalidate_rect(widget->window, widget->rect);
- }
- if (widget->mouse_release) {
- widget->mouse_release(widget, event);
- }
-}
-
-void
-ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
- if (!widget) return;
- short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
- if ((widget->state == LTK_NORMAL) && !pressed) {
- widget->state = LTK_ACTIVE;
- if (widget->change_state)
- widget->change_state(widget);
- if (widget->mouse_enter)
- widget->mouse_enter(widget, event);
- /* FIXME: do this properly */
- if (widget->window->active_widget != widget && !widget->motion_notify) {
- ltk_window_remove_active_widget(widget->window);
- ltk_window_set_active_widget(widget->window, widget);
- }
- if (widget->needs_redraw)
- ltk_window_invalidate_rect(widget->window, widget->rect);
- }
- if (widget->motion_notify)
- widget->motion_notify(widget, event);
-}
-
-void
-ltk_handle_event(ltk_window *window, XEvent event) {
- ltk_widget *root_widget = window->root_widget;
- switch (event.type) {
- case KeyPress:
- break;
- case KeyRelease:
- break;
- case ButtonPress:
- if (root_widget)
- ltk_widget_mouse_press_event(root_widget, event);
- break;
- case ButtonRelease:
- if (root_widget)
- ltk_widget_mouse_release_event(root_widget, event);
- break;
- case MotionNotify:
- if (root_widget)
- ltk_widget_motion_notify_event(root_widget, event);
- break;
- default:
- if (window->other_event)
- window->other_event(window, event);
- }
-}
-
-int
-ltk_check_widget_id_free(ltk_window *window, const char *id, const char *caller) {
- khint_t k;
- k = kh_get(widget, window->widget_hash, id);
- if (k != kh_end(window->widget_hash)) {
- (void)fprintf(stderr, "%s: Widget \"%s\" already exists.\n", caller, id);
- return 0;
- }
- return 1;
-}
-
-ltk_widget *
-ltk_get_widget(ltk_window *window, const char *id, ltk_widget_type type,
- const char *caller) {
- khint_t k;
- ltk_widget *widget;
- k = kh_get(widget, window->widget_hash, id);
- if (k == kh_end(window->widget_hash)) {
- (void)fprintf(stderr, "%s: Widget \"%s\" doesn't exist.\n", caller, id);
- return NULL;
- }
- widget = kh_value(window->widget_hash, k);
- if (type != LTK_WIDGET && widget->type != type) {
- (void)fprintf(stderr, "%s: Widget \"%s\" has wrong type.\n", caller, id);
- return NULL;
- }
- return widget;
-}
-
-void
-ltk_set_widget(ltk_window *window, ltk_widget *widget, const char *id) {
- int ret;
- khint_t k;
- /* apparently, khash requires the string to stay accessible */
- char *tmp = strdup(id);
- k = kh_put(widget, window->widget_hash, tmp, &ret);
- kh_value(window->widget_hash, k) = widget;
-}
-
-void
-ltk_remove_widget(ltk_window *window, const char *id) {
- khint_t k;
- k = kh_get(widget, window->widget_hash, id);
- if (k != kh_end(window->widget_hash)) {
- kh_del(widget, window->widget_hash, k);
- }
-}
-
-int main(int argc, char *argv[]) {
- ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500);
- ltk_mainloop(window);
-}
diff --git a/ltk.h b/ltk.h
@@ -28,6 +28,12 @@
#include "khash.h"
+typedef enum {
+ LTK_EVENT_RESIZE = 1 << 0,
+ LTK_EVENT_BUTTON = 1 << 1,
+ LTK_EVENT_KEY = 1 << 2
+} ltk_event_type;
+
typedef struct {
int x;
int y;
@@ -59,10 +65,10 @@ typedef enum {
typedef struct ltk_window ltk_window;
typedef struct ltk_widget {
+ ltk_rect rect;
ltk_window *window;
struct ltk_widget *active_widget;
struct ltk_widget *parent;
- ltk_widget_type type;
char *id;
void (*key_press) (void *, XEvent event);
@@ -78,14 +84,14 @@ typedef struct ltk_widget {
void (*change_state) (void *);
void (*destroy) (void *, int);
- ltk_rect rect;
- unsigned int row;
- unsigned int column;
- unsigned int row_span;
- unsigned int column_span;
- unsigned int needs_redraw;
+ ltk_widget_type type;
ltk_widget_state state;
- unsigned short sticky;
+ unsigned int sticky;
+ unsigned short row;
+ unsigned short column;
+ unsigned short row_span;
+ unsigned short column_span;
+ unsigned char needs_redraw;
} ltk_widget;
typedef struct {
@@ -104,8 +110,8 @@ typedef struct {
} ltk_theme;
struct ltk_event_queue {
- char *id;
- char *name;
+ ltk_event_type event_type;
+ char *data;
struct ltk_event_queue *prev;
struct ltk_event_queue *next;
};
@@ -121,7 +127,7 @@ typedef struct ltk_window {
Window xwindow;
ltk_widget *root_widget;
ltk_widget *active_widget;
- int (*other_event) (ltk_window *, XEvent event);
+ void (*other_event) (ltk_window *, XEvent event);
ltk_rect rect;
ltk_theme *theme;
ltk_rect dirty_rect;
@@ -136,7 +142,7 @@ 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, const char *id, const char *name);
+void ltk_queue_event(ltk_window *window, ltk_event_type type, const char *id, const char *data);
int ltk_mainloop(ltk_window *window);
diff --git a/ltkc.c b/ltkc.c
@@ -0,0 +1,154 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2020 lumidify <nobody@lumidify.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.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;
+ int in_len;
+ int in_alloc;
+ char *out_buffer;
+ int out_len;
+ int out_alloc;
+} io_buffers;
+
+int main(int argc, char *argv[]) {
+ int sockfd, maxrfd, maxwfd;
+ int infd = fileno(stdin);
+ int outfd = fileno(stdout);
+ struct sockaddr_un un;
+ fd_set rfds, wfds, rallfds, wallfds;
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 15;
+
+ if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ perror("Socket error");
+ return -1;
+ }
+ 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) {
+ perror("Socket error");
+ return -2;
+ }
+
+ io_buffers.in_buffer = malloc(BLK_SIZE);
+ if (!io_buffers.in_buffer)
+ return 1;
+ io_buffers.in_alloc = BLK_SIZE;
+
+ io_buffers.out_buffer = malloc(BLK_SIZE);
+ if (!io_buffers.out_buffer)
+ return 1;
+ io_buffers.out_alloc = BLK_SIZE;
+
+ FD_ZERO(&rallfds);
+ FD_ZERO(&wallfds);
+
+ FD_SET(sockfd, &rallfds);
+ FD_SET(infd, &rallfds);
+ FD_SET(sockfd, &wallfds);
+ FD_SET(outfd, &wallfds);
+ maxrfd = sockfd > infd ? sockfd : infd;
+ 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)
+ break;
+ rfds = rallfds;
+ wfds = wallfds;
+ /* Separate this because the writing fds are *usually* always ready,
+ 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);
+ if (nread < 0) {
+ return 2;
+ } else if (nread == 0) {
+ FD_CLR(sockfd, &rallfds);
+ FD_CLR(sockfd, &wallfds);
+ } else {
+ 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);
+ if (nread < 0) {
+ return 2;
+ } else if (nread == 0) {
+ FD_CLR(infd, &rallfds);
+ } else {
+ 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 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);
+ 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 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);
+ io_buffers.out_len -= nwritten;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/ltkd.c b/ltkd.c
@@ -0,0 +1,912 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include "util.h"
+#include "khash.h"
+#include "ini.h"
+#include "text.h"
+#include "ltk.h"
+#include "grid.h"
+#include "button.h"
+#include "draw.h"
+
+#define MAX_SOCK_CONNS 20
+#define READ_BLK_SIZE 128
+#define WRITE_BLK_SIZE 128
+
+struct token_list {
+ char **tokens;
+ int num_tokens;
+ int num_alloc;
+};
+
+static struct ltk_sock_info {
+ int fd; /* file descriptor for socket connection */
+ int event_mask; /* events to send to socket */
+ char *read; /* text read from socket */
+ int read_len; /* length of text in read buffer */
+ int read_alloc; /* size of read buffer */
+ char *to_write; /* text to be written to socket */
+ int write_len; /* length of text in write buffer */
+ int write_cur; /* length of text already written */
+ int write_alloc; /* size of write buffer */
+ /* stuff for tokenizing */
+ int in_token; /* last read char is inside token */
+ int offset; /* offset from removing backslashes */
+ int in_str; /* last read char is inside string */
+ int read_cur; /* length of text already tokenized */
+ int bs; /* last char was non-escaped backslash */
+ struct token_list tokens; /* current tokens */
+} sockets[MAX_SOCK_CONNS];
+
+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 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;
+
+errout:
+ err = errno;
+ close(fd);
+ errno = err;
+ return rval;
+}
+
+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 rretval, wretval;
+ int clifd;
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 10;
+
+ /* 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 */
+ /* initialize these just because I'm paranoid */
+ sockets[i].read = NULL;
+ sockets[i].to_write = NULL;
+ sockets[i].tokens.tokens = NULL;
+ }
+
+ 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 */
+ }
+
+ FD_SET(listenfd, &rallfds);
+ maxfd = listenfd;
+ maxi = -1;
+
+ while (running) {
+ rfds = rallfds;
+ wfds = wallfds;
+ /* separate these because the writing fds are usually
+ always ready for writing */
+ rretval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
+ wretval = select(maxfd + 1, NULL, &wfds, NULL, &tv);
+ while (XPending(window->dpy)) {
+ 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 (FD_ISSET(listenfd, &rfds)) {
+ if ((clifd = accept_sock(listenfd)) < 0) {
+ fprintf(stderr, "Error accepting socket connection\n");
+ exit(1); /* FIXME: proper error handling */
+ }
+ int i = add_client(clifd);
+ FD_SET(clifd, &rallfds);
+ FD_SET(clifd, &wallfds);
+ if (clifd > maxfd)
+ maxfd = clifd;
+ if (i > maxi)
+ maxi = i;
+ continue;
+ }
+ for (int i = 0; i <= maxi; i++) {
+ if ((clifd = sockets[i].fd) < 0)
+ continue;
+ if (FD_ISSET(clifd, &rfds)) {
+ if (read_sock(&sockets[i]) == 0) {
+ fprintf(stderr, "Closed socket fd %d\n", clifd); /* FIXME */
+ FD_CLR(clifd, &rallfds);
+ FD_CLR(clifd, &wallfds);
+ sockets[i].fd = -1;
+ close(clifd);
+ } else {
+ process_commands(window, &sockets[i]);
+ }
+ }
+ if (FD_ISSET(clifd, &wfds)) {
+ write_sock(&sockets[i]);
+ }
+ }
+ }
+ 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++) {
+ 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 */
+ }
+ }
+ free(cur->data);
+ last = cur;
+ cur = cur->prev;
+ free(last);
+ } while (cur);
+ window->first_event = window->last_event = NULL;
+ }
+
+ }
+ for (int i = 0; i < MAX_SOCK_CONNS; i++) {
+ if (sockets[i].read)
+ free(sockets[i].read);
+ if (sockets[i].to_write)
+ free(sockets[i].to_write);
+ if (sockets[i].tokens.tokens)
+ free(sockets[i].tokens.tokens);
+ }
+
+ unlink("ltk.sock");
+
+ return 0;
+}
+
+void
+ltk_redraw_window(ltk_window *window) {
+ ltk_widget *ptr;
+ if (!window) return;
+ if (window->dirty_rect.x >= window->rect.w) return;
+ if (window->dirty_rect.y >= window->rect.h) return;
+ if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
+ window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
+ if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
+ window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
+ XClearArea(window->dpy, window->xwindow, window->dirty_rect.x, window->dirty_rect.y, window->dirty_rect.w, window->dirty_rect.h, False);
+ if (!window->root_widget) return;
+ /* FIXME: actually respect the dirty rect... */
+ ptr = window->root_widget;
+ ptr->draw(ptr);
+}
+
+void
+ltk_window_other_event(ltk_window *window, XEvent event) {
+ ltk_widget *ptr = window->root_widget;
+ if (event.type == ConfigureNotify) {
+ unsigned int w, h;
+ w = event.xconfigure.width;
+ h = event.xconfigure.height;
+ int orig_w = window->rect.w;
+ int orig_h = window->rect.h;
+ if (orig_w != w || orig_h != h) {
+ window->rect.w = w;
+ window->rect.h = h;
+ if (ptr && ptr->resize) {
+ ptr->rect.w = w;
+ ptr->rect.h = h;
+ ptr->resize(ptr, orig_w, orig_h);
+ }
+ }
+ } else if (event.type == Expose && event.xexpose.count == 0) {
+ ltk_rect r;
+ r.x = event.xexpose.x;
+ r.y = event.xexpose.y;
+ r.w = event.xexpose.width;
+ r.h = event.xexpose.height;
+ 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_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)
+ ltk_fatal("Not enough memory left for window!\n");
+
+ window->dpy = XOpenDisplay(NULL);
+ window->screen = DefaultScreen(window->dpy);
+ window->cm = DefaultColormap(window->dpy, window->screen);
+ /* FIXME: validate theme properly */
+ ltk_load_theme(window, theme_path);
+ window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False);
+
+ ltk_window_theme *wtheme = window->theme->window;
+ window->xwindow =
+ XCreateSimpleWindow(window->dpy, DefaultRootWindow(window->dpy), x, y,
+ w, h, wtheme->border_width,
+ wtheme->fg.pixel, wtheme->bg.pixel);
+ window->gc = XCreateGC(window->dpy, window->xwindow, 0, 0);
+ XSetForeground(window->dpy, window->gc, wtheme->fg.pixel);
+ XSetBackground(window->dpy, window->gc, wtheme->bg.pixel);
+ XSetStandardProperties(window->dpy, window->xwindow, title, NULL, None,
+ NULL, 0, NULL);
+ XSetWMProtocols(window->dpy, window->xwindow, &window->wm_delete_msg, 1);
+ window->root_widget = NULL;
+
+ ltk_init_text(wtheme->font, window->dpy, window->screen, window->cm);
+
+ window->other_event = <k_window_other_event;
+
+ window->rect.w = 0;
+ window->rect.h = 0;
+ window->rect.x = 0;
+ window->rect.y = 0;
+ window->dirty_rect.w = 0;
+ window->dirty_rect.h = 0;
+ window->dirty_rect.x = 0;
+ window->dirty_rect.y = 0;
+
+ window->widget_hash = kh_init(widget);
+
+ XClearWindow(window->dpy, window->xwindow);
+ XMapRaised(window->dpy, window->xwindow);
+ XSelectInput(window->dpy, window->xwindow,
+ ExposureMask | KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask |
+ StructureNotifyMask | PointerMotionMask);
+
+ return window;
+}
+
+void
+ltk_destroy_window(ltk_window *window) {
+ khint_t k;
+ ltk_widget *ptr;
+ XDestroyWindow(window->dpy, window->xwindow);
+ for (k = kh_begin(window->widget_hash); k != kh_end(window->widget_hash); k++) {
+ if (kh_exist(window->widget_hash, k)) {
+ ptr = kh_value(window->widget_hash, k);
+ ptr->destroy(ptr, 1);
+ }
+ }
+ kh_destroy(widget, window->widget_hash);
+ ltk_cleanup_text();
+ XCloseDisplay(window->dpy);
+ free(window);
+}
+
+void
+ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
+ /* FIXME: A whole lot more error checking! */
+ if (strcmp(prop, "border_width") == 0) {
+ window->theme->window->border_width = atoi(value);
+ } else if (strcmp(prop, "bg") == 0) {
+ ltk_create_xcolor(window, value, &window->theme->window->bg);
+ } else if (strcmp(prop, "fg") == 0) {
+ ltk_create_xcolor(window, value, &window->theme->window->fg);
+ } else if (strcmp(prop, "font") == 0) {
+ window->theme->window->font = strdup(value);
+ } else if (strcmp(prop, "font_size") == 0) {
+ window->theme->window->font_size = atoi(value);
+ }
+}
+
+int
+ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value) {
+ if (strcmp(widget, "window") == 0) {
+ ltk_window_ini_handler(window, prop, value);
+ } else if (strcmp(widget, "button") == 0) {
+ ltk_button_ini_handler(window, prop, value);
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+static void
+ltk_load_theme(ltk_window *window, const char *path) {
+ window->theme = malloc(sizeof(ltk_theme));
+ if (!window->theme) ltk_fatal("Unable to allocate memory for theme.\n");
+ window->theme->window = malloc(sizeof(ltk_window_theme));
+ 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);
+ }
+}
+
+static void
+ltk_destroy_theme(ltk_theme *theme) {
+ free(theme->button);
+ free(theme->window);
+ free(theme);
+}
+
+int
+ltk_collide_rect(ltk_rect rect, int x, int y) {
+ return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y
+ && (rect.y + rect.h) >= y);
+}
+
+void
+ltk_window_remove_active_widget(ltk_window *window) {
+ ltk_widget *widget = window->active_widget;
+ if (!widget) return;
+ while (widget) {
+ widget->state = LTK_NORMAL;
+ widget->active_widget = NULL;
+ if (widget->change_state)
+ widget->change_state(widget);
+ if (widget->needs_redraw)
+ ltk_window_invalidate_rect(window, widget->rect);
+ widget = widget->parent;
+ }
+ window->active_widget = NULL;
+}
+
+void
+ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
+ window->active_widget = widget;
+ ltk_widget *parent = widget->parent;
+ widget->state = LTK_ACTIVE;
+ while (parent) {
+ widget->state = LTK_ACTIVE;
+ parent->active_widget = widget;
+ widget = parent;
+ parent = widget->parent;
+ }
+}
+
+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) {
+ widget->id = strdup(id);
+ widget->window = window;
+ widget->active_widget = NULL;
+ widget->parent = NULL;
+ widget->type = type;
+
+ widget->key_press = NULL;
+ widget->key_release = NULL;
+ widget->mouse_press = NULL;
+ widget->mouse_release = NULL;
+ widget->motion_notify = NULL;
+ widget->mouse_enter = NULL;
+ widget->mouse_leave = NULL;
+
+ widget->resize = NULL;
+ widget->draw = draw;
+ widget->change_state = change_state;
+ widget->destroy = destroy;
+
+ widget->needs_redraw = needs_redraw;
+ widget->state = LTK_NORMAL;
+ widget->row = 0;
+ widget->rect.x = 0;
+ widget->rect.y = 0;
+ widget->rect.w = 100;
+ widget->rect.h = 100;
+
+ widget->row = 0;
+ widget->column = 0;
+ widget->row_span = 0;
+ widget->column_span = 0;
+ widget->sticky = 0;
+}
+
+void
+ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
+ if (!widget || widget->state == LTK_DISABLED)
+ return;
+ if (event.xbutton.button == 1) {
+ /* ltk_widget *parent = widget->parent; FIXME */
+ widget->state = LTK_PRESSED;
+ if (widget->change_state)
+ widget->change_state(widget);
+ if (widget->needs_redraw)
+ ltk_window_invalidate_rect(widget->window, widget->rect);
+ }
+ if (widget->mouse_press) {
+ widget->mouse_press(widget, event);
+ }
+}
+
+void
+ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
+ if (!widget || widget->state == LTK_DISABLED)
+ return;
+ if (widget->state == LTK_PRESSED) {
+ widget->state = LTK_ACTIVE;
+ if (widget->change_state)
+ widget->change_state(widget);
+ if (widget->needs_redraw)
+ ltk_window_invalidate_rect(widget->window, widget->rect);
+ }
+ if (widget->mouse_release) {
+ widget->mouse_release(widget, event);
+ }
+}
+
+void
+ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
+ if (!widget) return;
+ short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
+ if ((widget->state == LTK_NORMAL) && !pressed) {
+ widget->state = LTK_ACTIVE;
+ if (widget->change_state)
+ widget->change_state(widget);
+ if (widget->mouse_enter)
+ widget->mouse_enter(widget, event);
+ /* FIXME: do this properly */
+ if (widget->window->active_widget != widget && !widget->motion_notify) {
+ ltk_window_remove_active_widget(widget->window);
+ ltk_window_set_active_widget(widget->window, widget);
+ }
+ if (widget->needs_redraw)
+ ltk_window_invalidate_rect(widget->window, widget->rect);
+ }
+ if (widget->motion_notify)
+ widget->motion_notify(widget, event);
+}
+
+void
+ltk_handle_event(ltk_window *window, XEvent event) {
+ ltk_widget *root_widget = window->root_widget;
+ switch (event.type) {
+ case KeyPress:
+ break;
+ case KeyRelease:
+ break;
+ case ButtonPress:
+ if (root_widget)
+ ltk_widget_mouse_press_event(root_widget, event);
+ break;
+ case ButtonRelease:
+ if (root_widget)
+ ltk_widget_mouse_release_event(root_widget, event);
+ break;
+ case MotionNotify:
+ if (root_widget)
+ ltk_widget_motion_notify_event(root_widget, event);
+ break;
+ default:
+ if (window->other_event)
+ window->other_event(window, event);
+ }
+}
+
+int
+ltk_check_widget_id_free(ltk_window *window, const char *id, const char *caller) {
+ khint_t k;
+ k = kh_get(widget, window->widget_hash, id);
+ if (k != kh_end(window->widget_hash)) {
+ (void)fprintf(stderr, "%s: Widget \"%s\" already exists.\n", caller, id);
+ return 0;
+ }
+ return 1;
+}
+
+ltk_widget *
+ltk_get_widget(ltk_window *window, const char *id, ltk_widget_type type,
+ const char *caller) {
+ khint_t k;
+ ltk_widget *widget;
+ k = kh_get(widget, window->widget_hash, id);
+ if (k == kh_end(window->widget_hash)) {
+ (void)fprintf(stderr, "%s: Widget \"%s\" doesn't exist.\n", caller, id);
+ return NULL;
+ }
+ widget = kh_value(window->widget_hash, k);
+ if (type != LTK_WIDGET && widget->type != type) {
+ (void)fprintf(stderr, "%s: Widget \"%s\" has wrong type.\n", caller, id);
+ return NULL;
+ }
+ return widget;
+}
+
+void
+ltk_set_widget(ltk_window *window, ltk_widget *widget, const char *id) {
+ int ret;
+ khint_t k;
+ /* apparently, khash requires the string to stay accessible */
+ char *tmp = strdup(id);
+ k = kh_put(widget, window->widget_hash, tmp, &ret);
+ kh_value(window->widget_hash, k) = widget;
+}
+
+void
+ltk_remove_widget(ltk_window *window, const char *id) {
+ khint_t k;
+ k = kh_get(widget, window->widget_hash, id);
+ if (k != kh_end(window->widget_hash)) {
+ kh_del(widget, window->widget_hash, k);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500);
+ return ltk_mainloop(window);
+}
diff --git a/socket_format.txt b/socket_format.txt
@@ -1,5 +1,5 @@
-Note: This is not implemented yet; it is just here to collect
-my thoughts while I keep working.
+Note: This is not fully implemented yet; it is just here to
+collect my thoughts while I keep working.
<widget type> <widget id> <command> <args>
> grid grd1 create 2 2
diff --git a/test.sh b/test.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# This is very hacky.
+#
+# 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
+
+cat test.gui | ./ltkc | while read cmd
+do
+ case "$cmd" in
+ "btn1 button_click") echo "quit"
+ ;;
+ esac
+done | ./ltkc
diff --git a/test_anim.sh b/test_anim.sh
@@ -1,29 +0,0 @@
-#!/bin/sh
-
-echo "grid grd1 create 2 2
-grid grd1 set-row-weight 0 1
-grid grd1 set-row-weight 1 1
-grid grd1 set-column-weight 0 1
-grid grd1 set-column-weight 1 1
-set-root-widget grd1
-draw drw1 create 100 100 #fff
-grid grd1 add drw1 0 1 1 1 15
-draw drw1 set-color #000
-"
-
-i=0
-while [ $i -lt 400 ]; do
- j=0
- while [ $j -lt 20 ]; do
- echo "draw drw1 line $((i+j)) $j $((i+j+2)) $((j+2))";
- sleep 0.05
- j=$((j+2))
- done
- j=20
- while [ $j -gt 0 ]; do
- echo "draw drw1 line $((i+40-j)) $j $((i+42-j)) $((j-2))";
- sleep 0.05
- j=$((j-2))
- done
- i=$((i+40));
-done
diff --git a/test_draw.gui b/test_draw.gui
@@ -1,10 +0,0 @@
-grid grd1 create 2 2
-grid grd1 set-row-weight 0 1
-grid grd1 set-row-weight 1 1
-grid grd1 set-column-weight 0 1
-grid grd1 set-column-weight 1 1
-set-root-widget grd1
-draw drw1 create 100 100 #fff
-grid grd1 add drw1 0 1 1 1 15
-draw drw1 set-color #000
-draw drw1 line 0 0 100 100