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 462218a997dc7c37f972e1a0bcf73d96548f8999
parent 25a158327d3d12d3eba052f202278ffa0d5539d5
Author: lumidify <nobody@lumidify.org>
Date:   Wed, 15 Nov 2023 22:51:31 +0100

Add basic image support; add more options to grid

Diffstat:
MMakefile | 17++++++++++++-----
MREADME.md | 3+++
Msrc/.gitignore | 1+
Msrc/box.c | 25++++++++++++++-----------
Msrc/box.h | 3++-
Msrc/button.c | 4++--
Msrc/button.h | 8++------
Msrc/cmd.c | 49++++++++++++++++++++++++++++++++++---------------
Msrc/cmd.h | 31+++++++++++++++++++++++++------
Msrc/config.c | 3++-
Msrc/entry.c | 5+++--
Msrc/entry.h | 8++------
Msrc/err.c | 1+
Msrc/err.h | 9+++++----
Msrc/grid.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/grid.h | 9+++++----
Asrc/image.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/image.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/image_widget.c | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/image_widget.h | 31+++++++++++++++++++++++++++++++
Msrc/label.c | 5+++--
Msrc/label.h | 8++------
Msrc/ltk.h | 9++++++++-
Msrc/ltkc.c | 199+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Asrc/ltkc_img.c | 26++++++++++++++++++++++++++
Msrc/ltkd.c | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/macros.h | 2++
Msrc/menu.c | 22+++++++++++-----------
Msrc/menu.h | 16+++-------------
Msrc/proto_types.h | 22++++++++++++----------
Msrc/text_pango.c | 1+
Msrc/util.c | 11+++++++++++
Msrc/util.h | 2++
Msrc/widget.c | 17+++++++++++++----
Msrc/widget.h | 9++++++---
Mtest.gui | 16++++++++--------
Mtest2.gui | 2+-
Mtest3.gui | 2+-
Mtest3.sh | 2+-
Atestimg.sh | 13+++++++++++++
40 files changed, 941 insertions(+), 319 deletions(-)

diff --git a/Makefile b/Makefile @@ -35,8 +35,8 @@ EXTRA_OBJ = $(EXTRA_OBJ_$(USE_PANGO)) EXTRA_CFLAGS = $(SANITIZE_FLAGS_$(SANITIZE)) $(DEV_CFLAGS_$(DEV)) $(EXTRA_CFLAGS_$(USE_PANGO)) EXTRA_LDFLAGS = $(SANITIZE_FLAGS_$(SANITIZE)) $(DEV_LDFLAGS_$(DEV)) $(EXTRA_LDFLAGS_$(USE_PANGO)) -LTK_CFLAGS = $(EXTRA_CFLAGS) -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -DMEMDEBUG=$(MEMDEBUG) -std=c99 `pkg-config --cflags x11 fontconfig xext xcursor` -D_POSIX_C_SOURCE=200809L -LTK_LDFLAGS = $(EXTRA_LDFLAGS) -lm `pkg-config --libs x11 fontconfig xext xcursor` +LTK_CFLAGS = $(EXTRA_CFLAGS) -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -DMEMDEBUG=$(MEMDEBUG) -std=c99 `pkg-config --cflags x11 fontconfig xext xcursor imlib2` -D_POSIX_C_SOURCE=200809L +LTK_LDFLAGS = $(EXTRA_LDFLAGS) -lm `pkg-config --libs x11 fontconfig xext xcursor imlib2` OBJ = \ src/strtonum.o \ @@ -64,6 +64,8 @@ OBJ = \ src/txtbuf.o \ src/ctrlsel.o \ src/cmd.o \ + src/image.o \ + src/image_widget.o \ $(EXTRA_OBJ) # Note: This could be improved so a change in a header only causes the .c files @@ -103,16 +105,21 @@ HDR = \ src/clipboard.h \ src/txtbuf.h \ src/ctrlsel.h \ - src/cmd.h + src/cmd.h \ + src/image.h \ + src/image_widget.h -all: src/ltkd src/ltkc +all: src/ltkd src/ltkc src/ltkc_img src/ltkd: $(OBJ) $(CC) -o $@ $(OBJ) $(LTK_LDFLAGS) -src/ltkc: src/ltkc.o src/util.o src/memory.o +src/ltkc: src/ltkc.o src/util.o src/memory.o src/txtbuf.o $(CC) -o $@ src/ltkc.o src/util.o src/memory.o src/txtbuf.o $(LTK_LDFLAGS) +src/ltkc_img: src/ltkc_img.o + $(CC) -o $@ src/ltkc_img.o $(LTK_LDFLAGS) + $(OBJ) : $(HDR) .c.o: diff --git a/README.md b/README.md @@ -23,3 +23,6 @@ Note: I know the default theme is butt-ugly at the moment. It is mainly to test things, not to look pretty. Note: Read 'socket_format.txt' for some documentation and open problems. + +Note: The image support currently requires at least Imlib2 version 1.10.0. +I might add compatibility support for older versions at some point. diff --git a/src/.gitignore b/src/.gitignore @@ -1,4 +1,5 @@ ltkd ltkc +ltkc_img *.o *.core diff --git a/src/box.c b/src/box.c @@ -14,6 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/* FIXME: implement other sticky options now supported by grid */ + #include <stdio.h> #include <stdlib.h> #include <stdarg.h> @@ -36,8 +38,7 @@ static ltk_box *ltk_box_create(ltk_window *window, const char *id, ltk_orientati static void ltk_box_destroy(ltk_widget *self, int shallow); static void ltk_recalculate_box(ltk_widget *self); static void ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget); -/* FIXME: Why is sticky unsigned short? */ -static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, ltk_error *err); +static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, ltk_sticky_mask sticky, ltk_error *err); static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err); /* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, ltk_error *err); */ static void ltk_box_scroll(ltk_widget *self); @@ -90,26 +91,26 @@ static struct ltk_widget_vtable vtable = { static int ltk_box_cmd_add( ltk_window *window, ltk_box *box, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); static int ltk_box_cmd_remove( ltk_window *window, ltk_box *box, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); /* static int ltk_box_cmd_clear ltk_window *window, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); */ static int ltk_box_cmd_create( ltk_window *window, ltk_box *box, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); @@ -117,6 +118,7 @@ static void ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { ltk_box *box = (ltk_box *)self; ltk_widget *ptr; + /* FIXME: clip out scrollbar */ ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip); for (size_t i = 0; i < box->num_widgets; i++) { ptr = box->widgets[i]; @@ -195,6 +197,7 @@ ltk_box_destroy(ltk_widget *self, int shallow) { /* FIXME: The widget positions are set with the old scrollbar->cur_pos, before the virtual_size is set - this can cause problems when a widget changes its size (in the scrolled direction) when resized. */ +/* FIXME: avoid complete recalculation when just scrolling (only position updated) */ static void ltk_recalculate_box(ltk_widget *self) { ltk_box *box = (ltk_box *)self; @@ -293,7 +296,7 @@ ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) { } static int -ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, ltk_error *err) { +ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, ltk_sticky_mask sticky, ltk_error *err) { if (widget->parent) { err->type = ERR_WIDGET_IN_CONTAINER; return 1; @@ -495,7 +498,7 @@ static int ltk_box_cmd_add( ltk_window *window, ltk_box *box, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -516,7 +519,7 @@ static int ltk_box_cmd_remove( ltk_window *window, ltk_box *box, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -536,7 +539,7 @@ static int ltk_box_cmd_create( ltk_window *window, ltk_box *box_unneeded, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)box_unneeded; @@ -561,7 +564,7 @@ ltk_box_cmd_create( static struct box_cmd { char *name; - int (*func)(ltk_window *, ltk_box *, char **, size_t, ltk_error *); + int (*func)(ltk_window *, ltk_box *, ltk_cmd_token *, size_t, ltk_error *); int needs_all; } box_cmds[] = { {"add", &ltk_box_cmd_add, 0}, diff --git a/src/box.h b/src/box.h @@ -20,6 +20,7 @@ /* FIXME: include everything here */ /* Requires the following includes: "scrollbar.h", "rect.h", "widget.h", "ltk.h" */ +#include "cmd.h" #include "err.h" typedef struct { @@ -33,6 +34,6 @@ typedef struct { ltk_orientation orient; } ltk_box; -int ltk_box_cmd(ltk_window *window, char **tokens, size_t num_tokens, ltk_error *); +GEN_CMD_HELPERS_PROTO(ltk_box_cmd) #endif /* _LTK_BOX_H_ */ diff --git a/src/button.c b/src/button.c @@ -211,7 +211,7 @@ static int ltk_button_cmd_create( ltk_window *window, ltk_button *button_unneeded, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)button_unneeded; @@ -236,7 +236,7 @@ ltk_button_cmd_create( static struct button_cmd { char *name; - int (*func)(ltk_window *, ltk_button *, char **, size_t, ltk_error *); + int (*func)(ltk_window *, ltk_button *, ltk_cmd_token *, size_t, ltk_error *); int needs_all; } button_cmds[] = { {"create", &ltk_button_cmd_create, 1}, diff --git a/src/button.h b/src/button.h @@ -19,6 +19,7 @@ /* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */ +#include "cmd.h" #include "err.h" typedef struct { @@ -31,11 +32,6 @@ int ltk_button_ini_handler(ltk_window *window, const char *prop, const char *val int ltk_button_fill_theme_defaults(ltk_window *window); void ltk_button_uninitialize_theme(ltk_window *window); -int ltk_button_cmd( - ltk_window *window, - char **tokens, - size_t num_tokens, - ltk_error * -); +GEN_CMD_HELPERS_PROTO(ltk_button_cmd) #endif /* LTK_BUTTON_H */ diff --git a/src/cmd.c b/src/cmd.c @@ -6,7 +6,7 @@ int ltk_parse_cmd( - ltk_window *window, char **tokens, size_t num_tokens, + ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens, ltk_cmdarg_parseinfo *parseinfo, size_t num_arg, ltk_error *err) { const char *errstr = NULL; if (num_tokens > num_arg || (num_tokens < num_arg && !parseinfo[num_tokens].optional)) { @@ -16,9 +16,14 @@ ltk_parse_cmd( } size_t i = 0; for (; i < num_tokens; i++) { + if (parseinfo[i].type != CMDARG_DATA && tokens[i].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = i; + goto error; + } switch (parseinfo[i].type) { case CMDARG_INT: - parseinfo[i].val.i = ltk_strtonum(tokens[i], parseinfo[i].min, parseinfo[i].max, &errstr); + parseinfo[i].val.i = ltk_strtonum(tokens[i].text, parseinfo[i].min, parseinfo[i].max, &errstr); if (errstr) { err->type = ERR_INVALID_ARGUMENT; err->arg = i; @@ -28,20 +33,29 @@ ltk_parse_cmd( break; case CMDARG_STICKY: parseinfo[i].val.sticky = LTK_STICKY_NONE; - for (const char *c = tokens[i]; *c != '\0'; c++) { + for (const char *c = tokens[i].text; *c != '\0'; c++) { switch (*c) { - case 'n': + case 't': parseinfo[i].val.sticky |= LTK_STICKY_TOP; break; - case 's': + case 'b': parseinfo[i].val.sticky |= LTK_STICKY_BOTTOM; break; - case 'e': + case 'r': parseinfo[i].val.sticky |= LTK_STICKY_RIGHT; break; - case 'w': + case 'l': parseinfo[i].val.sticky |= LTK_STICKY_LEFT; break; + case 'w': + parseinfo[i].val.sticky |= LTK_STICKY_SHRINK_WIDTH; + break; + case 'h': + parseinfo[i].val.sticky |= LTK_STICKY_SHRINK_HEIGHT; + break; + case 'p': + parseinfo[i].val.sticky |= LTK_STICKY_PRESERVE_ASPECT_RATIO; + break; default: err->type = ERR_INVALID_ARGUMENT; err->arg = i; @@ -51,7 +65,7 @@ ltk_parse_cmd( parseinfo[i].initialized = 1; break; case CMDARG_WIDGET: - parseinfo[i].val.widget = ltk_get_widget(tokens[i], parseinfo[i].widget_type, err); + parseinfo[i].val.widget = ltk_get_widget(tokens[i].text, parseinfo[i].widget_type, err); if (!parseinfo[i].val.widget) { err->arg = i; goto error; @@ -59,11 +73,16 @@ ltk_parse_cmd( parseinfo[i].initialized = 1; break; case CMDARG_STRING: - parseinfo[i].val.str = tokens[i]; + parseinfo[i].val.str = tokens[i].text; + parseinfo[i].initialized = 1; + break; + case CMDARG_DATA: + parseinfo[i].val.data = tokens[i].text; parseinfo[i].initialized = 1; + parseinfo[i].len = tokens[i].len; break; case CMDARG_COLOR: - if (ltk_color_create(window->renderdata, tokens[i], &parseinfo[i].val.color)) { + if (ltk_color_create(window->renderdata, tokens[i].text, &parseinfo[i].val.color)) { /* FIXME: this could fail even if the argument is fine */ err->type = ERR_INVALID_ARGUMENT; err->arg = i; @@ -72,9 +91,9 @@ ltk_parse_cmd( parseinfo[i].initialized = 1; break; case CMDARG_BOOL: - if (strcmp(tokens[i], "true") == 0) { + if (strcmp(tokens[i].text, "true") == 0) { parseinfo[i].val.b = 1; - } else if (strcmp(tokens[i], "false") == 0) { + } else if (strcmp(tokens[i].text, "false") == 0) { parseinfo[i].val.b = 0; } else { err->type = ERR_INVALID_ARGUMENT; @@ -84,9 +103,9 @@ ltk_parse_cmd( parseinfo[i].initialized = 1; break; case CMDARG_ORIENTATION: - if (strcmp(tokens[i], "horizontal") == 0) { + if (strcmp(tokens[i].text, "horizontal") == 0) { parseinfo[i].val.orient = LTK_HORIZONTAL; - } else if (strcmp(tokens[i], "vertical") == 0) { + } else if (strcmp(tokens[i].text, "vertical") == 0) { parseinfo[i].val.orient = LTK_VERTICAL; } else { err->type = ERR_INVALID_ARGUMENT; @@ -97,7 +116,7 @@ ltk_parse_cmd( break; case CMDARG_BORDERSIDES: parseinfo[i].val.border = LTK_BORDER_NONE; - for (const char *c = tokens[i]; *c != '\0'; c++) { + for (const char *c = tokens[i].text; *c != '\0'; c++) { switch (*c) { case 't': parseinfo[i].val.border |= LTK_BORDER_TOP; diff --git a/src/cmd.h b/src/cmd.h @@ -5,7 +5,8 @@ typedef enum { CMDARG_IGNORE, - CMDARG_STRING, + CMDARG_STRING, /* nul-terminated string */ + CMDARG_DATA, /* also char*, but may contain nul */ CMDARG_COLOR, CMDARG_INT, CMDARG_BOOL, @@ -21,9 +22,10 @@ typedef enum { typedef struct { ltk_cmdarg_datatype type; /* Note: Bool and int are both integers, but they are - separate just to make it a bit clearer */ + separate just to make it a bit clearer (same goes for str/data) */ union { char *str; + char *data; ltk_color color; int i; int b; @@ -32,6 +34,7 @@ typedef struct { ltk_orientation orient; ltk_widget *widget; } val; + size_t len; /* only for data */ int min, max; /* only for integers */ /* FIXME: which integer type is sensible here? */ ltk_widget_type widget_type; /* only for widgets */ int optional; @@ -40,7 +43,14 @@ typedef struct { /* Returns 1 on error, 0 on success */ /* All optional arguments must be in one block at the end */ -int ltk_parse_cmd(ltk_window *window, char **tokens, size_t num_tokens, ltk_cmdarg_parseinfo *parseinfo, size_t num_arg, ltk_error *err); +int ltk_parse_cmd( + ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens, + ltk_cmdarg_parseinfo *parseinfo, size_t num_arg, ltk_error *err +); + +#define GEN_CMD_HELPERS_PROTO(func_name) \ +int func_name(ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); + #define GEN_CMD_HELPERS(func_name, widget_type, widget_typename, array_name, entry_type) \ /* FIXME: maybe just get rid of this and rely on array already being sorted? */ \ @@ -63,7 +73,7 @@ array_name##_sort_helper(const void *entry1v, const void *entry2v) { \ int \ func_name( \ ltk_window *window, \ - char **tokens, \ + ltk_cmd_token *tokens, \ size_t num_tokens, \ ltk_error *err) { \ if (num_tokens < 3) { \ @@ -78,8 +88,17 @@ func_name( \ sizeof(array_name[0]), &array_name##_sort_helper); \ array_name##_sorted = 1; \ } \ + if (tokens[1].contains_nul) { \ + err->type = ERR_INVALID_ARGUMENT; \ + err->arg = 1; \ + return 1; \ + } else if (tokens[2].contains_nul) { \ + err->type = ERR_INVALID_ARGUMENT; \ + err->arg = 2; \ + return 1; \ + } \ entry_type *e = bsearch( \ - tokens[2], array_name, LENGTH(array_name), \ + tokens[2].text, array_name, LENGTH(array_name), \ sizeof(array_name[0]), &array_name##_search_helper \ ); \ if (!e) { \ @@ -90,7 +109,7 @@ func_name( \ if (e->needs_all) { \ return e->func(window, NULL, tokens, num_tokens, err); \ } else { \ - widget_typename *widget = (widget_typename *)ltk_get_widget(tokens[1], widget_type, err); \ + widget_typename *widget = (widget_typename *)ltk_get_widget(tokens[1].text, widget_type, err); \ if (!widget) { \ err->arg = 1; \ return 1; \ diff --git a/src/config.c b/src/config.c @@ -673,10 +673,11 @@ ltk_config_parsefile( return ret; } +/* FIXME: update this */ const char *default_config = "[general]\n" "explicit-focus = true\n" "all-activatable = true\n" -"[key-binding]\n" +"[key-binding:widget]\n" "bind-keypress move-next sym tab\n" "bind-keypress move-prev sym tab mods shift\n" "bind-keypress move-next text n\n" diff --git a/src/entry.c b/src/entry.c @@ -18,6 +18,7 @@ /* FIXME: cursors jump weirdly with bidi text (need to support strong/weak cursors in pango backend) */ /* FIXME: set imspot - needs to be standardized so widgets don't all do their own thing */ +/* FIXME: some sort of width setting (setting a pixel width would be kind of ugly) */ #include <stdio.h> #include <ctype.h> @@ -792,7 +793,7 @@ static int ltk_entry_cmd_create( ltk_window *window, ltk_entry *entry_unneeded, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)entry_unneeded; @@ -817,7 +818,7 @@ ltk_entry_cmd_create( static struct entry_cmd { char *name; - int (*func)(ltk_window *, ltk_entry *, char **, size_t, ltk_error *); + int (*func)(ltk_window *, ltk_entry *, ltk_cmd_token *, size_t, ltk_error *); int needs_all; } entry_cmds[] = { {"create", &ltk_entry_cmd_create, 1}, diff --git a/src/entry.h b/src/entry.h @@ -19,6 +19,7 @@ /* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */ +#include "cmd.h" #include "err.h" #include "config.h" @@ -44,11 +45,6 @@ int ltk_entry_register_keypress(const char *func_name, size_t func_len, ltk_keyp int ltk_entry_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b); void ltk_entry_cleanup(void); -int ltk_entry_cmd( - ltk_window *window, - char **tokens, - size_t num_tokens, - ltk_error * -); +GEN_CMD_HELPERS_PROTO(ltk_entry_cmd) #endif /* LTK_ENTRY_H */ diff --git a/src/err.c b/src/err.c @@ -11,6 +11,7 @@ static const char *errtable[] = { "Invalid widget id", "Invalid widget type", "Invalid command", + "Unknown error", "Menu is not submenu", "Menu entry already contains submenu", "Invalid grid position", diff --git a/src/err.h b/src/err.h @@ -14,11 +14,12 @@ typedef enum { ERR_INVALID_WIDGET_ID = 7, ERR_INVALID_WIDGET_TYPE = 8, ERR_INVALID_COMMAND = 9, + ERR_UNKNOWN = 10, /* widget specific */ - ERR_MENU_NOT_SUBMENU = 10, - ERR_MENU_ENTRY_CONTAINS_SUBMENU = 11, - ERR_GRID_INVALID_POSITION = 12, - ERR_INVALID_SEQNUM = 13, + ERR_MENU_NOT_SUBMENU = 11, + ERR_MENU_ENTRY_CONTAINS_SUBMENU = 12, + ERR_GRID_INVALID_POSITION = 13, + ERR_INVALID_SEQNUM = 14, } ltk_errtype; typedef struct { diff --git a/src/grid.c b/src/grid.c @@ -1,4 +1,5 @@ /* FIXME: sometimes, resizing doesn't work properly when running test.sh */ + /* * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org> * @@ -47,7 +48,7 @@ static void ltk_grid_destroy(ltk_widget *self, int shallow); static void ltk_recalculate_grid(ltk_widget *self); static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget); static int ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid, - int row, int column, int row_span, int column_span, unsigned short sticky, ltk_error *err); + int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err); static int ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err); static int ltk_grid_find_nearest_column(ltk_grid *grid, int x); static int ltk_grid_find_nearest_row(ltk_grid *grid, int y); @@ -97,31 +98,31 @@ static struct ltk_widget_vtable vtable = { static int ltk_grid_cmd_add( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); static int ltk_grid_cmd_ungrid( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); static int ltk_grid_cmd_create( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); static int ltk_grid_cmd_set_row_weight( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); static int ltk_grid_cmd_set_column_weight( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); @@ -146,7 +147,12 @@ ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { if (!grid->widget_grid[i]) continue; ltk_widget *ptr = grid->widget_grid[i]; - ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, real_clip)); + int max_w = grid->column_pos[ptr->column + ptr->column_span] - grid->column_pos[ptr->column]; + int max_h = grid->row_pos[ptr->row + ptr->row_span] - grid->row_pos[ptr->row]; + ltk_rect r = ltk_rect_intersect( + (ltk_rect){grid->column_pos[ptr->column], grid->row_pos[ptr->row], max_w, max_h}, real_clip + ); + ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, r)); } } @@ -239,6 +245,7 @@ ltk_recalculate_grid(ltk_widget *self) { width_static += grid->column_widths[i]; } } + /* FIXME: what should be done when static height or width is larger than grid? */ if (total_row_weight > 0) { height_unit = (float) (grid->widget.lrect.h - height_static) / (float) total_row_weight; } @@ -270,13 +277,33 @@ ltk_recalculate_grid(ltk_widget *self) { continue; /*orig_width = ptr->lrect.w; orig_height = ptr->lrect.h;*/ + ptr->lrect.w = ptr->ideal_w; + ptr->lrect.h = ptr->ideal_h; end_row = i + ptr->row_span; end_column = j + ptr->column_span; - if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT) { - ptr->lrect.w = grid->column_pos[end_column] - grid->column_pos[j]; - } - if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) { - ptr->lrect.h = grid->row_pos[end_row] - grid->row_pos[i]; + int max_w = grid->column_pos[end_column] - grid->column_pos[j]; + int max_h = grid->row_pos[end_row] - grid->row_pos[i]; + int stretch_width = (ptr->sticky & LTK_STICKY_LEFT) && (ptr->sticky & LTK_STICKY_RIGHT); + int shrink_width = (ptr->sticky & LTK_STICKY_SHRINK_WIDTH) && ptr->lrect.w > max_w; + int stretch_height = (ptr->sticky & LTK_STICKY_TOP) && (ptr->sticky & LTK_STICKY_BOTTOM); + int shrink_height = (ptr->sticky & LTK_STICKY_SHRINK_HEIGHT) && ptr->lrect.h > max_h; + if (stretch_width || shrink_width) + ptr->lrect.w = max_w; + if (stretch_height || shrink_height) + ptr->lrect.h = max_h; + if (ptr->sticky & LTK_STICKY_PRESERVE_ASPECT_RATIO) { + if (!stretch_width && !shrink_width) { + ptr->lrect.w = (int)(((double)ptr->lrect.h / ptr->ideal_h) * ptr->ideal_w); + } else if (!stretch_height && !shrink_height) { + ptr->lrect.h = (int)(((double)ptr->lrect.w / ptr->ideal_w) * ptr->ideal_h); + } else { + double scale_w = (double)ptr->lrect.w / ptr->ideal_w; + double scale_h = (double)ptr->lrect.h / ptr->ideal_h; + if (scale_w * ptr->ideal_h > ptr->lrect.h) + ptr->lrect.w = (int)(scale_h * ptr->ideal_w); + else if (scale_h * ptr->ideal_w > ptr->lrect.w) + ptr->lrect.h = (int)(scale_w * ptr->ideal_h); + } } /* FIXME: Figure out a better system for this - it would be nice to make it more efficient by not doing anything if nothing changed, but that doesn't work when @@ -289,22 +316,27 @@ ltk_recalculate_grid(ltk_widget *self) { /*if (orig_width != ptr->lrect.w || orig_height != ptr->lrect.h)*/ ltk_widget_resize(ptr); - if (ptr->sticky & LTK_STICKY_RIGHT) { + /* the "default" case needs to come first because the widget may be stretched + with aspect ratio preserving, and in that case it should still be centered */ + if (stretch_width || !(ptr->sticky & (LTK_STICKY_RIGHT|LTK_STICKY_LEFT))) { + ptr->lrect.x = grid->column_pos[j] + (grid->column_pos[end_column] - grid->column_pos[j] - ptr->lrect.w) / 2; + } else if (ptr->sticky & LTK_STICKY_RIGHT) { ptr->lrect.x = grid->column_pos[end_column] - ptr->lrect.w; } else if (ptr->sticky & LTK_STICKY_LEFT) { ptr->lrect.x = grid->column_pos[j]; - } else { - ptr->lrect.x = grid->column_pos[j] + ((grid->column_pos[end_column] - grid->column_pos[j]) / 2 - ptr->lrect.w / 2); } - if (ptr->sticky & LTK_STICKY_BOTTOM) { + if (stretch_height || !(ptr->sticky & (LTK_STICKY_TOP|LTK_STICKY_BOTTOM))) { + ptr->lrect.y = grid->row_pos[i] + (grid->row_pos[end_row] - grid->row_pos[i] - ptr->lrect.h) / 2; + } else if (ptr->sticky & LTK_STICKY_BOTTOM) { ptr->lrect.y = grid->row_pos[end_row] - ptr->lrect.h; } else if (ptr->sticky & LTK_STICKY_TOP) { ptr->lrect.y = grid->row_pos[i]; - } else { - ptr->lrect.y = grid->row_pos[i] + ((grid->row_pos[end_row] - grid->row_pos[i]) / 2 - ptr->lrect.h / 2); } + /* intersect both with the grid rect and with the rect of the covered cells since there may be + weird cases where the layout doesn't work properly and the cells are partially outside the grid */ ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect); + ptr->crect = ltk_rect_intersect((ltk_rect){grid->column_pos[j], grid->row_pos[i], max_w, max_h}, ptr->crect); } } } @@ -341,7 +373,7 @@ ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) { /* FIXME: Check if widget already exists at position */ static int ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid, - int row, int column, int row_span, int column_span, unsigned short sticky, ltk_error *err) { + int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err) { if (widget->parent) { err->type = ERR_WIDGET_IN_CONTAINER; return 1; @@ -543,7 +575,7 @@ static int ltk_grid_cmd_add( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -574,7 +606,7 @@ static int ltk_grid_cmd_ungrid( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -595,7 +627,7 @@ static int ltk_grid_cmd_create( ltk_window *window, ltk_grid *grid_unneeded, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)grid_unneeded; @@ -625,7 +657,7 @@ static int ltk_grid_cmd_set_row_weight( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -647,7 +679,7 @@ static int ltk_grid_cmd_set_column_weight( ltk_window *window, ltk_grid *grid, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -663,7 +695,7 @@ ltk_grid_cmd_set_column_weight( static struct grid_cmd { char *name; - int (*func)(ltk_window *, ltk_grid *, char **, size_t, ltk_error *); + int (*func)(ltk_window *, ltk_grid *, ltk_cmd_token *, size_t, ltk_error *); int needs_all; } grid_cmds[] = { {"add", &ltk_grid_cmd_add, 0}, diff --git a/src/grid.h b/src/grid.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,6 +19,7 @@ /* Requires the following includes: "rect.h", "widget.h", "ltk.h" */ +#include "cmd.h" #include "err.h" /* @@ -26,8 +27,6 @@ */ typedef struct { ltk_widget widget; - int rows; - int columns; ltk_widget **widget_grid; int *row_heights; int *column_widths; @@ -35,8 +34,10 @@ typedef struct { int *column_weights; int *row_pos; int *column_pos; + int rows; + int columns; } ltk_grid; -int ltk_grid_cmd(ltk_window *window, char **tokens, size_t num_tokens, ltk_error *err); +GEN_CMD_HELPERS_PROTO(ltk_grid_cmd) #endif /* LTK_GRID_H */ diff --git a/src/image.c b/src/image.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2023 lumidify <nobody@lumidify.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <Imlib2.h> + +#include "ltk.h" +#include "image.h" +#include "memory.h" +#include "graphics.h" +#include "xlib_shared.h" + +struct ltk_image { + Imlib_Image img; +}; + +void +ltk_image_init(ltk_renderdata *data, size_t cache_bytes) { + /* FIXME: overflow */ + imlib_set_cache_size((int)cache_bytes); + imlib_set_color_usage(128); + imlib_context_set_blend(0); + imlib_context_set_dither(1); + imlib_context_set_display(data->dpy); + imlib_context_set_visual(data->vis); + imlib_context_set_colormap(data->cm); +} + +ltk_image * +ltk_image_create_from_mem(const char *path, const char *data, size_t sz) { + Imlib_Image img = imlib_load_image_mem(path, data, sz); + if (!img) + return NULL; + ltk_image *image = ltk_malloc(sizeof(ltk_image)); + image->img = img; + return image; +} + +ltk_image * +ltk_image_create_cropped_scaled( + ltk_image *image, int src_x, int src_y, + int src_w, int src_h, int dst_w, int dst_h) { + imlib_context_set_image(image->img); + /* FIXME: check since when supported */ + Imlib_Image img = imlib_create_cropped_scaled_image( + src_x, src_y, src_w, src_h, dst_w, dst_h + ); + if (!img) + return NULL; + ltk_image *new_image = ltk_malloc(sizeof(ltk_image)); + new_image->img = img; + return new_image; +} + +void +ltk_image_draw(ltk_image *image, ltk_surface *draw_surf, int x, int y) { + imlib_context_set_image(image->img); + imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf)); + imlib_render_image_on_drawable(x, y); +} + +void +ltk_image_draw_scaled( + ltk_image *image, ltk_surface *draw_surf, + int x, int y, int w, int h) { + imlib_context_set_image(image->img); + imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf)); + imlib_render_image_on_drawable_at_size(x, y, w, h); +} + +void +ltk_image_draw_cropped_scaled( + ltk_image *image, ltk_surface *draw_surf, + int src_x, int src_y, int src_w, int src_h, + int dst_x, int dst_y, int dst_w, int dst_h) { + imlib_context_set_image(image->img); + imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf)); + imlib_render_image_part_on_drawable_at_size( + src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h + ); +} + +int +ltk_image_get_width(ltk_image *image) { + imlib_context_set_image(image->img); + return imlib_image_get_width(); +} + +int +ltk_image_get_height(ltk_image *image) { + imlib_context_set_image(image->img); + return imlib_image_get_height(); +} + +void +ltk_image_destroy(ltk_image *image) { + imlib_context_set_image(image->img); + imlib_free_image(); + ltk_free(image); +} diff --git a/src/image.h b/src/image.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 lumidify <nobody@lumidify.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LTK_IMAGE_H +#define LTK_IMAGE_H + +/* FIXME: Files including this need to include ltk.h because that's + currently where the typedef for ltk_surface is. That's really ugly. */ +#include "graphics.h" + +typedef struct ltk_image ltk_image; + +void ltk_image_init(ltk_renderdata *data, size_t cache_bytes); +/* path is only used to guess file type */ +/* data is not taken over! */ +ltk_image *ltk_image_create_from_mem(const char *path, const char *data, size_t sz); +ltk_image *ltk_image_create_cropped_scaled( + ltk_image *image, int src_x, int src_y, + int src_w, int src_h, int dst_w, int dst_h +); +void ltk_image_draw(ltk_image *image, ltk_surface *draw_surf, int x, int y); +void ltk_image_draw_scaled( + ltk_image *image, ltk_surface *draw_surf, + int x, int y, int w, int h +); +void ltk_image_draw_cropped_scaled( + ltk_image *image, ltk_surface *draw_surf, + int src_x, int src_y, int src_w, int src_h, + int dst_x, int dst_y, int dst_w, int dst_h +); +int ltk_image_get_width(ltk_image *image); +int ltk_image_get_height(ltk_image *image); +void ltk_image_destroy(ltk_image *image); + +#endif /* LTK_IMAGE_H */ diff --git a/src/image_widget.c b/src/image_widget.c @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 lumidify <nobody@lumidify.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> + +#include "event.h" +#include "memory.h" +#include "color.h" +#include "rect.h" +#include "widget.h" +#include "ltk.h" +#include "util.h" +#include "image_widget.h" +#include "graphics.h" +#include "cmd.h" + +/* FIXME: add padding, etc. */ + +static void ltk_image_widget_draw( + ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip +); +static ltk_image_widget *ltk_image_widget_create( + ltk_window *window, const char *id, const char *path, const char *data, size_t len +); +static void ltk_image_widget_destroy(ltk_widget *self, int shallow); + +static struct ltk_widget_vtable vtable = { + .draw = &ltk_image_widget_draw, + .destroy = &ltk_image_widget_destroy, + .hide = NULL, + .resize = NULL, + .change_state = NULL, + .child_size_change = NULL, + .remove_child = NULL, + .get_child_at_pos = NULL, + .key_press = NULL, + .key_release = NULL, + .mouse_press = NULL, + .mouse_release = NULL, + .motion_notify = NULL, + .mouse_leave = NULL, + .mouse_enter = NULL, + .type = LTK_WIDGET_IMAGE, + .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_SPECIAL, +}; + +static void +ltk_image_widget_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { + ltk_image_widget *img = (ltk_image_widget *)self; + ltk_rect lrect = self->lrect; + ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); + if (clip_final.w <= 0 || clip_final.h <= 0) + return; + /* FIXME: maybe replace with integer operations */ + double scalex = self->ideal_w / (double)lrect.w; + double scaley = self->ideal_h / (double)lrect.h; + int src_x = (int)(clip_final.x * scalex); + int src_y = (int)(clip_final.y * scaley); + int src_w = (int)(clip_final.w * scalex); + int src_h = (int)(clip_final.h * scaley); + ltk_image_draw_cropped_scaled( + img->img, draw_surf, src_x, src_y, src_w, src_h, + x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h + ); +} + +static ltk_image_widget * +ltk_image_widget_create(ltk_window *window, const char *id, const char *path, const char *data, size_t len) { + ltk_image_widget *img = ltk_malloc(sizeof(ltk_image_widget)); + img->img = ltk_image_create_from_mem(path, data, len); + if (!img->img) + return NULL; + int w = ltk_image_get_width(img->img); + int h = ltk_image_get_height(img->img); + ltk_fill_widget_defaults(&img->widget, id, window, &vtable, w, h); + img->widget.ideal_w = w; + img->widget.ideal_h = h; + + return img; +} + +static void +ltk_image_widget_destroy(ltk_widget *self, int shallow) { + (void)shallow; + ltk_image_widget *img = (ltk_image_widget *)self; + if (!img) { + ltk_warn("Tried to destroy NULL image widget.\n"); + return; + } + ltk_image_destroy(img->img); + ltk_free(img); +} + +/* image <image id> create <filename> <data> */ +static int +ltk_image_widget_cmd_create( + ltk_window *window, + ltk_image_widget *img_unneeded, + ltk_cmd_token *tokens, + size_t num_tokens, + ltk_error *err) { + (void)img_unneeded; + ltk_cmdarg_parseinfo cmd[] = { + {.type = CMDARG_IGNORE, .optional = 0}, + {.type = CMDARG_STRING, .optional = 0}, + {.type = CMDARG_IGNORE, .optional = 0}, + {.type = CMDARG_STRING, .optional = 0}, + {.type = CMDARG_DATA, .optional = 0}, + }; + if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) + return 1; + if (!ltk_widget_id_free(cmd[1].val.str)) { + err->type = ERR_WIDGET_ID_IN_USE; + err->arg = 1; + return 1; + } + ltk_image_widget *img = ltk_image_widget_create(window, cmd[1].val.str, cmd[3].val.str, cmd[4].val.data, cmd[4].len); + if (!img) { + /* FIXME: more sensible error name */ + err->type = ERR_UNKNOWN; + err->arg = -1; + return 1; + } + ltk_set_widget((ltk_widget *)img, cmd[1].val.str); + + return 0; +} + +static struct image_widget_cmd { + char *name; + int (*func)(ltk_window *, ltk_image_widget *, ltk_cmd_token *, size_t, ltk_error *); + int needs_all; +} image_widget_cmds[] = { + {"create", &ltk_image_widget_cmd_create, 1}, +}; + +GEN_CMD_HELPERS(ltk_image_widget_cmd, LTK_WIDGET_IMAGE, ltk_image_widget, image_widget_cmds, struct image_widget_cmd) diff --git a/src/image_widget.h b/src/image_widget.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 lumidify <nobody@lumidify.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LTK_IMAGE_WIDGET_H +#define LTK_IMAGE_WIDGET_H + +#include "cmd.h" +#include "err.h" +#include "image.h" + +typedef struct { + ltk_widget widget; + ltk_image *img; +} ltk_image_widget; + +GEN_CMD_HELPERS_PROTO(ltk_image_widget_cmd) + +#endif /* LTK_IMAGE_WIDGET_H */ diff --git a/src/label.c b/src/label.c @@ -129,6 +129,7 @@ ltk_label_create(ltk_window *window, const char *id, char *text) { label->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1); int text_w, text_h; ltk_text_line_get_size(label->tl, &text_w, &text_h); + /* FIXME: what was I even thinking here? label->widget.ideal_{w,h} isn't even initialized here */ ltk_fill_widget_defaults(&label->widget, id, window, &vtable, label->widget.ideal_w, label->widget.ideal_h); label->widget.ideal_w = text_w + theme.pad * 2; label->widget.ideal_h = text_h + theme.pad * 2; @@ -155,7 +156,7 @@ static int ltk_label_cmd_create( ltk_window *window, ltk_label *label_unneeded, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)label_unneeded; @@ -180,7 +181,7 @@ ltk_label_cmd_create( static struct label_cmd { char *name; - int (*func)(ltk_window *, ltk_label *, char **, size_t, ltk_error *); + int (*func)(ltk_window *, ltk_label *, ltk_cmd_token *, size_t, ltk_error *); int needs_all; } label_cmds[] = { {"create", &ltk_label_cmd_create, 1}, diff --git a/src/label.h b/src/label.h @@ -19,6 +19,7 @@ /* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */ +#include "cmd.h" #include "err.h" typedef struct { @@ -31,11 +32,6 @@ int ltk_label_ini_handler(ltk_window *window, const char *prop, const char *valu int ltk_label_fill_theme_defaults(ltk_window *window); void ltk_label_uninitialize_theme(ltk_window *window); -int ltk_label_cmd( - ltk_window *window, - char **tokens, - size_t num_tokens, - ltk_error *err -); +GEN_CMD_HELPERS_PROTO(ltk_label_cmd) #endif /* LTK_LABEL_H */ diff --git a/src/ltk.h b/src/ltk.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, 2018, 2022 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,8 +18,15 @@ #define _LTK_H_ #include <stdint.h> +#include <stdlib.h> #include "proto_types.h" +typedef struct { + char *text; + size_t len; + int contains_nul; +} ltk_cmd_token; + typedef enum { LTK_EVENT_RESIZE = 1 << 0, LTK_EVENT_BUTTON = 1 << 1, diff --git a/src/ltkc.c b/src/ltkc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2023 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,6 +28,7 @@ #include <sys/un.h> #include "util.h" #include "memory.h" +#include "macros.h" #define BLK_SIZE 128 char tmp_buf[BLK_SIZE]; @@ -47,16 +48,18 @@ static int sockfd = -1; int main(int argc, char *argv[]) { char num[12]; + int bs = 0; int last_newline = 1; + int in_str = 0; uint32_t seq = 0; - int maxrfd, maxwfd; + int maxfd; int infd = fileno(stdin); int outfd = fileno(stdout); struct sockaddr_un un; fd_set rfds, wfds, rallfds, wallfds; - struct timeval tv, tv_master; - tv_master.tv_sec = 0; - tv_master.tv_usec = 20000; + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 20000; size_t path_size; if (argc != 2) { @@ -90,6 +93,16 @@ int main(int argc, char *argv[]) { perror("Socket error"); return -2; } + if (set_nonblock(sockfd)) { + (void)fprintf(stderr, "Unable to set socket to non-blocking mode.\n"); + return 1; + } else if (set_nonblock(infd)) { + (void)fprintf(stderr, "Unable to set stdin to non-blocking mode.\n"); + return 1; + } else if (set_nonblock(outfd)) { + (void)fprintf(stderr, "Unable to set stdout to non-blocking mode.\n"); + return 1; + } io_buffers.in_buffer = ltk_malloc(BLK_SIZE); io_buffers.in_alloc = BLK_SIZE; @@ -104,105 +117,129 @@ int main(int argc, char *argv[]) { FD_SET(infd, &rallfds); FD_SET(sockfd, &wallfds); FD_SET(outfd, &wallfds); - maxrfd = sockfd > infd ? sockfd : infd; - maxwfd = sockfd > outfd ? sockfd : outfd; + maxfd = sockfd > infd ? sockfd : infd; + if (maxfd < outfd) + maxfd = outfd; + + struct timespec now, elapsed, last, sleep_time; + clock_gettime(CLOCK_MONOTONIC, &last); + sleep_time.tv_sec = 0; while (1) { - if (!FD_ISSET(infd, &rallfds) && - !FD_ISSET(sockfd, &rallfds) && - io_buffers.in_len == 0 && - io_buffers.out_len == 0) + if (!FD_ISSET(sockfd, &rallfds) && 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 */ - tv = tv_master; - select(maxrfd + 1, &rfds, NULL, NULL, &tv); - /* value of tv doesn't really matter anymore here because the - necessary framerate-limiting delay is already done */ - select(maxwfd + 1, NULL, &wfds, NULL, &tv); + select(maxfd + 1, &rfds, &wfds, NULL, &tv); + /* FIXME: make all this buffer handling a bit more intelligent */ if (FD_ISSET(sockfd, &rfds)) { - 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) { - FD_CLR(sockfd, &rallfds); - FD_CLR(sockfd, &wallfds); - } else { - io_buffers.out_len += nread; + while (1) { + 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) { + /* FIXME: distinguish errors */ + break; + } else if (nread == 0) { + FD_CLR(sockfd, &rallfds); + FD_CLR(sockfd, &wallfds); + break; + } else { + io_buffers.out_len += nread; + } } } if (FD_ISSET(infd, &rfds)) { - int nread = read(infd, tmp_buf, BLK_SIZE); - if (nread < 0) { - return 2; - } else if (nread == 0) { - FD_CLR(infd, &rallfds); - } else { - for (int i = 0; i < nread; i++) { - if (last_newline) { - int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", seq); - if (numlen < 0 || (unsigned)numlen >= sizeof(num)) - ltk_fatal("There's a bug in the universe.\n"); - ltk_grow_string( - &io_buffers.in_buffer, - &io_buffers.in_alloc, - io_buffers.in_len + numlen - ); - memcpy(io_buffers.in_buffer + io_buffers.in_len, num, numlen); - io_buffers.in_len += numlen; - last_newline = 0; - seq++; - } - if (tmp_buf[i] == '\n') - last_newline = 1; - if (io_buffers.in_len == io_buffers.in_alloc) { - ltk_grow_string( - &io_buffers.in_buffer, - &io_buffers.in_alloc, - io_buffers.in_len + 1 - ); + while (1) { + int nread = read(infd, tmp_buf, BLK_SIZE); + if (nread < 0) { + break; + } else if (nread == 0) { + FD_CLR(infd, &rallfds); + break; + } else { + for (int i = 0; i < nread; i++) { + if (last_newline) { + int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", seq); + if (numlen < 0 || (unsigned)numlen >= sizeof(num)) + ltk_fatal("There's a bug in the universe.\n"); + ltk_grow_string( + &io_buffers.in_buffer, + &io_buffers.in_alloc, + io_buffers.in_len + numlen + ); + memcpy(io_buffers.in_buffer + io_buffers.in_len, num, numlen); + io_buffers.in_len += numlen; + last_newline = 0; + seq++; + } + if (tmp_buf[i] == '\\') { + bs++; + bs %= 2; + } else if (tmp_buf[i] == '"' && !bs) { + in_str = !in_str; + } else if (tmp_buf[i] == '\n' && !in_str) { + last_newline = 1; + } else { + bs = 0; + } + if (io_buffers.in_len == io_buffers.in_alloc) { + ltk_grow_string( + &io_buffers.in_buffer, + &io_buffers.in_alloc, + io_buffers.in_len + 1 + ); + } + io_buffers.in_buffer[io_buffers.in_len++] = tmp_buf[i]; } - io_buffers.in_buffer[io_buffers.in_len++] = tmp_buf[i]; } } } 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; + while (io_buffers.in_len > 0) { + 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) { + break; + } 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; + while (io_buffers.out_len > 0) { + 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) { + break; + } else { + memmove(io_buffers.out_buffer, + io_buffers.out_buffer + nwritten, + io_buffers.out_len - nwritten); + io_buffers.out_len -= nwritten; + } } } + clock_gettime(CLOCK_MONOTONIC, &now); + ltk_timespecsub(&now, &last, &elapsed); + /* FIXME: configure framerate */ + if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) { + sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec; + nanosleep(&sleep_time, NULL); + } + last = now; } ltk_cleanup(); diff --git a/src/ltkc_img.c b/src/ltkc_img.c @@ -0,0 +1,26 @@ +/* This is just a temporary hack to preprocess an image for sending it to + ltkd. The nicer way for this would be to have a special case for the + "image create" command in ltkc, but I was too lazy to implement that + right now. */ + +#include <stdio.h> + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + int c; + while ((c = getchar()) != EOF) { + switch (c) { + case '\\': + fputs("\\\\", stdout); + break; + case '"': + fputs("\\\"", stdout); + break; + default: + putchar(c); + } + } + + return 0; +} diff --git a/src/ltkd.c b/src/ltkd.c @@ -1,10 +1,8 @@ -/* FIXME: backslashes should be parsed properly! */ /* FIXME: Figure out how to properly print window id */ /* FIXME: error checking in tokenizer (is this necessary?) */ -/* FIXME: parsing doesn't work properly with bs? */ /* FIXME: strip whitespace at end of lines in socket format */ /* - * Copyright (c) 2016-2018, 2020-2023 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -60,6 +58,8 @@ #include "scrollbar.h" #include "box.h" #include "menu.h" +#include "image.h" +#include "image_widget.h" #include "macros.h" #include "config.h" @@ -70,11 +70,13 @@ #define WRITE_BLK_SIZE 128 struct token_list { - char **tokens; + ltk_cmd_token *tokens; + /* FIXME: size_t everywhere */ int num_tokens; int num_alloc; }; +/* FIXME: switch to size_t */ static struct ltk_sock_info { int fd; /* file descriptor for socket connection */ int event_mask; /* events to send to socket */ @@ -132,7 +134,7 @@ 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 tokenize_command(struct ltk_sock_info *sock); -static int ltk_set_root_widget_cmd(ltk_window *window, char **tokens, int num_tokens, ltk_error *err); +static int ltk_set_root_widget_cmd(ltk_window *window, ltk_cmd_token *tokens, int num_tokens, ltk_error *err); static int process_commands(ltk_window *window, int client); static int add_client(int fd); static int listen_sock(const char *sock_path); @@ -156,7 +158,7 @@ typedef struct { int (*register_keypress)(const char *, size_t, ltk_keypress_binding); int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding); void (*cleanup)(void); - int (*cmd)(ltk_window *, char **, size_t, ltk_error *); + int (*cmd)(ltk_window *, ltk_cmd_token *, size_t, ltk_error *); } ltk_widget_funcs; /* FIXME: use binary search when searching for the widget */ @@ -212,6 +214,16 @@ ltk_widget_funcs widget_funcs[] = { .cmd = &ltk_label_cmd }, { + .name = "image", + .ini_handler = NULL, + .fill_theme_defaults = NULL, + .uninitialize_theme = NULL, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + .cmd = &ltk_image_widget_cmd + }, + { .name = "menu", .ini_handler = &ltk_menu_ini_handler, .fill_theme_defaults = &ltk_menu_fill_theme_defaults, @@ -346,6 +358,9 @@ static struct { int listenfd; } sock_state; +/* FIXME: this is extremely dangerous right now because pretty much any command + can be executed, so for instance the widget that caused the lock could also + be destroyed, causing issues when this function returns */ int ltk_handle_lock_client(ltk_window *window, int client) { if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1) @@ -356,24 +371,30 @@ ltk_handle_lock_client(ltk_window *window, int client) { FD_ZERO(&wallfds); FD_SET(clifd, &rallfds); FD_SET(clifd, &wallfds); - int rretval, wretval; - struct timeval tv, tv_master; - tv_master.tv_sec = 0; - tv_master.tv_usec = 20000; + int retval; + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + struct timespec now, elapsed, last, sleep_time; + clock_gettime(CLOCK_MONOTONIC, &last); + sleep_time.tv_sec = 0; while (1) { rfds = rallfds; wfds = wallfds; - /* separate these because the writing fds are usually - always ready for writing */ - tv = tv_master; - rretval = select(clifd + 1, &rfds, NULL, NULL, &tv); - /* value of tv doesn't really matter anymore here because the - necessary framerate-limiting delay is already done */ - wretval = select(clifd + 1, NULL, &wfds, NULL, &tv); - - if (rretval > 0 || ((sockets[client].write_cur != sockets[client].write_len) && wretval > 0)) { + retval = select(clifd + 1, &rfds, &wfds, NULL, &tv); + + if (retval > 0) { if (FD_ISSET(clifd, &rfds)) { - if (read_sock(&sockets[client]) == 0) { + int ret; + while ((ret = read_sock(&sockets[client])) == 1) { + int pret; + if ((pret = process_commands(window, client)) == 1) + return 1; + else if (pret == -1) + return 0; + } + /* FIXME: maybe also return on read error? or would that be dangerous? */ + if (ret == 0) { FD_CLR(clifd, &sock_state.rallfds); FD_CLR(clifd, &sock_state.wallfds); ltk_widget_remove_client(client); @@ -390,18 +411,21 @@ ltk_handle_lock_client(ltk_window *window, int client) { break; } return 0; - } else { - int ret; - if ((ret = process_commands(window, client)) == 1) - return 1; - else if (ret == -1) - return 0; } } if (FD_ISSET(clifd, &wfds)) { + /* FIXME: call in loop like above */ write_sock(&sockets[client]); } } + clock_gettime(CLOCK_MONOTONIC, &now); + ltk_timespecsub(&now, &last, &elapsed); + /* FIXME: configure framerate */ + if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) { + sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec; + nanosleep(&sleep_time, NULL); + } + last = now; } return 0; } @@ -410,12 +434,11 @@ static int ltk_mainloop(ltk_window *window) { ltk_event event; fd_set rfds, wfds; - int rretval, wretval; + int retval; int clifd; - struct timeval tv, tv_master; - tv_master.tv_sec = 0; - /* FIXME: configure this number */ - tv_master.tv_usec = 20000; + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; FD_ZERO(&sock_state.rallfds); FD_ZERO(&sock_state.wallfds); @@ -434,10 +457,10 @@ ltk_mainloop(ltk_window *window) { /* FIXME: make time management smarter - maybe always figure out how long it will take until the next timer is due and then sleep if no other events are happening */ - struct timespec now, elapsed, last; + struct timespec now, elapsed, last, lasttimer, sleep_time; clock_gettime(CLOCK_MONOTONIC, &last); - - /* FIXME: framerate limiting for draw */ + lasttimer = last; + sleep_time.tv_sec = 0; /* initialize keyboard mapping */ ltk_generate_keyboard_event(window->renderdata, &event); @@ -473,17 +496,11 @@ ltk_mainloop(ltk_window *window) { } rfds = sock_state.rallfds; wfds = sock_state.wallfds; - /* separate these because the writing fds are usually - always ready for writing */ - tv = tv_master; - rretval = select(sock_state.maxfd + 1, &rfds, NULL, NULL, &tv); - /* value of tv doesn't really matter anymore here because the - necessary framerate-limiting delay is already done */ - wretval = select(sock_state.maxfd + 1, NULL, &wfds, NULL, &tv); + retval = select(sock_state.maxfd + 1, &rfds, &wfds, NULL, &tv); while (!ltk_next_event(window->renderdata, window->clipboard, window->cur_kbd, &event)) ltk_handle_event(window, &event); - if (rretval > 0 || (sock_write_available && wretval > 0)) { + if (retval > 0) { if (FD_ISSET(sock_state.listenfd, &rfds)) { if ((clifd = accept_sock(sock_state.listenfd)) < 0) { /* FIXME: Just log this! */ @@ -502,11 +519,23 @@ ltk_mainloop(ltk_window *window) { if ((clifd = sockets[i].fd) < 0) continue; if (FD_ISSET(clifd, &rfds)) { - if (read_sock(&sockets[i]) == 0) { + /* FIXME: better error handling - this assumes error + is always because read would block */ + /* FIXME: maybe maximum number of iterations here to + avoid choking on a lot of data? although such a + large amount of data would probably cause other + problems anyways */ + /* or maybe measure time and break after max time? */ + int ret; + while ((ret = read_sock(&sockets[i])) == 1) { + process_commands(window, i); + } + if (ret == 0) { ltk_widget_remove_client(i); FD_CLR(clifd, &sock_state.rallfds); FD_CLR(clifd, &sock_state.wallfds); sockets[i].fd = -1; + /* FIXME: what to do on error? */ close(clifd); int newmaxsocket = -1; for (int j = 0; j <= maxsocket; j++) { @@ -518,18 +547,23 @@ ltk_mainloop(ltk_window *window) { ltk_quit(window); break; } - } else { - process_commands(window, i); } } + /* FIXME: maybe ignore SIGPIPE signal - then don't call FD_CLR + for wallfds above but rather when write fails with EPIPE */ + /* -> this would possibly allow data to be written still in the + hypothetical scenario that only the writing end of the socket + is closed (and ltkd wouldn't crash if only the reading end is + closed) */ if (FD_ISSET(clifd, &wfds)) { + /* FIXME: also call in loop like reading above */ write_sock(&sockets[i]); } } } clock_gettime(CLOCK_MONOTONIC, &now); - ltk_timespecsub(&now, &last, &elapsed); + ltk_timespecsub(&now, &lasttimer, &elapsed); /* Note: it should be safe to give the same pointer as the first and last argument, as long as ltk_timespecsub/add isn't changed incompatibly */ size_t i = 0; @@ -539,7 +573,7 @@ ltk_mainloop(ltk_window *window) { (timers[i].remaining.tv_sec == 0 && timers[i].remaining.tv_nsec == 0)) { timers[i].callback(timers[i].data); if (timers[i].repeat.tv_sec == 0 && timers[i].repeat.tv_nsec == 0) { - /* remove timers because it has no repeat */ + /* remove timer because it has no repeat */ memmove(timers + i, timers + i + 1, sizeof(ltk_timer) * (timers_num - i - 1)); } else { ltk_timespecadd(&timers[i].remaining, &timers[i].repeat, &timers[i].remaining); @@ -549,13 +583,22 @@ ltk_mainloop(ltk_window *window) { i++; } } - last = now; + lasttimer = now; 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; } + + clock_gettime(CLOCK_MONOTONIC, &now); + ltk_timespecsub(&now, &last, &elapsed); + /* FIXME: configure framerate */ + if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) { + sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec; + nanosleep(&sleep_time, NULL); + } + last = now; } ltk_cleanup(); @@ -704,7 +747,7 @@ ltk_log_msg(const char *mode, const char *format, va_list args) { static int ltk_set_root_widget_cmd( ltk_window *window, - char **tokens, + ltk_cmd_token *tokens, int num_tokens, ltk_error *err) { ltk_widget *widget; @@ -712,8 +755,12 @@ ltk_set_root_widget_cmd( err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS; err->arg = -1; return 1; + } else if (tokens[1].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = 1; + return 1; } - widget = ltk_get_widget(tokens[1], LTK_WIDGET_ANY, err); + widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err); if (!widget) { err->arg = 1; return 1; @@ -1167,6 +1214,9 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int int tw, th; ltk_text_line_get_size(tmp, &tw, &th); ltk_text_line_destroy(tmp); + /* FIXME: cache doesn't really make any sense right now anyways + since images are only loaded from memory */ + ltk_image_init(window->renderdata, 0); return window; } @@ -1342,12 +1392,14 @@ push_token(struct token_list *tl, char *token) { 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 = ltk_realloc(tl->tokens, new_size * sizeof(char *)); + ltk_cmd_token *new = ltk_reallocarray(tl->tokens, new_size, sizeof(ltk_cmd_token)); if (!new) return -1; tl->tokens = new; tl->num_alloc = new_size; } - tl->tokens[tl->num_tokens++] = token; + tl->tokens[tl->num_tokens].text = token; + tl->tokens[tl->num_tokens].len = 0; + tl->tokens[tl->num_tokens++].contains_nul = 0; return 0; } @@ -1434,6 +1486,11 @@ accept_sock(int listenfd) { if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) { return -1; } + if (set_nonblock(clifd)) { + /* FIXME: what could even be done if close fails? */ + close(clifd); + return -1; + } return clifd; } @@ -1452,7 +1509,7 @@ read_sock(struct ltk_sock_info *sock) { 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); + sock->tokens.tokens[i].text = sock->read + (sock->tokens.tokens[i].text - old); } } nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE); @@ -1467,7 +1524,7 @@ read_sock(struct ltk_sock_info *sock) { Returns -1 on error, 0 otherwise. */ static int write_sock(struct ltk_sock_info *sock) { - if (!sock->write_len) + if (sock->write_len == sock->write_cur) return 0; int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ? sock->write_len - sock->write_cur : WRITE_BLK_SIZE; @@ -1495,6 +1552,7 @@ write_sock(struct ltk_sock_info *sock) { static void move_write_pos(struct ltk_sock_info *sock) { + /* FIXME: also resize if too large */ if (sock->write_cur > 0) { memmove(sock->to_write, sock->to_write + sock->write_cur, sock->write_len - sock->write_cur); @@ -1564,12 +1622,17 @@ ltk_queue_sock_write_fmt(int client, const char *fmt, ...) { static int tokenize_command(struct ltk_sock_info *sock) { for (; sock->read_cur < sock->read_len; sock->read_cur++) { + /* FIXME: strip extra whitespace? Or maybe just have a "hard" protocol where it always has to be one space. */ 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++; + if (sock->bs / 2) + sock->offset++; + else + sock->tokens.tokens[sock->tokens.num_tokens - 1].len++; sock->bs %= 2; sock->read[sock->read_cur-sock->offset] = '\\'; } else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) { @@ -1577,6 +1640,7 @@ tokenize_command(struct ltk_sock_info *sock) { sock->read_cur++; sock->offset = 0; sock->in_token = 0; + sock->bs = 0; return 0; } else if (sock->read[sock->read_cur] == '"') { sock->offset++; @@ -1589,8 +1653,13 @@ tokenize_command(struct ltk_sock_info *sock) { } else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) { sock->read[sock->read_cur-sock->offset] = '\0'; sock->in_token = !sock->in_token; + sock->bs = 0; } else { sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur]; + /* FIXME: assert that num_tokens > 0 */ + sock->tokens.tokens[sock->tokens.num_tokens - 1].len++; + if (sock->read[sock->read_cur] == '\0') + sock->tokens.tokens[sock->tokens.num_tokens - 1].contains_nul = 1; sock->bs = 0; } } @@ -1602,42 +1671,61 @@ tokenize_command(struct ltk_sock_info *sock) { /* FIXME: this is really ugly and inefficient right now - it will be replaced with something more generic at some point (or maybe just with a binary protocol?) */ static int -handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err) { +handle_mask_command(int client, ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { if (num_tokens != 4 && num_tokens != 5) { err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS; err->arg = -1; return 1; } + /* FIXME: make this nicer */ + /* -> use generic cmd handling like the widgets */ + if (tokens[1].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = 1; + return 1; + } else if (tokens[2].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = 2; + return 1; + } else if (tokens[3].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = 3; + return 1; + } else if (num_tokens == 5 && tokens[4].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = 4; + return 1; + } uint32_t mask = 0; int lock = 0; int special = 0; - ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET_ANY, err); + ltk_widget *widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err); if (!widget) { err->arg = 1; return 1; } - if (!strcmp(tokens[2], "widget")) { - if (!strcmp(tokens[3], "mousepress")) { + if (!strcmp(tokens[2].text, "widget")) { + if (!strcmp(tokens[3].text, "mousepress")) { mask = LTK_PEVENTMASK_MOUSEPRESS; - } else if (!strcmp(tokens[3], "mouserelease")) { + } else if (!strcmp(tokens[3].text, "mouserelease")) { mask = LTK_PEVENTMASK_MOUSERELEASE; - } else if (!strcmp(tokens[3], "mousemotion")) { + } else if (!strcmp(tokens[3].text, "mousemotion")) { mask = LTK_PEVENTMASK_MOUSEMOTION; - } else if (!strcmp(tokens[3], "configure")) { + } else if (!strcmp(tokens[3].text, "configure")) { mask = LTK_PEVENTMASK_CONFIGURE; - } else if (!strcmp(tokens[3], "statechange")) { + } else if (!strcmp(tokens[3].text, "statechange")) { mask = LTK_PEVENTMASK_STATECHANGE; - } else if (!strcmp(tokens[3], "none")) { + } else if (!strcmp(tokens[3].text, "none")) { mask = LTK_PEVENTMASK_NONE; } else { err->type = ERR_INVALID_ARGUMENT; err->arg = 3; return 1; } - } else if (!strcmp(tokens[2], "menuentry")) { - if (!strcmp(tokens[3], "press")) { + } else if (!strcmp(tokens[2].text, "menuentry")) { + if (!strcmp(tokens[3].text, "press")) { mask = LTK_PWEVENTMASK_MENUENTRY_PRESS; - } else if (!strcmp(tokens[3], "none")) { + } else if (!strcmp(tokens[3].text, "none")) { mask = LTK_PWEVENTMASK_MENUENTRY_NONE; } else { err->type = ERR_INVALID_ARGUMENT; @@ -1645,10 +1733,10 @@ handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err return 1; } special = 1; - } else if (!strcmp(tokens[2], "button")) { - if (!strcmp(tokens[3], "press")) { + } else if (!strcmp(tokens[2].text, "button")) { + if (!strcmp(tokens[3].text, "press")) { mask = LTK_PWEVENTMASK_BUTTON_PRESS; - } else if (!strcmp(tokens[3], "none")) { + } else if (!strcmp(tokens[3].text, "none")) { mask = LTK_PWEVENTMASK_BUTTON_NONE; } else { err->type = ERR_INVALID_ARGUMENT; @@ -1662,7 +1750,7 @@ handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err return 1; } if (num_tokens == 5) { - if (!strcmp(tokens[4], "lock")) { + if (!strcmp(tokens[4].text, "lock")) { lock = 1; } else { err->type = ERR_INVALID_ARGUMENT; @@ -1671,7 +1759,7 @@ handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err } } - if (!strcmp(tokens[0], "mask-add")) { + if (!strcmp(tokens[0].text, "mask-add")) { if (lock) { if (special) ltk_widget_add_to_event_lwmask(widget, client, mask); @@ -1683,7 +1771,7 @@ handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err else ltk_widget_add_to_event_mask(widget, client, mask); } - } else if (!strcmp(tokens[0], "mask-set")) { + } else if (!strcmp(tokens[0].text, "mask-set")) { if (lock) { if (special) ltk_widget_set_event_lwmask(widget, client, mask); @@ -1695,7 +1783,7 @@ handle_mask_command(int client, char **tokens, size_t num_tokens, ltk_error *err else ltk_widget_set_event_mask(widget, client, mask); } - } else if (!strcmp(tokens[0], "mask-remove")) { + } else if (!strcmp(tokens[0].text, "mask-remove")) { if (lock) { if (special) ltk_widget_remove_from_event_lwmask(widget, client, mask); @@ -1723,7 +1811,7 @@ process_commands(ltk_window *window, int client) { if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1) return 0; struct ltk_sock_info *sock = &sockets[client]; - char **tokens; + ltk_cmd_token *tokens; int num_tokens; ltk_error errdetail = {ERR_NONE, -1}; int err; @@ -1731,7 +1819,9 @@ process_commands(ltk_window *window, int client) { int last = 0; uint32_t seq; const char *errstr; + int contains_nul = 0; while (!tokenize_command(sock)) { + contains_nul = 0; err = 0; tokens = sock->tokens.tokens; num_tokens = sock->tokens.num_tokens; @@ -1740,43 +1830,48 @@ process_commands(ltk_window *window, int client) { errdetail.arg = -1; err = 1; } else { - seq = (uint32_t)ltk_strtonum(tokens[0], 0, UINT32_MAX, &errstr); + contains_nul = tokens[0].contains_nul; + seq = (uint32_t)ltk_strtonum(tokens[0].text, 0, UINT32_MAX, &errstr); tokens++; num_tokens--; - if (errstr) { + if (errstr || contains_nul) { errdetail.type = ERR_INVALID_SEQNUM; errdetail.arg = -1; err = 1; seq = sock->last_seq; - } else if (strcmp(tokens[0], "set-root-widget") == 0) { + } else if (tokens[0].contains_nul) { + errdetail.type = ERR_INVALID_ARGUMENT; + errdetail.arg = 0; + err = 1; + seq = sock->last_seq; + } else if (strcmp(tokens[0].text, "set-root-widget") == 0) { err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "quit") == 0) { + } else if (strcmp(tokens[0].text, "quit") == 0) { ltk_quit(window); last = 1; - } else if (strcmp(tokens[0], "destroy") == 0) { + } else if (strcmp(tokens[0].text, "destroy") == 0) { err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errdetail); - } else if (strncmp(tokens[0], "mask", 4) == 0) { + } else if (strncmp(tokens[0].text, "mask", 4) == 0) { err = handle_mask_command(client, tokens, num_tokens, &errdetail); - } else if (strcmp(tokens[0], "event-unlock") == 0) { + } else if (strcmp(tokens[0].text, "event-unlock") == 0) { if (num_tokens != 2) { errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS; errdetail.arg = -1; err = 1; - } else if (strcmp(tokens[1], "true") == 0) { + } else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "true") == 0) { retval = 1; - } else if (strcmp(tokens[1], "false") == 0) { + } else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "false") == 0) { retval = -1; } else { err = 1; errdetail.type = ERR_INVALID_ARGUMENT; - errdetail.arg = -1; errdetail.arg = 1; } last = 1; } else { int found = 0; for (size_t i = 0; i < LENGTH(widget_funcs); i++) { - if (widget_funcs[i].cmd && !strcmp(tokens[0], widget_funcs[i].name)) { + if (widget_funcs[i].cmd && !strcmp(tokens[0].text, widget_funcs[i].name)) { err = widget_funcs[i].cmd(window, tokens, num_tokens, &errdetail); found = 1; } @@ -1802,12 +1897,12 @@ process_commands(ltk_window *window, int client) { if (last) break; } - 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; + if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0].text != sock->read) { + memmove(sock->read, sock->tokens.tokens[0].text, sock->read + sock->read_len - sock->tokens.tokens[0].text); + ptrdiff_t offset = sock->tokens.tokens[0].text - sock->read; /* Hmm, seems a bit ugly... */ for (int i = 0; i < sock->tokens.num_tokens; i++) { - sock->tokens.tokens[i] -= offset; + sock->tokens.tokens[i].text -= offset; } sock->read_len -= offset; sock->read_cur -= offset; diff --git a/src/macros.h b/src/macros.h @@ -2,6 +2,8 @@ #define _MACROS_H_ /* stolen from OpenBSD */ +/* Note: some code calls these macros with the same first and last + argument, so it is important that that doesn't cause bad behavior. */ #define ltk_timespecadd(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ diff --git a/src/menu.c b/src/menu.c @@ -1346,7 +1346,7 @@ static int ltk_menu_cmd_create( ltk_window *window, ltk_menu *menu_unneeded, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)menu_unneeded; @@ -1377,7 +1377,7 @@ static int ltk_menu_cmd_insert_entry( ltk_window *window, ltk_menu *menu, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -1398,7 +1398,7 @@ static int ltk_menu_cmd_add_entry( ltk_window *window, ltk_menu *menu, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -1418,7 +1418,7 @@ static int ltk_menu_cmd_remove_entry_index( ltk_window *window, ltk_menu *menu, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -1438,7 +1438,7 @@ static int ltk_menu_cmd_remove_entry_id( ltk_window *window, ltk_menu *menu, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -1458,7 +1458,7 @@ static int ltk_menu_cmd_remove_all_entries( ltk_window *window, ltk_menu *menu, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)window; @@ -1474,7 +1474,7 @@ static int ltk_menuentry_cmd_create( ltk_window *window, ltk_menuentry *e_unneeded, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)e_unneeded; @@ -1501,7 +1501,7 @@ static int ltk_menuentry_cmd_attach_submenu( ltk_window *window, ltk_menuentry *e, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { ltk_cmdarg_parseinfo cmd[] = { @@ -1523,7 +1523,7 @@ static int ltk_menuentry_cmd_detach_submenu( ltk_window *window, ltk_menuentry *e, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)window; @@ -1539,7 +1539,7 @@ ltk_menuentry_cmd_detach_submenu( static struct menu_cmd { char *name; - int (*func)(ltk_window *, ltk_menu *, char **, size_t, ltk_error *); + int (*func)(ltk_window *, ltk_menu *, ltk_cmd_token *, size_t, ltk_error *); int needs_all; } menu_cmds[] = { {"add-entry", &ltk_menu_cmd_add_entry, 0}, @@ -1552,7 +1552,7 @@ static struct menu_cmd { static struct menuentry_cmd { char *name; - int (*func)(ltk_window *, ltk_menuentry *, char **, size_t, ltk_error *); + int (*func)(ltk_window *, ltk_menuentry *, ltk_cmd_token *, size_t, ltk_error *); int needs_all; } menuentry_cmds[] = { {"attach-submenu", &ltk_menuentry_cmd_attach_submenu, 0}, diff --git a/src/menu.h b/src/menu.h @@ -17,6 +17,7 @@ #ifndef LTK_MENU_H #define LTK_MENU_H +#include "cmd.h" #include "ltk.h" #include "text.h" #include "widget.h" @@ -69,18 +70,7 @@ int ltk_submenuentry_ini_handler(ltk_window *window, const char *prop, const cha int ltk_submenuentry_fill_theme_defaults(ltk_window *window); void ltk_submenuentry_uninitialize_theme(ltk_window *window); -int ltk_menu_cmd( - ltk_window *window, - char **tokens, - size_t num_tokens, - ltk_error *err -); - -int ltk_menuentry_cmd( - ltk_window *window, - char **tokens, - size_t num_tokens, - ltk_error *err -); +GEN_CMD_HELPERS_PROTO(ltk_menu_cmd) +GEN_CMD_HELPERS_PROTO(ltk_menuentry_cmd) #endif /* LTK_MENU_H */ diff --git a/src/proto_types.h b/src/proto_types.h @@ -1,16 +1,17 @@ #ifndef LTK_PROTO_TYPES_H #define LTK_PROTO_TYPES_H -#define LTK_WIDGET_UNKNOWN 0 -#define LTK_WIDGET_ANY 1 -#define LTK_WIDGET_GRID 2 -#define LTK_WIDGET_BUTTON 3 -#define LTK_WIDGET_LABEL 4 -#define LTK_WIDGET_BOX 5 -#define LTK_WIDGET_MENU 6 -#define LTK_WIDGET_MENUENTRY 7 -#define LTK_WIDGET_ENTRY 8 -#define LTK_NUM_WIDGETS 9 +#define LTK_WIDGET_UNKNOWN 0 +#define LTK_WIDGET_ANY 1 +#define LTK_WIDGET_GRID 2 +#define LTK_WIDGET_BUTTON 3 +#define LTK_WIDGET_LABEL 4 +#define LTK_WIDGET_BOX 5 +#define LTK_WIDGET_MENU 6 +#define LTK_WIDGET_MENUENTRY 7 +#define LTK_WIDGET_ENTRY 8 +#define LTK_WIDGET_IMAGE 9 +#define LTK_NUM_WIDGETS 10 #define LTK_WIDGETMASK_UNKNOWN (UINT32_C(1) << LTK_WIDGET_UNKNOWN) #define LTK_WIDGETMASK_ANY (UINT32_C(0xFFFF)) @@ -21,6 +22,7 @@ #define LTK_WIDGETMASK_MENU (UINT32_C(1) << LTK_WIDGET_MENU) #define LTK_WIDGETMASK_MENUENTRY (UINT32_C(1) << LTK_WIDGET_MENUENTRY) #define LTK_WIDGETMASK_ENTRY (UINT32_C(1) << LTK_WIDGET_ENTRY) +#define LTK_WIDGETMASK_IMAGE (UINT32_C(1) << LTK_WIDGET_IMAGE) /* P == protocol; W == widget */ diff --git a/src/text_pango.c b/src/text_pango.c @@ -101,6 +101,7 @@ ltk_text_line_create(ltk_text_context *ctx, uint16_t font_size, char *text, int tl->text = ltk_strdup(text); tl->len = strlen(tl->text); tl->font_size = font_size; + /* FIXME: does this ever return NULL? */ tl->layout = pango_layout_new(ctx->context); PangoFontDescription *desc = pango_font_description_from_string(ctx->default_font); diff --git a/src/util.c b/src/util.c @@ -15,6 +15,7 @@ */ #include <pwd.h> +#include <fcntl.h> #include <ctype.h> #include <errno.h> #include <stdio.h> @@ -409,3 +410,13 @@ next_utf8(char *text, size_t len, size_t index) { i++; return i; } + +int +set_nonblock(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + return -1; + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) + return -1; + return 0; +} diff --git a/src/util.h b/src/util.h @@ -53,6 +53,8 @@ int str_array_equal(const char *terminated, const char *array, size_t len); size_t prev_utf8(char *text, size_t index); size_t next_utf8(char *text, size_t len, size_t index); +int set_nonblock(int fd); + #define LENGTH(X) (sizeof(X) / sizeof(X[0])) #endif /* _LTK_UTIL_H_ */ diff --git a/src/widget.c b/src/widget.c @@ -1301,7 +1301,7 @@ ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err) { int ltk_widget_destroy_cmd( ltk_window *window, - char **tokens, + ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) { (void)window; @@ -1311,10 +1311,19 @@ ltk_widget_destroy_cmd( err->arg = -1; return 1; } + if (tokens[1].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = 1; + return 1; + } else if (num_tokens == 3 && tokens[2].contains_nul) { + err->type = ERR_INVALID_ARGUMENT; + err->arg = 2; + return 1; + } if (num_tokens == 3) { - if (strcmp(tokens[2], "deep") == 0) { + if (strcmp(tokens[2].text, "deep") == 0) { shallow = 0; - } else if (strcmp(tokens[2], "shallow") == 0) { + } else if (strcmp(tokens[2].text, "shallow") == 0) { shallow = 1; } else { err->type = ERR_INVALID_ARGUMENT; @@ -1322,7 +1331,7 @@ ltk_widget_destroy_cmd( return 1; } } - ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET_ANY, err); + ltk_widget *widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err); if (!widget) { err->arg = 1; return 1; diff --git a/src/widget.h b/src/widget.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org> + * Copyright (c) 2021-2023 lumidify <nobody@lumidify.org> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -44,7 +44,10 @@ typedef enum { LTK_STICKY_LEFT = 1 << 0, LTK_STICKY_RIGHT = 1 << 1, LTK_STICKY_TOP = 1 << 2, - LTK_STICKY_BOTTOM = 1 << 3 + LTK_STICKY_BOTTOM = 1 << 3, + LTK_STICKY_SHRINK_WIDTH = 1 << 4, + LTK_STICKY_SHRINK_HEIGHT = 1 << 5, + LTK_STICKY_PRESERVE_ASPECT_RATIO = 1 << 6, } ltk_sticky_mask; typedef enum { @@ -164,7 +167,7 @@ struct ltk_widget_vtable { void ltk_widget_hide(ltk_widget *widget); int ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err); -int ltk_widget_destroy_cmd(struct ltk_window *window, char **tokens, size_t num_tokens, ltk_error *err); +int ltk_widget_destroy_cmd(struct ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err); void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, struct ltk_window *window, struct ltk_widget_vtable *vtable, int w, int h); void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state); diff --git a/test.gui b/test.gui @@ -5,23 +5,23 @@ grid grd1 set-column-weight 0 1 grid grd1 set-column-weight 1 1 set-root-widget grd1 box box1 create vertical -grid grd1 add box1 0 0 1 1 nsew +grid grd1 add box1 0 0 1 1 lrtb button btn1 create "I'm a button!" button btn2 create "I'm also a button!" button btn3 create "I'm another boring button." -box box1 add btn1 ew -box box1 add btn2 e +box box1 add btn1 lr +box box1 add btn2 r box box1 add btn3 box box2 create vertical -grid grd1 add box2 1 0 1 1 nsew +grid grd1 add box2 1 0 1 1 lrtb button btn4 create "2 I'm a button!" button btn5 create "2 I'm also a button!" button btn6 create "2 I'm another boring button." -box box2 add btn4 ew -box box2 add btn5 e +box box2 add btn4 lr +box box2 add btn5 r box box2 add btn6 button btn7 create "Button 7" button btn8 create "Button 8" -grid grd1 add btn7 0 1 1 1 nsew -grid grd1 add btn8 1 1 1 1 ew +grid grd1 add btn7 0 1 1 1 lrtb +grid grd1 add btn8 1 1 1 1 lr mask-add btn1 button press diff --git a/test2.gui b/test2.gui @@ -41,5 +41,5 @@ menuentry entry14 attach-submenu submenu2 submenu submenu3 create menu submenu3 add-entry entry16 menuentry entry15 attach-submenu submenu3 -grid grd1 add menu1 0 0 1 1 ew +grid grd1 add menu1 0 0 1 1 lr mask-add entry10 menuentry press diff --git a/test3.gui b/test3.gui @@ -13,4 +13,4 @@ grid grd1 add btn2 1 0 1 1 grid grd1 add btn3 2 0 1 1 mask-add btn1 button press entry entry1 create "Hi" -grid grd1 add entry1 3 0 1 1 ew +grid grd1 add entry1 3 0 1 1 w diff --git a/test3.sh b/test3.sh @@ -14,7 +14,7 @@ do echo "quit" ;; *) - printf "%s\n" "$cmd" >&2 + printf "client1: %s\n" "$cmd" >&2 ;; esac done | ./src/ltkc $ltk_id diff --git a/testimg.sh b/testimg.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +export LTKDIR="`pwd`/.ltk" +ltk_id=`./src/ltkd -t "Cool Window"` +#if [ $? -ne 0 ]; then +# echo "Unable to start ltkd." >&2 +# exit 1 +#fi + +{ printf "grid grd1 create 2 1\ngrid grd1 set-row-weight 0 1\ngrid grd1 set-row-weight 1 1\ngrid grd1 set-column-weight 0 1\nset-root-widget grd1\nimage img1 create test.png \""; ./src/ltkc_img < ~/test.png; printf "\"\ngrid grd1 add img1 1 0 1 1 lrp\n"; } |./src/ltkc $ltk_id | while read cmd +do + printf "%s\n" "$cmd" >&2 +done | ./src/ltkc $ltk_id