From b307906cf4b702f23eaedd12f776e97d4ea01d6f Mon Sep 17 00:00:00 2001 From: Florian Stinglmayr Date: Tue, 2 Jul 2019 21:53:29 +0200 Subject: [PATCH] massive improvements all abound --- libdc/include/dc/api.h | 6 ++ libdc/src/api.c | 29 +++++++- ncdc/CMakeLists.txt | 11 ++- ncdc/include/ncdc/cmds.h | 7 +- ncdc/include/ncdc/config.h | 13 ++++ ncdc/include/ncdc/mainwindow.h | 6 ++ ncdc/include/ncdc/ncdc.h | 17 +++++ ncdc/include/ncdc/textview.h | 15 ++++ ncdc/src/cmds.c | 27 ++++++- ncdc/src/config.c | 82 +++++++++++++++++++++ ncdc/src/login.c | 41 +++++++++++ ncdc/src/mainwindow.c | 39 ++++++---- ncdc/src/ncdc.c | 67 ++++++++++------- ncdc/src/textview.c | 82 +++++++++++++++++++++ ncdc/src/util.c | 128 +++++++++++++++++++++++++-------- 15 files changed, 495 insertions(+), 75 deletions(-) create mode 100644 ncdc/include/ncdc/config.h create mode 100644 ncdc/include/ncdc/textview.h create mode 100644 ncdc/src/config.c create mode 100644 ncdc/src/login.c create mode 100644 ncdc/src/textview.c diff --git a/libdc/include/dc/api.h b/libdc/include/dc/api.h index d1d0ffd..9dc7754 100644 --- a/libdc/include/dc/api.h +++ b/libdc/include/dc/api.h @@ -57,4 +57,10 @@ bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **guilds); +/** + * Fetch a list of friends of the login account "login". Returns a GPtrArray + * of dc_account_t which automatically cleans itself up. + */ +bool dc_api_get_friends(dc_api_t api, dc_account_t login, GPtrArray **friends); + #endif diff --git a/libdc/src/api.c b/libdc/src/api.c index 199b7d4..2eddfaa 100644 --- a/libdc/src/api.c +++ b/libdc/src/api.c @@ -360,13 +360,40 @@ cleanup: return ret; } +bool dc_api_get_friends(dc_api_t api, dc_account_t login, GPtrArray **friends) +{ + char *url = NULL; + json_t *reply = NULL; + bool ret = false; + + return_if_true(api == NULL, false); + return_if_true(login == NULL, false); + + asprintf(&url, "users/%s/guilds", dc_account_id(login)); + + reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); + goto_if_true(reply == NULL, cleanup); + + dc_util_dump_json(reply); + ret = true; + +cleanup: + + json_decref(reply); + reply = NULL; + + return ret; +} + bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) { char *url = NULL; json_t *reply = NULL, *c = NULL, *val = NULL; size_t i = 0; bool ret = false; - GPtrArray *guilds = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); + GPtrArray *guilds = g_ptr_array_new_with_free_func( + (GDestroyNotify)dc_unref + ); return_if_true(api == NULL, false); return_if_true(login == NULL, false); diff --git a/ncdc/CMakeLists.txt b/ncdc/CMakeLists.txt index f9c22b1..71a6612 100644 --- a/ncdc/CMakeLists.txt +++ b/ncdc/CMakeLists.txt @@ -2,20 +2,25 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0) PKG_CHECK_MODULES(NCURSES REQUIRED ncursesw) PKG_CHECK_MODULES(PANEL REQUIRED panel) -PKG_CHECK_MODULES(EDIT REQUIRED libedit) +PKG_CHECK_MODULES(CONFUSE REQUIRED libconfuse) SET(TARGET "ncdc") SET(SOURCES "include/ncdc/cmds.h" + "include/ncdc/config.h" "include/ncdc/input.h" "include/ncdc/mainwindow.h" "include/ncdc/ncdc.h" + "include/ncdc/textview.h" "src/cmds.c" + "src/config.c" "src/emacs.c" "src/input.c" + "src/login.c" "src/mainwindow.c" "src/ncdc.c" + "src/textview.c" "src/util.c" ) @@ -28,7 +33,7 @@ INCLUDE_DIRECTORIES( ${GLIB2_INCLUDE_DIRS} ${NCURSES_INCLUDE_DIRS} ${PANEL_INCLUDE_DIRS} - ${EDIT_INCLUDE_DIRS} + ${CONFUSE_INCLUDE_DIRS} ) ADD_EXECUTABLE(${TARGET} ${SOURCES}) @@ -37,5 +42,5 @@ TARGET_LINK_LIBRARIES(${TARGET} ${GLIB2_LIBRARIES} ${NCURSES_LIBRARIES} ${PANEL_LIBRARIES} - ${EDIT_LIBRARIES} + ${CONFUSE_LIBRARIES} ) diff --git a/ncdc/include/ncdc/cmds.h b/ncdc/include/ncdc/cmds.h index 5c5f6de..0708ea4 100644 --- a/ncdc/include/ncdc/cmds.h +++ b/ncdc/include/ncdc/cmds.h @@ -5,7 +5,7 @@ #include typedef bool (*ncdc_command_t)(ncdc_mainwindow_t n, - wchar_t const *s, size_t len); + size_t argc, wchar_t **argv); typedef struct { wchar_t const *name; @@ -14,6 +14,9 @@ typedef struct { extern ncdc_commands_t cmds[]; -bool ncdc_cmd_quit(ncdc_mainwindow_t n, wchar_t const *s, size_t len); +bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s); + +bool ncdc_cmd_quit(ncdc_mainwindow_t n, size_t ac, wchar_t **av); +bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av); #endif diff --git a/ncdc/include/ncdc/config.h b/ncdc/include/ncdc/config.h new file mode 100644 index 0000000..6b9fd42 --- /dev/null +++ b/ncdc/include/ncdc/config.h @@ -0,0 +1,13 @@ +#ifndef NCDC_CONFIG_H +#define NCDC_CONFIG_H + +#include + +struct ncdc_config_; +typedef struct ncdc_config_ *ncdc_config_t; + +ncdc_config_t ncdc_config_new(void); + +dc_account_t ncdc_config_account(ncdc_config_t c, char const *name); + +#endif diff --git a/ncdc/include/ncdc/mainwindow.h b/ncdc/include/ncdc/mainwindow.h index f86ffd4..e165394 100644 --- a/ncdc/include/ncdc/mainwindow.h +++ b/ncdc/include/ncdc/mainwindow.h @@ -2,12 +2,18 @@ #define NCDC_MAINWINDOW_H #include +#include struct ncdc_mainwindow_; typedef struct ncdc_mainwindow_ *ncdc_mainwindow_t; ncdc_mainwindow_t ncdc_mainwindow_new(void); +/* holy shit stains I am lazy + */ +#define LOG(n, ...) ncdc_mainwindow_log(n, __VA_ARGS__) +void ncdc_mainwindow_log(ncdc_mainwindow_t w, wchar_t const *fmt, ...); + void ncdc_mainwindow_refresh(ncdc_mainwindow_t n); void ncdc_mainwindow_input_ready(ncdc_mainwindow_t n); diff --git a/ncdc/include/ncdc/ncdc.h b/ncdc/include/ncdc/ncdc.h index f52777d..f5260d2 100644 --- a/ncdc/include/ncdc/ncdc.h +++ b/ncdc/include/ncdc/ncdc.h @@ -30,15 +30,32 @@ #include #include #include +#include #define return_if_true(v,r) do { if (v) return r; } while(0) #define goto_if_true(v,l) do { if (v) goto l; } while(0) +struct ncdc_account_ { + dc_account_t account; + GPtrArray *friends; + GPtrArray *guilds; +}; + +typedef struct ncdc_account_ *ncdc_account_t; + +extern GHashTable *accounts; extern char *ncdc_private_dir; +extern void *config; int strwidth(char const *string); char *read_char(FILE *stream); +char *w_convert(wchar_t const *w); +wchar_t* wcsndup(const wchar_t* string, size_t maxlen); +size_t w_strlenv(wchar_t **s); +void w_strfreev(wchar_t **s); +wchar_t **w_tokenise(wchar_t const *w); +wchar_t *w_next_tok(wchar_t const *w); wchar_t const *w_next_word(wchar_t const *w, ssize_t len); #endif diff --git a/ncdc/include/ncdc/textview.h b/ncdc/include/ncdc/textview.h new file mode 100644 index 0000000..30376d0 --- /dev/null +++ b/ncdc/include/ncdc/textview.h @@ -0,0 +1,15 @@ +#ifndef NCDC_TEXTVIEW_H +#define NCDC_TEXTVIEW_H + +#include + +struct ncdc_textview_; +typedef struct ncdc_textview_ *ncdc_textview_t; + +ncdc_textview_t ncdc_textview_new(void); + +void ncdc_textview_append(ncdc_textview_t v, wchar_t const *w); +wchar_t const *ncdc_textview_nthline(ncdc_textview_t v, size_t i); +void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols); + +#endif diff --git a/ncdc/src/cmds.c b/ncdc/src/cmds.c index 7402a58..81a3d1b 100644 --- a/ncdc/src/cmds.c +++ b/ncdc/src/cmds.c @@ -1,11 +1,36 @@ #include ncdc_commands_t cmds[] = { + { L"login", ncdc_cmd_login }, { L"quit", ncdc_cmd_quit }, { NULL, NULL } }; -bool ncdc_cmd_quit(ncdc_mainwindow_t n, wchar_t const *s, size_t len) +bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s) +{ + wchar_t **tokens = NULL; + size_t size = 0; + size_t i = 0; + bool ret = false; + + tokens = w_tokenise(s); + return_if_true(tokens == NULL, false); + + size = w_strlenv(tokens); + + for (i = 0; cmds[i].name != NULL; i++) { + if (wcscmp(cmds[i].name, tokens[0]) == 0) { + ret = cmds[i].handler(n, size, tokens); + break; + } + } + + w_strfreev(tokens); + + return ret; +} + +bool ncdc_cmd_quit(ncdc_mainwindow_t n, size_t ac, wchar_t **av) { exit(0); } diff --git a/ncdc/src/config.c b/ncdc/src/config.c new file mode 100644 index 0000000..6373a29 --- /dev/null +++ b/ncdc/src/config.c @@ -0,0 +1,82 @@ +#include +#include + +static cfg_opt_t account_opts[] = { + CFG_STR("email", NULL, CFGF_NONE), + CFG_STR("password", NULL, CFGF_NONE), + CFG_END() +}; + +static cfg_opt_t opts[] = { + CFG_SEC("account", account_opts, CFGF_TITLE|CFGF_MULTI), + CFG_END() +}; + +struct ncdc_config_ +{ + dc_refable_t ref; + + char *configpath; + cfg_t *cfg; +}; + +static void ncdc_config_free(ncdc_config_t c) +{ + return_if_true(c == NULL,); + + cfg_free(c->cfg); + free(c->configpath); + free(c); +} + +ncdc_config_t ncdc_config_new(void) +{ + ncdc_config_t c = calloc(1, sizeof(struct ncdc_config_)); + return_if_true(c == NULL, NULL); + + c->ref.cleanup = (dc_cleanup_t)ncdc_config_free; + + c->cfg = cfg_init(opts, CFGF_NONE); + if (c->cfg == NULL) { + free(c); + return NULL; + } + + asprintf(&c->configpath, "%s/config", ncdc_private_dir); + if (cfg_parse(c->cfg, c->configpath) == CFG_PARSE_ERROR) { + free(c->configpath); + cfg_free(c->cfg); + free(c); + return NULL; + } + + return c; +} + +dc_account_t ncdc_config_account(ncdc_config_t c, char const *name) +{ + cfg_t *cfg = NULL; + dc_account_t acc = NULL; + size_t i = 0; + + return_if_true(c == NULL || name == NULL, NULL); + + for (i = 0; i < cfg_size(c->cfg, "account"); i++) { + cfg = cfg_getnsec(c->cfg, "account", i); + if (strcmp(cfg_title(cfg), name) == 0) { + /* entry has been found + */ + char const *email = cfg_getstr(cfg, "email"); + char const *password = cfg_getstr(cfg, "password"); + + if (email == NULL || password == NULL) { + continue; + } + + acc = dc_account_new2(email, password); + break; + } + } + + return acc; +} diff --git a/ncdc/src/login.c b/ncdc/src/login.c new file mode 100644 index 0000000..a44f380 --- /dev/null +++ b/ncdc/src/login.c @@ -0,0 +1,41 @@ +#include +#include +#include + +bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) +{ + char *arg = NULL; + bool ret = false; + ncdc_account_t ptr = NULL; + dc_account_t acc = NULL; + + goto_if_true(ac <= 1, cleanup); + + arg = w_convert(av[1]); + goto_if_true(arg == NULL, cleanup); + + if (g_hash_table_lookup(accounts, arg) != NULL) { + LOG(n, L"login: %ls: this account is already logged in", av[1]); + goto cleanup; + } + + acc = ncdc_config_account(config, arg); + if (acc == NULL) { + LOG(n, L"login: %ls: no such account in configuration", av[1]); + goto cleanup; + } + + ptr = calloc(1, sizeof(struct ncdc_account_)); + goto_if_true(ptr == NULL, cleanup); + + ptr->account = acc; + g_hash_table_insert(accounts, arg, ptr); + + ret = true; + +cleanup: + + free(arg); + + return ret; +} diff --git a/ncdc/src/mainwindow.c b/ncdc/src/mainwindow.c index 58c302e..1051bbd 100644 --- a/ncdc/src/mainwindow.c +++ b/ncdc/src/mainwindow.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -36,6 +37,7 @@ struct ncdc_mainwindow_ WINDOW *sep2; ncdc_input_t in; + ncdc_textview_t log; int focus; }; @@ -57,6 +59,7 @@ static void ncdc_mainwindow_free(ncdc_mainwindow_t n) delwin(n->sep2); dc_unref(n->in); + dc_unref(n->log); free(n); } @@ -71,6 +74,8 @@ ncdc_mainwindow_t ncdc_mainwindow_new(void) ptr->in = ncdc_input_new(); ncdc_input_set_callback(ptr->in, ncdc_mainwindow_callback, ptr); + ptr->log = ncdc_textview_new(); + ptr->guilds = newwin(5, 5, 1, 1); ptr->chat = newwin(5, 5, 4, 4); @@ -95,15 +100,11 @@ ncdc_mainwindow_callback(ncdc_input_t i, wchar_t const *s, ncdc_mainwindow_t mainwin = (ncdc_mainwindow_t)arg; if (s[0] == '/') { - size_t i = 0; - wchar_t const *n = w_next_word(s, len); - - for (; cmds[i].name != NULL; i++) { - if (wcsncmp(s+1, cmds[i].name, (n-s-1)) == 0) { - cmds[i].handler(mainwin, s, len); - return true; - } + if (s[1] == '\0') { + return false; } + + return ncdc_dispatch(mainwin, s+1); } return false; @@ -135,9 +136,9 @@ static void ncdc_mainwindow_resize(ncdc_mainwindow_t n) wnoutrefresh(n->sep1); n->chat_h = LINES - n->input_h - 1; - n->chat_w = COLS - n->guilds_w - 1; + n->chat_w = COLS - n->guilds_w - 2; n->chat_y = 0; - n->chat_x = n->guilds_w + 1; + n->chat_x = n->guilds_w + 2; wresize(n->chat, n->chat_h, n->chat_w); mvwin(n->chat, n->chat_y, n->chat_x); @@ -194,6 +195,8 @@ void ncdc_mainwindow_input_ready(ncdc_mainwindow_t n) void ncdc_mainwindow_refresh(ncdc_mainwindow_t n) { wnoutrefresh(n->guilds); + + ncdc_textview_render(n->log, n->chat, n->chat_h, n->chat_w); wnoutrefresh(n->chat); ncdc_input_draw(n->in, n->input); @@ -205,6 +208,18 @@ void ncdc_mainwindow_refresh(ncdc_mainwindow_t n) wnoutrefresh(n->sep2); ncdc_mainwindow_update_focus(n); - - doupdate(); +} + +void ncdc_mainwindow_log(ncdc_mainwindow_t w, wchar_t const *fmt, ...) +{ + va_list lst; + wchar_t buf[256] = {0}; + + return_if_true(w == NULL || fmt == NULL,); + + va_start(lst, fmt); + vswprintf(buf, 255, fmt, lst); + va_end(lst); + + ncdc_textview_append(w->log, buf); } diff --git a/ncdc/src/ncdc.c b/ncdc/src/ncdc.c index b39d3e0..70a5e57 100644 --- a/ncdc/src/ncdc.c +++ b/ncdc/src/ncdc.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -16,14 +17,34 @@ ncdc_mainwindow_t mainwin = NULL; */ static bool done = false; -char *dc_private_dir = NULL; -char *dc_config_file = NULL; +/* all the accounts we have logged into + */ +GHashTable *accounts = NULL; -static GKeyFile *config = NULL; +char *ncdc_private_dir = NULL; +void *config = NULL; dc_loop_t loop = NULL; dc_api_t api = NULL; +static void ncdc_account_free(void *ptr) +{ + ncdc_account_t a = (ncdc_account_t)ptr; + + return_if_true(ptr == NULL,); + + if (a->friends != NULL) { + g_ptr_array_unref(a->friends); + } + + if (a->guilds != NULL) { + g_ptr_array_unref(a->guilds); + } + + dc_unref(a->account); + free(ptr); +} + static void sighandler(int sig) { endwin(); @@ -42,8 +63,16 @@ static void cleanup(void) done = true; + if (accounts != NULL) { + g_hash_table_unref(accounts); + accounts = NULL; + } + dc_unref(api); dc_unref(loop); + + dc_unref(config); + dc_unref(mainwin); } static void stdin_handler(int sock, short what, void *data) @@ -78,31 +107,17 @@ static bool init_everything(void) dc_loop_add_api(loop, api); - config = g_key_file_new(); + config = ncdc_config_new(); return_if_true(config == NULL, false); - g_key_file_load_from_file(config, dc_config_file, 0, NULL); + accounts = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, ncdc_account_free + ); + return_if_true(accounts == NULL, false); return true; } -dc_account_t account_from_config(void) -{ - char const *email = NULL; - char const *password = NULL; - void *ptr = NULL; - - email = g_key_file_get_string(config, "account", "email", NULL); - password = g_key_file_get_string(config, "account", "password", NULL); - - return_if_true(email == NULL || password == NULL, NULL); - - ptr = dc_account_new2(email, password); - dc_account_set_id(ptr, "@me"); - - return ptr; -} - int main(int ac, char **av) { bool done = false; @@ -116,17 +131,15 @@ int main(int ac, char **av) return 3; } - asprintf(&dc_private_dir, "%s/.ndc", getenv("HOME")); - if (mkdir(dc_private_dir, 0755) < 0) { + asprintf(&ncdc_private_dir, "%s/.ncdc", getenv("HOME")); + if (mkdir(ncdc_private_dir, 0755) < 0) { if (errno != EEXIST) { - fprintf(stderr, "failed to make %s: %s\n", dc_private_dir, + fprintf(stderr, "failed to make %s: %s\n", ncdc_private_dir, strerror(errno)); return 3; } } - asprintf(&dc_config_file, "%s/config", dc_private_dir); - if (!init_everything()) { return 3; } diff --git a/ncdc/src/textview.c b/ncdc/src/textview.c new file mode 100644 index 0000000..3c0cf19 --- /dev/null +++ b/ncdc/src/textview.c @@ -0,0 +1,82 @@ +#include +#include + +struct ncdc_textview_ +{ + dc_refable_t ref; + GPtrArray *par; +}; + +static void ncdc_textview_free(ncdc_textview_t v) +{ + return_if_true(v == NULL,); + + g_ptr_array_unref(v->par); + free(v); +} + +ncdc_textview_t ncdc_textview_new(void) +{ + ncdc_textview_t p = calloc(1, sizeof(struct ncdc_textview_)); + return_if_true(p == NULL, NULL); + + p->ref.cleanup = (dc_cleanup_t)ncdc_textview_free; + + p->par = g_ptr_array_new_with_free_func(free); + if (p->par == NULL) { + free(p); + return NULL; + } + + return p; +} + +void ncdc_textview_append(ncdc_textview_t v, wchar_t const *w) +{ + return_if_true(v == NULL || w == NULL,); + + wchar_t const *p = w; + wchar_t const *last = w; + + while ((p = wcschr(p, '\n')) != NULL) { + wchar_t *dup = wcsndup(p, p - last); + g_ptr_array_add(v->par, dup); + last = p; + } + + g_ptr_array_add(v->par, wcsdup(last)); +} + +wchar_t const *ncdc_textview_nthline(ncdc_textview_t v, size_t idx) +{ + return_if_true(v == NULL, NULL); + return_if_true(idx >= v->par->len, NULL); + return g_ptr_array_index(v->par, idx); +} + +void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols) +{ + ssize_t i = 0, needed_lines = 0, atline = 0; + + werase(win); + + if (v->par->len == 0) { + return; + } + + for (i = v->par->len-1; i >= 0; i--) { + wchar_t const *w = ncdc_textview_nthline(v, i); + size_t sz = wcslen(w); + + needed_lines += (sz / cols); + if ((sz / cols) == 0) { + needed_lines += 1; + } + atline = (lines - needed_lines); + mvwaddwstr(win, atline, 0, ncdc_textview_nthline(v, i)); + + if (needed_lines >= lines) { + break; + } + } +} diff --git a/ncdc/src/util.c b/ncdc/src/util.c index 836cf37..70c0a71 100644 --- a/ncdc/src/util.c +++ b/ncdc/src/util.c @@ -14,40 +14,110 @@ wchar_t const *w_next_word(wchar_t const *w, ssize_t len) return w+i; } -char *read_char(FILE *stream) +wchar_t* wcsndup(const wchar_t* string, size_t maxlen) { - uint8_t str[7] = {0}; - int len = 0, i = 0; + size_t n = wcsnlen(string, maxlen) + 1; + wchar_t* r = calloc(n, sizeof(wchar_t)); + return r == NULL ? NULL : wmemcpy(r, string, n); +} - /* check if we need more +size_t w_strlenv(wchar_t **s) +{ + size_t i = 0; + for (; s[i] != NULL; i++) + ; + return i; +} + +void w_strfreev(wchar_t **s) +{ + size_t i = 0; + + return_if_true(s == NULL,); + + for (; s[i] != NULL; i++) { + free(s[i]); + } + + free(s); +} + +wchar_t **w_tokenise(wchar_t const *w) +{ + GPtrArray *array = g_ptr_array_new(); + wchar_t const *item = w; + wchar_t *dup = NULL; + size_t len = 0, origlen = 0; + + while ((dup = w_next_tok(item)) != NULL) { + len = origlen = wcslen(dup); + + if (*dup == '"') { + memmove(dup, dup+1, sizeof(wchar_t)*(len-1)); + --len; + } + + if (len > 0 && dup[len-1] == '"') { + dup[len-1] = '\0'; + --len; + } + + g_ptr_array_add(array, dup); + item += origlen; + } + + g_ptr_array_add(array, NULL); + + return (wchar_t**)g_ptr_array_free(array, FALSE); +} + +char *w_convert(wchar_t const *w) +{ + size_t sz = 0; + char *ptr = NULL; + + sz = wcstombs(NULL, w, 0); + + ptr = calloc(sz+1, sizeof(char)); + return_if_true(ptr == NULL, NULL); + + wcstombs(ptr, w, sz); + return ptr; +} + +wchar_t *w_next_tok(wchar_t const *w) +{ + bool quotes = false; + wchar_t const *start = NULL; + + /* skip first white spaces if there are any */ - str[0] = (uint8_t)fgetc(stream); - len = g_utf8_skip[str[0]]; + for (; *w != '\0' && iswspace(*w); w++) + ; - for (i = 1; i < len; i++) { - str[i] = (uint8_t)fgetc(stream); - } - str[len] = '\0'; - - return strdup((char const *)str); -} - -int strwidth(char const *string) -{ - size_t needed = mbstowcs(NULL, string, 0) + 1; - wchar_t *wcstring = calloc(needed, sizeof(wchar_t)); - size_t ret = 0; - - return_if_true(wcstring == NULL, -1); - - ret = mbstowcs(wcstring, string, needed); - if (ret == (size_t)-1) { - free(wcstring); - return -1; + if (*w == '\0') { + return NULL; } - int width = wcswidth(wcstring, needed); - free(wcstring); + start = w; + quotes = (*w == '"'); - return width; + do { + if (iswspace(*w) && !quotes) { + --w; + break; + } + + if (*w == '"' && *(w-1) != '\\' && quotes) { + break; + } + + if (*w == '\0') { + break; + } + + ++w; + } while (1); + + return wcsndup(start, (w - start)); }