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:
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 = <k_entry_motion_notify,
.mouse_leave = <k_entry_mouse_leave,
.mouse_enter = <k_entry_mouse_enter,
+ .cmd_return = <k_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 = <k_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 *);