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 e714a12f36b44d6b67fcad122c858af6b903bdb2
parent 59e4368e073cc9c2a99bfb26f928e120502c5ce5
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 27 Aug 2023 21:55:58 +0200

Add basic support for external line editor

Diffstat:
M.ltk/ltk.cfg | 3++-
MMakefile | 2+-
Msrc/config.c | 9+++++++--
Msrc/config.h | 1+
Msrc/entry.c | 43++++++++++++++++++++++++++++++++++++-------
Msrc/ltk.h | 9+++++++++
Msrc/ltkd.c | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/text_pango.c | 4+++-
Msrc/text_stb.c | 5+++--
Msrc/theme.c | 5++++-
Msrc/txtbuf.c | 19++++++++++++-------
Msrc/txtbuf.h | 20+++++++++++++-------
Msrc/util.c | 252++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/util.h | 6++++--
Msrc/widget.h | 1+
15 files changed, 404 insertions(+), 45 deletions(-)

diff --git a/.ltk/ltk.cfg b/.ltk/ltk.cfg @@ -1,9 +1,9 @@ [general] explicit-focus = true all-activatable = true +line-editor = "st -e vi %f" # In future: # text-editor = ... -# line-editor = ... [key-binding:widget] # In future: @@ -44,6 +44,7 @@ bind-keypress expand-selection-right sym right mods shift bind-keypress selection-to-clipboard text c mods ctrl bind-keypress paste-clipboard text v mods ctrl bind-keypress switch-selection-side text o mods alt +bind-keypress edit-external text E mods ctrl # default mapping (just to silence warnings) [key-mapping] diff --git a/Makefile b/Makefile @@ -109,7 +109,7 @@ src/ltkd: $(OBJ) $(CC) -o $@ $(OBJ) $(LTK_LDFLAGS) src/ltkc: src/ltkc.o src/util.o src/memory.o - $(CC) -o $@ src/ltkc.o src/util.o src/memory.o $(LTK_LDFLAGS) + $(CC) -o $@ src/ltkc.o src/util.o src/memory.o src/txtbuf.o $(LTK_LDFLAGS) $(OBJ) : $(HDR) diff --git a/src/config.c b/src/config.c @@ -435,6 +435,7 @@ destroy_config(ltk_config *c) { } ltk_free(c->mappings[i].mappings); } + ltk_free(c->general.line_editor); ltk_free(c->mappings); ltk_free(c); } @@ -494,6 +495,7 @@ load_from_text( config->mappings_alloc = config->mappings_len = 0; config->general.explicit_focus = 0; config->general.all_activatable = 0; + config->general.line_editor = NULL; struct lexstate s = {filename, file_contents, len, 0, 1, 0}; struct token tok = next_token(&s); @@ -548,6 +550,8 @@ load_from_text( msg = "Invalid boolean setting"; goto error; } + } else if (str_array_equal("line-editor", prev2tok.text, prev2tok.len)) { + config->general.line_editor = ltk_strndup(tok.text, tok.len); } else { msg = "Invalid setting"; goto error; @@ -658,9 +662,10 @@ ltk_config_parsefile( keyrelease_binding_handler release_handler, char **errstr) { unsigned long len = 0; - char *file_contents = ltk_read_file(filename, &len); + char *ferrstr = NULL; + char *file_contents = ltk_read_file(filename, &len, &ferrstr); if (!file_contents) { - *errstr = ltk_print_fmt("Unable to open file \"%s\"", filename); + *errstr = ltk_print_fmt("Unable to open file \"%s\": %s", filename, ferrstr); return 1; } int ret = load_from_text(filename, file_contents, len, press_handler, release_handler, errstr); diff --git a/src/config.h b/src/config.h @@ -36,6 +36,7 @@ typedef struct { } ltk_language_mapping; typedef struct { + char *line_editor; char explicit_focus; char all_activatable; } ltk_general_config; diff --git a/src/entry.c b/src/entry.c @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -/* FIXME: allow opening text in external program */ +/* FIXME: mouse actions for expanding selection (shift+click) */ /* 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 */ @@ -58,6 +58,7 @@ static int ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event); static int ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event); static int ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event); static int ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event); +static void ltk_entry_cmd_return(ltk_widget *self, char *text, size_t len); /* FIXME: also allow binding key release, not just press */ typedef void (*cb_func)(ltk_entry *, ltk_key_event *); @@ -78,9 +79,10 @@ static void paste_clipboard(ltk_entry *entry, ltk_key_event *event); static void select_all(ltk_entry *entry, ltk_key_event *event); static void delete_char_backwards(ltk_entry *entry, ltk_key_event *event); static void delete_char_forwards(ltk_entry *entry, ltk_key_event *event); +static void edit_external(ltk_entry *entry, ltk_key_event *event); static void recalc_ideal_size(ltk_entry *entry); static void ensure_cursor_shown(ltk_entry *entry); -static void insert_text(ltk_entry *entry, char *text, size_t len); +static void insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor); struct key_cb { char *text; @@ -94,6 +96,7 @@ static struct key_cb cb_map[] = { {"cursor-to-end", &cursor_to_end}, {"delete-char-backwards", &delete_char_backwards}, {"delete-char-forwards", &delete_char_forwards}, + {"edit-external", &edit_external}, {"expand-selection-left", &expand_selection_left}, {"expand-selection-right", &expand_selection_right}, {"paste-clipboard", &paste_clipboard}, @@ -170,6 +173,7 @@ static struct ltk_widget_vtable vtable = { .motion_notify = &ltk_entry_motion_notify, .mouse_leave = &ltk_entry_mouse_leave, .mouse_enter = &ltk_entry_mouse_enter, + .cmd_return = &ltk_entry_cmd_return, .change_state = NULL, .get_child_at_pos = NULL, .resize = NULL, @@ -425,7 +429,7 @@ paste_primary(ltk_entry *entry, ltk_key_event *event) { (void)event; txtbuf *buf = ltk_clipboard_get_primary_text(entry->widget.window->clipboard); if (buf) - insert_text(entry, buf->text, buf->len); + insert_text(entry, buf->text, buf->len, 1); } static void @@ -433,7 +437,7 @@ paste_clipboard(ltk_entry *entry, ltk_key_event *event) { (void)event; txtbuf *buf = ltk_clipboard_get_clipboard_text(entry->widget.window->clipboard); if (buf) - insert_text(entry, buf->text, buf->len); + insert_text(entry, buf->text, buf->len, 1); } static void @@ -529,7 +533,7 @@ ensure_cursor_shown(ltk_entry *entry) { /* FIXME: maybe make this a regular key binding with wildcard text like in ledit? */ static void -insert_text(ltk_entry *entry, char *text, size_t len) { +insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor) { size_t num = 0; /* FIXME: this is ugly and there are probably a lot of other cases that need to be handled */ @@ -558,7 +562,8 @@ insert_text(ltk_entry *entry, char *text, size_t len) { if (text[i] != '\n' && text[i] != '\r') entry->text[j++] = text[i]; } - entry->pos += reallen; + if (move_cursor) + entry->pos += reallen; entry->text[entry->len] = '\0'; ltk_text_line_set_text(entry->tl, entry->text, 0); recalc_ideal_size(entry); @@ -567,6 +572,29 @@ insert_text(ltk_entry *entry, char *text, size_t len) { ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget); } +static void +ltk_entry_cmd_return(ltk_widget *self, char *text, size_t len) { + ltk_entry *e = (ltk_entry *)self; + wipe_selection(e); + e->len = e->pos = 0; + insert_text(e, text, len, 0); +} + +static void +edit_external(ltk_entry *entry, ltk_key_event *event) { + (void)event; + ltk_config *config = ltk_config_get(); + /* FIXME: allow arguments to key mappings - this would allow to have different key mappings + for different editors instead of just one command */ + if (!config->general.line_editor) { + ltk_warn("Unable to run external editing command: line editor not configured\n"); + } else { + /* FIXME: somehow show that there was an error if this returns 1? */ + /* FIXME: change interface to not require length of cmd */ + ltk_window_call_cmd(entry->widget.window, &entry->widget, config->general.line_editor, strlen(config->general.line_editor), entry->text, entry->len); + } +} + static int ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) { ltk_entry *entry = (ltk_entry *)self; @@ -590,7 +618,7 @@ ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) { /* FIXME: properly handle everything */ if (event->text[0] == '\n' || event->text[0] == '\r' || event->text[0] == 0x1b) return 0; - insert_text(entry, event->text, strlen(event->text)); + insert_text(entry, event->text, strlen(event->text), 1); return 1; } return 0; @@ -751,6 +779,7 @@ ltk_entry_destroy(ltk_widget *self, int shallow) { ltk_warn("Tried to destroy NULL entry.\n"); return; } + ltk_free(entry->text); ltk_surface_cache_release_key(entry->key); ltk_text_line_destroy(entry->tl); ltk_free(entry); diff --git a/src/ltk.h b/src/ltk.h @@ -59,6 +59,14 @@ struct ltk_window { ltk_widget *active_widget; ltk_widget *pressed_widget; void (*other_event) (struct ltk_window *, ltk_event *event); + + /* PID of external command called e.g. by text widget to edit text. + ON exit, cmd_caller->vtable->cmd_return is called with the text + the external command wrote to a file. */ + int cmd_pid; + char *cmd_tmpfile; + char *cmd_caller; + ltk_rect rect; ltk_window_theme *theme; ltk_rect dirty_rect; @@ -83,6 +91,7 @@ struct ltk_window_theme { ltk_color bg; }; +int ltk_window_call_cmd(ltk_window *window, ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen); void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect); void ltk_queue_event(ltk_window *window, ltk_userevent_type type, const char *id, const char *data); void ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event); diff --git a/src/ltkd.c b/src/ltkd.c @@ -4,7 +4,7 @@ /* FIXME: parsing doesn't work properly with bs? */ /* FIXME: strip whitespace at end of lines in socket format */ /* - * Copyright (c) 2016, 2017, 2018, 2020, 2021, 2022 lumidify <nobody@lumidify.org> + * Copyright (c) 2016-2018, 2020-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 @@ -34,6 +34,8 @@ #include <sys/un.h> #include <sys/stat.h> +#include <sys/wait.h> +#include <sys/types.h> #include <sys/select.h> #include <sys/socket.h> @@ -441,7 +443,34 @@ ltk_mainloop(ltk_window *window) { ltk_generate_keyboard_event(window->renderdata, &event); ltk_handle_event(window, &event); + int pid = -1; + int wstatus = 0; while (running) { + if (window->cmd_caller && (pid = waitpid(window->cmd_pid, &wstatus, WNOHANG)) > 0) { + ltk_error err; + ltk_widget *cmd_caller = ltk_get_widget(window->cmd_caller, LTK_WIDGET_ANY, &err); + /* FIXME: should commands be split into read/write and block write commands during external editing? */ + /* FIXME: what if a new widget with same id was created in meantime? */ + if (!cmd_caller) { + ltk_warn("Widget '%s' disappeared while text was being edited in external program\n", window->cmd_caller); + } else if (cmd_caller->vtable->cmd_return) { + size_t file_len = 0; + char *errstr = NULL; + char *contents = ltk_read_file(window->cmd_tmpfile, &file_len, &errstr); + if (!contents) { + ltk_warn("Unable to read file '%s' written by external command: %s\n", window->cmd_tmpfile, errstr); + } else { + cmd_caller->vtable->cmd_return(cmd_caller, contents, file_len); + ltk_free(contents); + } + } + ltk_free(window->cmd_caller); + window->cmd_caller = NULL; + window->cmd_pid = -1; + unlink(window->cmd_tmpfile); + ltk_free(window->cmd_tmpfile); + window->cmd_tmpfile = NULL; + } rfds = sock_state.rallfds; wfds = sock_state.wallfds; /* separate these because the writing fds are usually @@ -1030,6 +1059,40 @@ handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name return 1; } +int +ltk_window_call_cmd(ltk_window *window, ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen) { + if (window->cmd_caller) { + /* FIXME: allow multiple programs? */ + ltk_warn("External program to edit text is already being run\n"); + return 1; + } + /* FIXME: support environment variable $TMPDIR */ + ltk_free(window->cmd_tmpfile); + window->cmd_tmpfile = ltk_strdup("/tmp/ltk.XXXXXX"); + int fd = mkstemp(window->cmd_tmpfile); + if (fd == -1) { + ltk_warn("Unable to create temporary file while trying to run command '%.*s'\n", (int)cmdlen, cmd); + return 1; + } + close(fd); + /* FIXME: give file descriptor directly to modified version of ltk_write_file */ + char *errstr = NULL; + if (ltk_write_file(window->cmd_tmpfile, text, textlen, &errstr)) { + ltk_warn("Unable to write to file '%s' while trying to run command '%.*s': %s\n", window->cmd_tmpfile, (int)cmdlen, cmd, errstr); + unlink(window->cmd_tmpfile); + return 1; + } + int pid = -1; + if ((pid = ltk_parse_run_cmd(cmd, cmdlen, window->cmd_tmpfile)) <= 0) { + ltk_warn("Unable to run command '%.*s'\n", (int)cmdlen, cmd); + unlink(window->cmd_tmpfile); + return 1; + } + window->cmd_pid = pid; + window->cmd_caller = ltk_strdup(caller->id); + return 0; +} + static ltk_window * ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) { char *theme_path; @@ -1070,6 +1133,10 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int window->active_widget = NULL; window->pressed_widget = NULL; + window->cmd_pid = -1; + window->cmd_tmpfile = NULL; + window->cmd_caller = NULL; + window->surface_cache = ltk_surface_cache_create(window->renderdata); window->other_event = &ltk_window_other_event; @@ -1092,6 +1159,7 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int static void ltk_destroy_window(ltk_window *window) { + ltk_free(window->cmd_tmpfile); ltk_clipboard_destroy(window->clipboard); ltk_text_context_destroy(window->text_context); if (window->popups) diff --git a/src/text_pango.c b/src/text_pango.c @@ -166,6 +166,8 @@ ltk_text_line_add_attr_bg(ltk_text_line *tl, size_t start, size_t end, ltk_color PangoAttribute *attr = pango_attr_background_new(c.red, c.green, c.blue); attr->start_index = start; attr->end_index = end; + /* FIXME: this is sketchy - if add_attr_bg/fg is called multiple times, + pango_layout_set_attributes will probably ref the same AttrList multiple times */ pango_attr_list_insert(tl->attrs, attr); pango_layout_set_attributes(tl->layout, tl->attrs); } @@ -355,7 +357,7 @@ ltk_text_line_move_cursor_visually(ltk_text_line *tl, size_t pos, int movement, void ltk_text_line_destroy(ltk_text_line *tl) { if (tl->attrs) - g_object_unref(tl->attrs); + pango_attr_list_unref(tl->attrs); g_object_unref(tl->layout); ltk_free(tl->text); ltk_free(tl); diff --git a/src/text_stb.c b/src/text_stb.c @@ -331,9 +331,10 @@ static ltk_font * ltk_create_font(char *path, uint16_t id, int index) { unsigned long len; ltk_font *font = ltk_malloc(sizeof(ltk_font)); - char *contents = ltk_read_file(path, &len); + char *errstr = NULL; + char *contents = ltk_read_file(path, &len, &errstr); if (!contents) - ltk_fatal_errno("Unable to read font file %s\n", path); + ltk_fatal_errno("Unable to read font file %s: %s\n", path, errstr); int offset = stbtt_GetFontOffsetForIndex((unsigned char *)contents, index); font->info.data = NULL; if (!stbtt_InitFont(&font->info, (unsigned char *)contents, offset)) diff --git a/src/theme.c b/src/theme.c @@ -46,6 +46,7 @@ ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *prop, c } break; case THEME_STRING: + /* FIXME: check if already set? */ *(entry->ptr.str) = ltk_strdup(value); entry->initialized = 1; break; @@ -102,6 +103,8 @@ int ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len) { for (size_t i = 0; i < len; i++) { ltk_theme_parseinfo *e = &parseinfo[i]; + if (e->initialized) + continue; switch (e->type) { case THEME_INT: *(e->ptr.i) = e->defaultval.i; @@ -143,7 +146,7 @@ ltk_theme_uninitialize(ltk_window *window, ltk_theme_parseinfo *parseinfo, size_ continue; switch (e->type) { case THEME_STRING: - free(*(e->ptr.str)); + ltk_free(*(e->ptr.str)); e->initialized = 0; break; case THEME_COLOR: diff --git a/src/txtbuf.c b/src/txtbuf.c @@ -17,7 +17,7 @@ txtbuf_new(void) { } txtbuf * -txtbuf_new_from_char(char *str) { +txtbuf_new_from_char(const char *str) { txtbuf *buf = ltk_malloc(sizeof(txtbuf)); buf->text = ltk_strdup(str); buf->len = strlen(str); @@ -26,7 +26,7 @@ txtbuf_new_from_char(char *str) { } txtbuf * -txtbuf_new_from_char_len(char *str, size_t len) { +txtbuf_new_from_char_len(const char *str, size_t len) { txtbuf *buf = ltk_malloc(sizeof(txtbuf)); buf->text = ltk_strndup(str, len); buf->len = len; @@ -35,7 +35,7 @@ txtbuf_new_from_char_len(char *str, size_t len) { } void -txtbuf_fmt(txtbuf *buf, char *fmt, ...) { +txtbuf_fmt(txtbuf *buf, const char *fmt, ...) { va_list args; va_start(args, fmt); int len = vsnprintf(buf->text, buf->cap, fmt, args); @@ -52,12 +52,12 @@ txtbuf_fmt(txtbuf *buf, char *fmt, ...) { } void -txtbuf_set_text(txtbuf *buf, char *text) { +txtbuf_set_text(txtbuf *buf, const char *text) { txtbuf_set_textn(buf, text, strlen(text)); } void -txtbuf_set_textn(txtbuf *buf, char *text, size_t len) { +txtbuf_set_textn(txtbuf *buf, const char *text, size_t len) { txtbuf_resize(buf, len); buf->len = len; memmove(buf->text, text, len); @@ -65,7 +65,7 @@ txtbuf_set_textn(txtbuf *buf, char *text, size_t len) { } void -txtbuf_append(txtbuf *buf, char *text) { +txtbuf_append(txtbuf *buf, const char *text) { txtbuf_appendn(buf, text, strlen(text)); } @@ -73,7 +73,7 @@ txtbuf_append(txtbuf *buf, char *text) { space so a buffer that will be filled up anyways doesn't have to be constantly resized */ void -txtbuf_appendn(txtbuf *buf, char *text, size_t len) { +txtbuf_appendn(txtbuf *buf, const char *text, size_t len) { /* FIXME: overflow protection here and everywhere else */ txtbuf_resize(buf, buf->len + len); memmove(buf->text + buf->len, text, len); @@ -116,6 +116,11 @@ txtbuf_dup(txtbuf *src) { return dst; } +char * +txtbuf_get_textcopy(txtbuf *buf) { + return buf->text ? ltk_strndup(buf->text, buf->len) : ltk_strdup(""); +} + /* FIXME: proper "normalize" function to add nul-termination if needed */ int txtbuf_cmp(txtbuf *buf1, txtbuf *buf2) { diff --git a/src/txtbuf.h b/src/txtbuf.h @@ -24,39 +24,39 @@ txtbuf *txtbuf_new(void); * Create a new txtbuf, initializing it with the nul-terminated * string 'str'. The input string is copied. */ -txtbuf *txtbuf_new_from_char(char *str); +txtbuf *txtbuf_new_from_char(const char *str); /* * Create a new txtbuf, initializing it with the string 'str' * of length 'len'. The input string is copied. */ -txtbuf *txtbuf_new_from_char_len(char *str, size_t len); +txtbuf *txtbuf_new_from_char_len(const char *str, size_t len); /* * Replace the stored text in 'buf' with the text generated by * 'snprintf' when called with the given format string and args. */ -void txtbuf_fmt(txtbuf *buf, char *fmt, ...); +void txtbuf_fmt(txtbuf *buf, const char *fmt, ...); /* * Replace the stored text in 'buf' with 'text'. */ -void txtbuf_set_text(txtbuf *buf, char *text); +void txtbuf_set_text(txtbuf *buf, const char *text); /* * Same as txtbuf_set_text, but with explicit length for 'text'. */ -void txtbuf_set_textn(txtbuf *buf, char *text, size_t len); +void txtbuf_set_textn(txtbuf *buf, const char *text, size_t len); /* * Append 'text' to the text stored in 'buf'. */ -void txtbuf_append(txtbuf *buf, char *text); +void txtbuf_append(txtbuf *buf, const char *text); /* * Same as txtbuf_append, but with explicit length for 'text'. */ -void txtbuf_appendn(txtbuf *buf, char *text, size_t len); +void txtbuf_appendn(txtbuf *buf, const char *text, size_t len); /* * Compare the text of two txtbuf's like 'strcmp'. @@ -91,6 +91,12 @@ void txtbuf_copy(txtbuf *dst, txtbuf *src); txtbuf *txtbuf_dup(txtbuf *src); /* + * Get copy of text stored in 'buf'. + * The returned text belongs to the caller and needs to be freed. + */ +char *txtbuf_get_textcopy(txtbuf *buf); + +/* * Clear the text, but do not reduce the internal capacity * (for efficiency if it will be filled up again anyways). */ diff --git a/src/util.c b/src/util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 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 @@ -15,6 +15,7 @@ */ #include <pwd.h> +#include <ctype.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> @@ -24,25 +25,250 @@ #include <sys/stat.h> #include "util.h" +#include "array.h" #include "memory.h" +#include "txtbuf.h" /* FIXME: Should these functions really fail on memory error? */ -/* FIXME: *len should be long, not unsigned long! */ + char * -ltk_read_file(const char *path, unsigned long *len) { - FILE *f; +ltk_read_file(const char *filename, size_t *len_ret, char **errstr_ret) { + long len; char *file_contents; - f = fopen(path, "rb"); - if (!f) return NULL; - fseek(f, 0, SEEK_END); - *len = ftell(f); - fseek(f, 0, SEEK_SET); - file_contents = ltk_malloc(*len + 1); - fread(file_contents, 1, *len, f); - file_contents[*len] = '\0'; - fclose(f); + FILE *file; + /* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */ + file = fopen(filename, "r"); + if (!file) goto error; + if (fseek(file, 0, SEEK_END)) goto errorclose; + len = ftell(file); + if (len < 0) goto errorclose; + if (fseek(file, 0, SEEK_SET)) goto errorclose; + file_contents = ltk_malloc((size_t)len + 1); + clearerr(file); + fread(file_contents, 1, (size_t)len, file); + if (ferror(file)) goto errorclose; + file_contents[len] = '\0'; + if (fclose(file)) goto error; + *len_ret = (size_t)len; return file_contents; +error: + if (errstr_ret) + *errstr_ret = strerror(errno); + return NULL; +errorclose: + if (errstr_ret) + *errstr_ret = strerror(errno); + fclose(file); + return NULL; +} + +/* FIXME: not sure if errno actually is set usefully after all these functions */ +int +ltk_write_file(const char *path, const char *data, size_t len, char **errstr_ret) { + FILE *file = fopen(path, "w"); + if (!file) goto error; + clearerr(file); + if (fwrite(data, 1, len, file) < len) goto errorclose; + if (fclose(file)) goto error; + return 0; +error: + if (errstr_ret) + *errstr_ret = strerror(errno); + return 1; +errorclose: + if (errstr_ret) + *errstr_ret = strerror(errno); + fclose(file); + return 1; +} + +/* FIXME: maybe have a few standard array types defined somewhere else */ +LTK_ARRAY_INIT_DECL_STATIC(cmd, char *) +LTK_ARRAY_INIT_IMPL_STATIC(cmd, char *) + +static void +free_helper(char *ptr) { + ltk_free(ptr); +} + +/* FIXME: this is really ugly */ +/* FIXME: parse command only once in beginning instead of each time it is run? */ +/* FIXME: this handles double-quote, but the config parser already uses that, so + it's kind of weird because it's parsed twice (also backslashes are parsed twice). */ +int +ltk_parse_run_cmd(const char *cmdtext, size_t len, const char *filename) { + int bs = 0; + int in_sqstr = 0; + int in_dqstr = 0; + int in_ws = 1; + char c; + size_t cur_start = 0; + int offset = 0; + txtbuf *cur_arg = txtbuf_new(); + ltk_array(cmd) *cmd = ltk_array_create(cmd, 4); + char *cmdcopy = ltk_strndup(cmdtext, len); + for (size_t i = 0; i < len; i++) { + c = cmdcopy[i]; + if (c == '\\') { + if (bs) { + offset++; + bs = 0; + } else { + bs = 1; + } + } else if (isspace(c)) { + if (!in_sqstr && !in_dqstr) { + if (bs) { + if (in_ws) { + in_ws = 0; + cur_start = i; + offset = 0; + } else { + offset++; + } + bs = 0; + } else if (!in_ws) { + /* FIXME: shouldn't this be < instead of <=? */ + if (cur_start <= i - offset) + txtbuf_appendn(cur_arg, cmdcopy + cur_start, i - cur_start - offset); + /* FIXME: cmd is named horribly */ + ltk_array_append(cmd, cmd, txtbuf_get_textcopy(cur_arg)); + txtbuf_clear(cur_arg); + in_ws = 1; + offset = 0; + } + /* FIXME: parsing weird here - bs just ignored */ + } else if (bs) { + bs = 0; + } + } else if (c == '%') { + if (bs) { + if (in_ws) { + cur_start = i; + offset = 0; + } else { + offset++; + } + bs = 0; + } else if (!in_sqstr && filename && i < len - 1 && cmdcopy[i + 1] == 'f') { + if (!in_ws && cur_start < i - offset) + txtbuf_appendn(cur_arg, cmdcopy + cur_start, i - cur_start - offset); + txtbuf_append(cur_arg, filename); + i++; + cur_start = i + 1; + offset = 0; + } else if (in_ws) { + cur_start = i; + offset = 0; + } + in_ws = 0; + } else if (c == '"') { + if (in_sqstr) { + bs = 0; + } else if (bs) { + if (in_ws) { + cur_start = i; + offset = 0; + } else { + offset++; + } + bs = 0; + } else if (in_dqstr) { + offset++; + in_dqstr = 0; + continue; + } else { + in_dqstr = 1; + if (in_ws) { + cur_start = i + 1; + offset = 0; + } else { + offset++; + continue; + } + } + in_ws = 0; + } else if (c == '\'') { + if (in_dqstr) { + bs = 0; + } else if (bs) { + if (in_ws) { + cur_start = i; + offset = 0; + } else { + offset++; + } + bs = 0; + } else if (in_sqstr) { + offset++; + in_sqstr = 0; + continue; + } else { + in_sqstr = 1; + if (in_ws) { + cur_start = i + 1; + offset = 0; + } else { + offset++; + continue; + } + } + in_ws = 0; + } else if (bs) { + if (!in_sqstr && !in_dqstr) { + if (in_ws) { + cur_start = i; + offset = 0; + } else { + offset++; + } + } + bs = 0; + in_ws = 0; + } else { + if (in_ws) { + cur_start = i; + offset = 0; + } + in_ws = 0; + } + cmdcopy[i - offset] = cmdcopy[i]; + } + if (in_sqstr || in_dqstr) { + ltk_warn("Unterminated string in command\n"); + goto error; + } + if (!in_ws) { + if (cur_start <= len - offset) + txtbuf_appendn(cur_arg, cmdcopy + cur_start, len - cur_start - offset); + ltk_array_append(cmd, cmd, txtbuf_get_textcopy(cur_arg)); + } + if (cmd->len == 0) { + ltk_warn("Empty command\n"); + goto error; + } + ltk_array_append(cmd, cmd, NULL); /* necessary for execvp */ + int fret = -1; + if ((fret = fork()) < 0) { + ltk_warn("Unable to fork\n"); + goto error; + } else if (fret == 0) { + if (execvp(cmd->buf[0], cmd->buf) == -1) { + /* FIXME: what to do on error here? */ + exit(1); + } + } else { + ltk_free(cmdcopy); + txtbuf_destroy(cur_arg); + ltk_array_destroy_deep(cmd, cmd, &free_helper); + return fret; + } +error: + ltk_free(cmdcopy); + txtbuf_destroy(cur_arg); + ltk_array_destroy_deep(cmd, cmd, &free_helper); + return -1; } /* If `needed` is larger than `*alloc_size`, resize `*str` to diff --git a/src/util.h b/src/util.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 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 @@ -25,7 +25,9 @@ long long ltk_strtonum( long long maxval, const char **errstrp ); -char *ltk_read_file(const char *path, unsigned long *len); +char *ltk_read_file(const char *filename, size_t *len_ret, char **errstr_ret); +int ltk_write_file(const char *path, const char *data, size_t len, char **errstr_ret); +int ltk_parse_run_cmd(const char *cmdtext, size_t len, const char *filename); void ltk_grow_string(char **str, int *alloc_size, int needed); char *ltk_setup_directory(void); char *ltk_strcat_useful(const char *str1, const char *str2); diff --git a/src/widget.h b/src/widget.h @@ -128,6 +128,7 @@ struct ltk_widget_vtable { int (*mouse_enter)(struct ltk_widget *, ltk_motion_event *); int (*press)(struct ltk_widget *); int (*release)(struct ltk_widget *); + void (*cmd_return)(struct ltk_widget *self, char *text, size_t len); void (*resize)(struct ltk_widget *); void (*hide)(struct ltk_widget *);