URI: 
       tStandardize error handling - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 488ed473efaa6153752374f9c27f67fe45dc4823
   DIR parent 480c476bee3efca10facb5ff2be6863bf112b72a
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu, 23 Jun 2022 12:37:01 +0200
       
       Standardize error handling
       
       Diffstat:
         M src/box.c                           |      97 +++++++++++++++++++------------
         M src/box.h                           |       5 ++++-
         M src/button.c                        |      18 +++++++++++-------
         M src/button.h                        |       5 ++++-
         A src/err.c                           |      28 ++++++++++++++++++++++++++++
         A src/err.h                           |      31 +++++++++++++++++++++++++++++++
         M src/grid.c                          |     154 ++++++++++++++++++++-----------
         M src/grid.h                          |      10 ++++++----
         M src/label.c                         |      18 +++++++++++-------
         M src/label.h                         |      11 +++++++----
         M src/ltkd.c                          |      91 ++++++++++++++++++++-----------
         M src/menu.c                          |     218 ++++++++++++++++---------------
         M src/menu.h                          |      10 +++++-----
         M src/util.c                          |       5 ++---
         M src/util.h                          |       2 +-
         M src/widget.c                        |      38 ++++++++++++++++++-------------
         M src/widget.h                        |      10 ++++++----
       
       17 files changed, 472 insertions(+), 279 deletions(-)
       ---
   DIR diff --git a/src/box.c b/src/box.c
       t@@ -36,9 +36,9 @@ 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, char **errstr);
       -static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, char **errstr);
       -/* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, char **errstr); */
       +static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short 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);
        static int ltk_box_mouse_press(ltk_widget *self, ltk_button_event *event);
        static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y);
       t@@ -67,24 +67,24 @@ static int ltk_box_cmd_add(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        static int ltk_box_cmd_remove(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        /*
        static int ltk_box_cmd_clear
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        */
        static int ltk_box_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        
        static void
        ltk_box_draw(ltk_widget *self, ltk_rect clip) {
       t@@ -229,9 +229,9 @@ 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, char **errstr) {
       +ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, ltk_error *err) {
                if (widget->parent) {
       -                *errstr = "Widget already inside a container.\n";
       +                err->type = ERR_WIDGET_IN_CONTAINER;
                        return 1;
                }
                if (box->num_widgets >= box->num_alloc) {
       t@@ -263,12 +263,12 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short
        }
        
        static int
       -ltk_box_remove(ltk_widget *widget, ltk_widget *self, char **errstr) {
       +ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err) {
                ltk_box *box = (ltk_box *)self;
                int sc_w = box->sc->widget.rect.w;
                int sc_h = box->sc->widget.rect.h;
                if (widget->parent != (ltk_widget *)box) {
       -                *errstr = "Widget isn't contained in given box.\n";
       +                err->type = ERR_WIDGET_NOT_IN_CONTAINER;
                        return 1;
                }
                widget->parent = NULL;
       t@@ -303,7 +303,7 @@ ltk_box_remove(ltk_widget *widget, ltk_widget *self, char **errstr) {
                        }
                }
        
       -        *errstr = "Widget isn't contained in given box.\n";
       +        err->type = ERR_WIDGET_NOT_IN_CONTAINER;
                return 1;
        }
        
       t@@ -347,20 +347,24 @@ ltk_box_cmd_add(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                const char *c;
                ltk_box *box;
                ltk_widget *widget;
        
                int sticky = 0;
                if (num_tokens != 4 && num_tokens != 5) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_BOX, errstr);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, errstr);
       -        if (!box || !widget) {
       -                *errstr = "Invalid widget ID.\n";
       +        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_BOX, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        if (!box) {
       +                err->arg = 1;
       +                return 1;
       +        } else if (!widget) {
       +                err->arg = 3;
                        return 1;
                }
        
       t@@ -375,13 +379,18 @@ ltk_box_cmd_add(
                                } else if (*c == 'w') {
                                        sticky |= LTK_STICKY_LEFT;
                                } else {
       -                                *errstr = "Invalid sticky specification.\n";
       +                                err->type = ERR_INVALID_ARGUMENT;
       +                                err->arg = 4;
                                        return 1;
                                }
                        }
                }
        
       -        return ltk_box_add(window, widget, box, sticky, errstr);
       +        if (ltk_box_add(window, widget, box, sticky, err)) {
       +                err->arg = 3;
       +                return 1;
       +        }
       +        return 0;
        }
        
        /* box <box id> remove <widget id> */
       t@@ -390,23 +399,31 @@ ltk_box_cmd_remove(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_box *box;
                ltk_widget *widget;
        
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_BOX, errstr);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, errstr);
       -        if (!box || !widget) {
       -                *errstr = "Invalid widget ID.\n";
       +        box = (ltk_box *)ltk_get_widget(tokens[1], LTK_BOX, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        if (!box) {
       +                err->arg = 1;
       +                return 1;
       +        } else if (!widget) {
       +                err->arg = 3;
                        return 1;
                }
        
       -        return ltk_box_remove(widget, (ltk_widget *)box, errstr);
       +        if (ltk_box_remove(widget, (ltk_widget *)box, err)) {
       +                err->arg = 3;
       +                return 1;
       +        }
       +        return 0;
        }
        
        /* box <box id> create <orientation> */
       t@@ -415,16 +432,19 @@ ltk_box_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                ltk_box *box;
                ltk_orientation orient;
        
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       +        /* FIXME: race condition */
                if (!ltk_widget_id_free(tokens[1])) {
       -                *errstr = "Widget ID already taken.\n";
       +                err->type = ERR_WIDGET_ID_IN_USE;
       +                err->arg = 1;
                        return 1;
                }
                if (!strcmp(tokens[3], "horizontal")) {
       t@@ -432,7 +452,8 @@ ltk_box_cmd_create(
                } else if (!strcmp(tokens[3], "vertical")) {
                        orient = LTK_VERTICAL;
                } else {
       -                *errstr = "Invalid orientation.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 3;
                        return 1;
                }
                box = (ltk_box *)ltk_box_create(window, tokens[1], orient);
       t@@ -447,19 +468,21 @@ ltk_box_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                if (num_tokens < 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (strcmp(tokens[2], "add") == 0) {
       -                return ltk_box_cmd_add(window, tokens, num_tokens, errstr);
       +                return ltk_box_cmd_add(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "remove") == 0) {
       -                return ltk_box_cmd_remove(window, tokens, num_tokens, errstr);
       +                return ltk_box_cmd_remove(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "create") == 0) {
       -                return ltk_box_cmd_create(window, tokens, num_tokens, errstr);
       +                return ltk_box_cmd_create(window, tokens, num_tokens, err);
                } else {
       -                *errstr = "Invalid command.\n";
       +                err->type = ERR_INVALID_COMMAND;
       +                err->arg = -1;
                        return 1;
                }
        
   DIR diff --git a/src/box.h b/src/box.h
       t@@ -17,8 +17,11 @@
        #ifndef _LTK_BOX_H_
        #define _LTK_BOX_H_
        
       +/* FIXME: include everything here */
        /* Requires the following includes: "scrollbar.h", "rect.h", "widget.h", "ltk.h" */
        
       +#include "err.h"
       +
        typedef struct {
                ltk_widget widget;
                ltk_scrollbar *sc;
       t@@ -30,6 +33,6 @@ typedef struct {
                ltk_orientation orient;
        } ltk_box;
        
       -int ltk_box_cmd(ltk_window *window, char **tokens, size_t num_tokens, char **errstr);
       +int ltk_box_cmd(ltk_window *window, char **tokens, size_t num_tokens, ltk_error *);
        
        #endif /* _LTK_BOX_H_ */
   DIR diff --git a/src/button.c b/src/button.c
       t@@ -212,14 +212,16 @@ ltk_button_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                ltk_button *button;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (!ltk_widget_id_free(tokens[1])) {
       -                *errstr = "Widget ID already taken.\n";
       +                err->type = ERR_WIDGET_ID_IN_USE;
       +                err->arg = 1;
                        return 1;
                }
                button = ltk_button_create(window, tokens[1], tokens[3]);
       t@@ -234,15 +236,17 @@ ltk_button_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                if (num_tokens < 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (strcmp(tokens[2], "create") == 0) {
       -                return ltk_button_cmd_create(window, tokens, num_tokens, errstr);
       +                return ltk_button_cmd_create(window, tokens, num_tokens, err);
                } else {
       -                *errstr = "Invalid command.\n";
       +                err->type = ERR_INVALID_COMMAND;
       +                err->arg = -1;
                        return 1;
                }
        
   DIR diff --git a/src/button.h b/src/button.h
       t@@ -19,6 +19,8 @@
        
        /* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
        
       +#include "err.h"
       +
        typedef struct {
                ltk_widget widget;
                ltk_text_line *tl;
       t@@ -32,6 +34,7 @@ int ltk_button_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *
       +);
        
        #endif /* _LTK_BUTTON_H_ */
   DIR diff --git a/src/err.c b/src/err.c
       t@@ -0,0 +1,28 @@
       +#include "err.h"
       +
       +static const char *errtable[] = {
       +        "None",
       +        "Widget already in container",
       +        "Widget not in container",
       +        "Widget id already in use",
       +        "Invalid number of arguments",
       +        "Invalid argument",
       +        "Invalid index",
       +        "Invalid widget id",
       +        "Invalid widget type",
       +        "Invalid command",
       +        "Menu is not submenu",
       +        "Menu entry already contains submenu",
       +        "Invalid grid position",
       +};
       +
       +#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
       +
       +const char *
       +errtype_to_string(ltk_errtype type) {
       +        if (type < 0 || type >= LENGTH(errtable)) {
       +                /* that's a funny error, now isn't it? */
       +                return "Invalid error";
       +        }
       +        return errtable[type];
       +}
   DIR diff --git a/src/err.h b/src/err.h
       t@@ -0,0 +1,31 @@
       +#ifndef LTK_ERR_H
       +#define LTK_ERR_H
       +
       +/* WARNING: THIS NEEDS TO BE KEPT IN SYNC WITH THE TABLE IN err.c! */
       +/* (also, the explicit value setting is redundant, but just in case) */
       +typedef enum {
       +        ERR_NONE = 0,
       +        ERR_WIDGET_IN_CONTAINER = 1,
       +        ERR_WIDGET_NOT_IN_CONTAINER = 2,
       +        ERR_WIDGET_ID_IN_USE = 3,
       +        ERR_INVALID_NUMBER_OF_ARGUMENTS = 4,
       +        ERR_INVALID_ARGUMENT = 5,
       +        ERR_INVALID_INDEX = 6,
       +        ERR_INVALID_WIDGET_ID = 7,
       +        ERR_INVALID_WIDGET_TYPE = 8,
       +        ERR_INVALID_COMMAND = 9,
       +        /* widget specific */
       +        ERR_MENU_NOT_SUBMENU = 10,
       +        ERR_MENU_ENTRY_CONTAINS_SUBMENU = 11,
       +        ERR_GRID_INVALID_POSITION = 12,
       +} ltk_errtype;
       +
       +typedef struct {
       +        ltk_errtype type;
       +        /* corresponding argument, -1 if none */
       +        int arg;
       +} ltk_error;
       +
       +const char *errtype_to_string(ltk_errtype type);
       +
       +#endif /* LTK_ERR_H */
   DIR diff --git a/src/grid.c b/src/grid.c
       t@@ -46,8 +46,8 @@ 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, char **errstr);
       -static int ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, char **errstr);
       +    int row, int column, int row_span, int column_span, unsigned short 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);
        static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y);
       t@@ -76,27 +76,27 @@ static int ltk_grid_cmd_add(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        static int ltk_grid_cmd_ungrid(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        static int ltk_grid_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        static int ltk_grid_cmd_set_row_weight(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        static int ltk_grid_cmd_set_column_weight(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err);
        
        static void
        ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight) {
       t@@ -317,13 +317,13 @@ 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, char **errstr) {
       +    int row, int column, int row_span, int column_span, unsigned short sticky, ltk_error *err) {
                if (widget->parent) {
       -                *errstr = "Widget already inside a container.\n";
       +                err->type = ERR_WIDGET_IN_CONTAINER;
                        return 1;
                }
                if (row + row_span > grid->rows || column + column_span > grid->columns) {
       -                *errstr = "Invalid row or column.\n";
       +                err->type = ERR_GRID_INVALID_POSITION;
                        return 1;
                }
                widget->sticky = sticky;
       t@@ -344,10 +344,10 @@ ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
        }
        
        static int
       -ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, char **errstr) {
       +ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err) {
                ltk_grid *grid = (ltk_grid *)self;
                if (widget->parent != (ltk_widget *)grid) {
       -                *errstr = "Widget isn't gridded in given grid.\n";
       +                err->type = ERR_WIDGET_NOT_IN_CONTAINER;
                        return 1;
                }
                widget->parent = NULL;
       t@@ -402,7 +402,7 @@ ltk_grid_cmd_add(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                const char *c;
                ltk_grid *grid;
                ltk_widget *widget;
       t@@ -410,33 +410,42 @@ ltk_grid_cmd_add(
        
                int row, column, row_span, column_span, sticky = 0;
                if (num_tokens != 8 && num_tokens != 9) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, errstr);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, errstr);
       -        if (!grid || !widget) {
       -                *errstr = "Invalid widget ID.\n";
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        if (!grid) {
       +                err->arg = 1;
       +                return 1;
       +        } else if (!widget) {
       +                err->arg = 3;
                        return 1;
                }
       +
                row         = ltk_strtonum(tokens[4], 0, grid->rows - 1, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid row number.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 4;
                        return 1;
                }
                column      = ltk_strtonum(tokens[5], 0, grid->columns - 1, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid row number.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 5;
                        return 1;
                }
                row_span    = ltk_strtonum(tokens[6], 1, grid->rows, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid row span.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 6;
                        return 1;
                }
                column_span = ltk_strtonum(tokens[7], 1, grid->columns, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid column span.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 7;
                        return 1;
                }
        
       t@@ -451,13 +460,19 @@ ltk_grid_cmd_add(
                                } else if (*c == 'w') {
                                        sticky |= LTK_STICKY_LEFT;
                                } else {
       -                                *errstr = "Invalid sticky specification.\n";
       +                                err->type = ERR_INVALID_ARGUMENT;
       +                                err->arg = 8;
                                        return 1;
                                }
                        }
                }
        
       -        return ltk_grid_add(window, widget, grid, row, column, row_span, column_span, sticky, errstr);
       +        /* FIXME: better error reporting for invalid grid position */
       +        if (ltk_grid_add(window, widget, grid, row, column, row_span, column_span, sticky, err)) {
       +                err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 3 : -1;
       +                return 1;
       +        }
       +        return 0;
        }
        
        /* grid <grid id> remove <widget id> */
       t@@ -466,18 +481,29 @@ ltk_grid_cmd_ungrid(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_grid *grid;
                ltk_widget *widget;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
       +                return 1;
       +        }
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       +        widget = ltk_get_widget(tokens[3], LTK_WIDGET, err);
       +        if (!grid) {
       +                err->arg = 1;
       +                return 1;
       +        } else if (!widget) {
       +                err->arg = 3;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, errstr);
       -        widget = ltk_get_widget(tokens[3], LTK_WIDGET, errstr);
       -        if (!grid || !widget) return 1;
       -        return ltk_grid_ungrid(widget, (ltk_widget *)grid, errstr);
       +        if (ltk_grid_ungrid(widget, (ltk_widget *)grid, err)) {
       +                err->arg = 3;
       +                return 1;
       +        }
       +        return 0;
        }
        
        /* grid <grid id> create <rows> <columns> */
       t@@ -486,26 +512,30 @@ ltk_grid_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                int rows, columns;
                ltk_grid *grid;
                const char *errstr_num;
                if (num_tokens != 5) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (!ltk_widget_id_free(tokens[1])) {
       -                *errstr = "Widget ID already taken.\n";
       +                err->type = ERR_WIDGET_ID_IN_USE;
       +                err->arg = 1;
                        return 1;
                }
                rows    = ltk_strtonum(tokens[3], 1, 64, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid number of rows.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 3;
                        return 1;
                }
                columns = ltk_strtonum(tokens[4], 1, 64, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid number of columns.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 4;
                        return 1;
                }
                grid = ltk_grid_create(window, tokens[1], rows, columns);
       t@@ -520,25 +550,31 @@ ltk_grid_cmd_set_row_weight(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_grid *grid;
                int row, weight;
                const char *errstr_num;
                if (num_tokens != 5) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
       +                return 1;
       +        }
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       +        if (!grid) {
       +                err->arg = 1;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, errstr);
       -        if (!grid) return 1;
                row    = ltk_strtonum(tokens[3], 0, grid->rows, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid row number.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 3;
                        return 1;
                }
                weight = ltk_strtonum(tokens[4], 0, 64, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid row weight.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 4;
                        return 1;
                }
                ltk_grid_set_row_weight(grid, row, weight);
       t@@ -552,25 +588,31 @@ ltk_grid_cmd_set_column_weight(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_grid *grid;
                int column, weight;
                const char *errstr_num;
                if (num_tokens != 5) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
       +                return 1;
       +        }
       +        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, err);
       +        if (!grid) {
       +                err->arg = 1;
                        return 1;
                }
       -        grid = (ltk_grid *)ltk_get_widget(tokens[1], LTK_GRID, errstr);
       -        if (!grid) return 1;
                column = ltk_strtonum(tokens[3], 0, grid->columns, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid column number.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 3;
                        return 1;
                }
                weight = ltk_strtonum(tokens[4], 0, 64, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid column weight.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 4;
                        return 1;
                }
                ltk_grid_set_column_weight(grid, column, weight);
       t@@ -584,23 +626,25 @@ ltk_grid_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                if (num_tokens < 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (strcmp(tokens[2], "add") == 0) {
       -                return ltk_grid_cmd_add(window, tokens, num_tokens, errstr);
       +                return ltk_grid_cmd_add(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "ungrid") == 0) {
       -                return ltk_grid_cmd_ungrid(window, tokens, num_tokens, errstr);
       +                return ltk_grid_cmd_ungrid(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "create") == 0) {
       -                return ltk_grid_cmd_create(window, tokens, num_tokens, errstr);
       +                return ltk_grid_cmd_create(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "set-row-weight") == 0) {
       -                return ltk_grid_cmd_set_row_weight(window, tokens, num_tokens, errstr);
       +                return ltk_grid_cmd_set_row_weight(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "set-column-weight") == 0) {
       -                return ltk_grid_cmd_set_column_weight(window, tokens, num_tokens, errstr);
       +                return ltk_grid_cmd_set_column_weight(window, tokens, num_tokens, err);
                } else {
       -                *errstr = "Invalid command.\n";
       +                err->type = ERR_INVALID_COMMAND;
       +                err->arg = -1;
                        return 1;
                }
        
   DIR diff --git a/src/grid.h b/src/grid.h
       t@@ -14,11 +14,13 @@
         * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
         */
        
       -#ifndef _LTK_GRID_H_
       -#define _LTK_GRID_H_
       +#ifndef LTK_GRID_H
       +#define LTK_GRID_H
        
        /* Requires the following includes: "rect.h", "widget.h", "ltk.h" */
        
       +#include "err.h"
       +
        /*
         * Struct to represent a grid widget.
         */
       t@@ -35,6 +37,6 @@ typedef struct {
                int *column_pos;
        } ltk_grid;
        
       -int ltk_grid_cmd(ltk_window *window, char **tokens, size_t num_tokens, char **errstr);
       +int ltk_grid_cmd(ltk_window *window, char **tokens, size_t num_tokens, ltk_error *err);
        
       -#endif
       +#endif /* LTK_GRID_H */
   DIR diff --git a/src/label.c b/src/label.c
       t@@ -151,14 +151,16 @@ ltk_label_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                ltk_label *label;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (!ltk_widget_id_free(tokens[1])) {
       -                *errstr = "Widget ID already taken.\n";
       +                err->type = ERR_WIDGET_ID_IN_USE;
       +                err->arg = 1;
                        return 1;
                }
                label = ltk_label_create(window, tokens[1], tokens[3]);
       t@@ -173,15 +175,17 @@ ltk_label_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                if (num_tokens < 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (strcmp(tokens[2], "create") == 0) {
       -                return ltk_label_cmd_create(window, tokens, num_tokens, errstr);
       +                return ltk_label_cmd_create(window, tokens, num_tokens, err);
                } else {
       -                *errstr = "Invalid command.\n";
       +                err->type = ERR_INVALID_COMMAND;
       +                err->arg = -1;
                        return 1;
                }
        
   DIR diff --git a/src/label.h b/src/label.h
       t@@ -14,11 +14,13 @@
         * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
         */
        
       -#ifndef _LTK_LABEL_H_
       -#define _LTK_LABEL_H_
       +#ifndef LTK_LABEL_H
       +#define LTK_LABEL_H
        
        /* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
        
       +#include "err.h"
       +
        typedef struct {
                ltk_widget widget;
                ltk_text_line *tl;
       t@@ -32,6 +34,7 @@ int ltk_label_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr);
       +    ltk_error *err
       +);
        
       -#endif /* _LTK_LABEL_H_ */
       +#endif /* LTK_LABEL_H */
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -126,8 +126,9 @@ 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 queue_sock_write(struct ltk_sock_info *sock, const char *str, int len);
       +static int queue_sock_write_fmt(struct ltk_sock_info *sock, const char *fmt, ...);
        static int tokenize_command(struct ltk_sock_info *sock);
       -static int ltk_set_root_widget_cmd(ltk_window *window, char **tokens, int num_tokens, char **errstr);
       +static int ltk_set_root_widget_cmd(ltk_window *window, char **tokens, int num_tokens, ltk_error *err);
        static void process_commands(ltk_window *window, struct ltk_sock_info *sock);
        static int add_client(int fd);
        static int listen_sock(const char *sock_path);
       t@@ -471,14 +472,18 @@ ltk_set_root_widget_cmd(
            ltk_window *window,
            char **tokens,
            int num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                ltk_widget *widget;
                if (num_tokens != 2) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
       +                return 1;
       +        }
       +        widget = ltk_get_widget(tokens[1], LTK_WIDGET, err);
       +        if (!widget) {
       +                err->arg = 1;
                        return 1;
                }
       -        widget = ltk_get_widget(tokens[1], LTK_WIDGET, errstr);
       -        if (!widget) return 1;
                window->root_widget = widget;
                ltk_window_invalidate_rect(window, widget->rect);
                widget->rect.w = window->rect.w;
       t@@ -1050,8 +1055,7 @@ static int
        read_sock(struct ltk_sock_info *sock) {
                int nread;
                char *old = sock->read;
       -        int ret = ltk_grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE);
       -        if (ret) return -1; /* fixme: errno? */
       +        ltk_grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE);
                /* move tokens to new addresses - this was added as an
                   afterthought and really needs to be cleaned up */
                if (sock->read != old) {
       t@@ -1097,25 +1101,28 @@ write_sock(struct ltk_sock_info *sock) {
                return 0;
        }
        
       -/* Queue `str` to be written to the socket. If len is < 0, it is set to `strlen(str)`.
       -   Returns -1 on error, 0 otherwise.
       -   Note: The string must include all '\n', etc. as defined in the protocol. This
       -   function just adds the given string verbatim. */
       -static int
       -queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) {
       +static void
       +move_write_pos(struct ltk_sock_info *sock) {
                if (sock->write_cur > 0) {
                        memmove(sock->to_write, sock->to_write + sock->write_cur,
                                sock->write_len - sock->write_cur);
                        sock->write_len -= sock->write_cur;
                        sock->write_cur = 0;
                }
       +}
        
       +/* Queue `str` to be written to the socket. If len is < 0, it is set to `strlen(str)`.
       +   Returns -1 on error, 0 otherwise.
       +   Note: The string must include all '\n', etc. as defined in the protocol. This
       +   function just adds the given string verbatim. */
       +static int
       +queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) {
       +        move_write_pos(sock);
                if (len < 0)
                        len = strlen(str);
        
       -        if (sock->write_alloc - sock->write_len < len &&
       -            ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len))
       -                return -1;
       +        if (sock->write_alloc - sock->write_len < len)
       +                ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len);
        
                (void)strncpy(sock->to_write + sock->write_len, str, len);
                sock->write_len += len;
       t@@ -1125,6 +1132,28 @@ queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) {
                return 0;
        }
        
       +static int
       +queue_sock_write_fmt(struct ltk_sock_info *sock, const char *fmt, ...) {
       +        move_write_pos(sock);
       +        va_list args;
       +        va_start(args, fmt);
       +        int len = vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
       +        if (len < 0) {
       +                ltk_fatal("Unable to print formatted text to socket.\n");
       +        } else if (len >= sock->write_alloc - sock->write_len) {
       +                va_end(args);
       +                va_start(args, fmt);
       +                /* snprintf always writes '\0', even though we don't actually need it here */
       +                ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + 1);
       +                vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
       +        }
       +        va_end(args);
       +        sock->write_len += len;
       +        sock_write_available = 1;
       +
       +        return 0;
       +}
       +
        /* Tokenize the current read buffer in `sock`.
           Returns 0 immediately if the end of a command was encountered, 1 otherwise. */
        static int
       t@@ -1169,7 +1198,7 @@ static void
        process_commands(ltk_window *window, struct ltk_sock_info *sock) {
                char **tokens;
                int num_tokens;
       -        char *errstr;
       +        ltk_error errdetail = {ERR_NONE, -1};
                int err;
                while (!tokenize_command(sock)) {
                        err = 0;
       t@@ -1178,36 +1207,34 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) {
                        if (num_tokens < 1)
                                continue;
                        if (strcmp(tokens[0], "grid") == 0) {
       -                        err = ltk_grid_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_grid_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "box") == 0) {
       -                        err = ltk_box_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_box_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "button") == 0) {
       -                        err = ltk_button_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_button_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "label") == 0) {
       -                        err = ltk_label_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_label_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "menu") == 0) {
       -                        err = ltk_menu_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "submenu") == 0) {
       -                        err = ltk_menu_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "menuentry") == 0) {
       -                        err = ltk_menuentry_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_menuentry_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "set-root-widget") == 0) {
       -                        err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errstr);
       -/*
       -                } else if (strcmp(tokens[0], "draw") == 0) {
       -                        err = ltk_draw_cmd(window, tokens, num_tokens, &errstr);
       -*/
       +                        err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errdetail);
                        } else if (strcmp(tokens[0], "quit") == 0) {
                                ltk_quit(window);
                        } else if (strcmp(tokens[0], "destroy") == 0) {
       -                        err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errstr);
       +                        err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errdetail);
                        } else {
       -                        errstr = "Invalid command.\n";
       +                        errdetail.type = ERR_INVALID_COMMAND;
       +                        errdetail.arg = -1;
                                err = 1;
                        }
                        sock->tokens.num_tokens = 0;
                        if (err) {
       -                        if (queue_sock_write(sock, errstr, -1) < 0)
       +                        const char *errmsg = errtype_to_string(errdetail.type);
       +                        if (queue_sock_write_fmt(sock, "err %d arg %d msg \"%s\"\n", errdetail.type, errdetail.arg, errmsg) < 0)
                                        ltk_fatal("Unable to queue socket write.\n");
                        }
                }
   DIR diff --git a/src/menu.c b/src/menu.c
       t@@ -111,10 +111,10 @@ static void ltk_menuentry_destroy(ltk_widget *self, int shallow);
        static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state);
        static int ltk_menuentry_mouse_release(ltk_widget *self, ltk_button_event *event);
        static void ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry);
       -static void ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, char **errstr);
       +static int ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err);
        static void ltk_menuentry_detach_submenu(ltk_menuentry *e);
        
       -static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, char **errstr);
       +static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err);
        
        #define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_MENU && ((ltk_menu *)e->widget.parent)->is_submenu)
        
       t@@ -305,7 +305,6 @@ ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) {
                } else if (((self->state & LTK_PRESSED) ||
                           ((self->state & LTK_ACTIVE) && (in_submenu || submenus_opened))) &&
                           e->submenu && e->submenu->widget.hidden) {
       -                printf("popup: %s, %d, %d, %d\n", self->id, submenus_opened, self->state, self->parent->hidden);
                        popup_active_menu(e);
                        if (self->parent && self->parent->vtable->type == LTK_MENU)
                                ((ltk_menu *)self->parent)->popup_submenus = 1;
       t@@ -389,7 +388,6 @@ ltk_menu_draw(ltk_widget *self, ltk_rect clip) {
                                ltk_menuentry_draw(&menu->entries[i]->widget, clip_final);
                                i++;
                        } else {
       -                        ltk_widget *widget = &menu->entries[i]->widget;
                                ltk_menuentry_draw(&menu->entries[i]->widget, clip_final);
                        }
                }
       t@@ -460,7 +458,6 @@ ltk_menu_resize(ltk_widget *self) {
                int mbw = t->border_width;
                int cur_abs_x = -(int)menu->x_scroll_offset + rect.x + start_x + t->pad;
                int cur_abs_y = -(int)menu->y_scroll_offset + rect.y + start_y + t->pad;
       -        printf("%d, %d\n", self->rect.x, self->rect.w);
        
                for (size_t i = 0; i < menu->num_entries; i++) {
                        ltk_menuentry *e = menu->entries[i];
       t@@ -667,7 +664,6 @@ ltk_menu_hide(ltk_widget *self) {
                /* FIXME: this is really ugly/hacky */
                if (menu->unpopup_submenus_on_hide && self->parent && self->parent->vtable->type == LTK_MENUENTRY &&
                    self->parent->parent && self->parent->parent->vtable->type == LTK_MENU) {
       -                printf("hide: %s\n", self->id);
                        ((ltk_menu *)self->parent->parent)->popup_submenus = 0;
                }
                menu->unpopup_submenus_on_hide = 1;
       t@@ -951,36 +947,37 @@ ltk_menuentry_destroy(ltk_widget *self, int shallow) {
                ltk_free(e);
        }
        
       -static void
       -ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx, char **errstr) {
       +static int
       +ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx, ltk_error *err) {
                if (entry->widget.parent) {
       -                *errstr = "Entry already part of other menu.\n";
       -                return;
       +                err->type = ERR_WIDGET_IN_CONTAINER;
       +                return 1;
                }
                if (insert_entry(menu, entry, idx)) {
       -                *errstr = "Illegal index.\n";
       -                return;
       +                err->type = ERR_INVALID_INDEX;
       +                return 1;
                }
                entry->widget.parent = &menu->widget;
                ltk_menuentry_recalc_ideal_size(entry);
                recalc_ideal_menu_size(&menu->widget, NULL);
                menu->widget.dirty = 1;
       +        return 0;
        }
        
       -static void
       -ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry, char **errstr) {
       -        ltk_menu_insert_entry(menu, entry, menu->num_entries, errstr);
       +static int
       +ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry, ltk_error *err) {
       +        return ltk_menu_insert_entry(menu, entry, menu->num_entries, err);
        }
        
        /* FIXME: maybe allow any menu and just change is_submenu (also need to recalculate size then) */
       -static void
       -ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, char **errstr) {
       +static int
       +ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err) {
                if (!submenu->is_submenu) {
       -                *errstr = "Not a submenu.\n";
       -                return;
       +                err->type = ERR_MENU_NOT_SUBMENU;
       +                return 1;
                } else if (e->submenu) {
       -                *errstr = "Menu entry already has attached submenu.\n";
       -                return;
       +                err->type = ERR_MENU_ENTRY_CONTAINS_SUBMENU;
       +                return 1;
                }
                e->submenu = submenu;
                ltk_menuentry_recalc_ideal_size(e);
       t@@ -991,6 +988,7 @@ ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, char **errstr)
                }
                if (!e->widget.hidden)
                        ltk_window_invalidate_rect(e->widget.window, e->widget.rect);
       +        return 0;
        }
        
        /* FIXME: hide all entries when menu hidden? */
       t@@ -1005,9 +1003,9 @@ shrink_entries(ltk_menu *menu) {
        }
        
        static int
       -ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, char **errstr) {
       +ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, ltk_error *err) {
                if (idx >= menu->num_entries) {
       -                *errstr = "Invalid menu entry index.\n";
       +                err->type = ERR_INVALID_INDEX;
                        return 1;
                }
                menu->entries[idx]->widget.parent = NULL;
       t@@ -1033,25 +1031,24 @@ get_entry_with_id(ltk_menu *menu, const char *id) {
        }
        
        static int
       -ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, char **errstr) {
       +ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, ltk_error *err) {
                size_t idx = get_entry_with_id(menu, id);
                if (idx >= menu->num_entries) {
       -                *errstr = "Invalid menu entry id.\n";
       +                err->type = ERR_INVALID_WIDGET_ID;
                        return 1;
                }
       -        ltk_menu_remove_entry_index(menu, idx, errstr);
       -        return 0;
       +        return ltk_menu_remove_entry_index(menu, idx, err);
        }
        
        static int
       -ltk_menu_remove_child(ltk_widget *child, ltk_widget *self, char **errstr) {
       +ltk_menu_remove_child(ltk_widget *child, ltk_widget *self, ltk_error *err) {
                ltk_menu *menu = (ltk_menu *)self;
                for (size_t i = 0; i < menu->num_entries; i++) {
                        if (&menu->entries[i]->widget == child) {
       -                        return ltk_menu_remove_entry_index(menu, i, errstr);
       +                        return ltk_menu_remove_entry_index(menu, i, err);
                        }
                }
       -        *errstr = "Widget not contained in menu.\n";
       +        err->type = ERR_WIDGET_NOT_IN_CONTAINER;
                return 1;
        }
        
       t@@ -1108,14 +1105,16 @@ ltk_menu_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                ltk_menu *menu;
                if (num_tokens != 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (!ltk_widget_id_free(tokens[1])) {
       -                *errstr = "Widget ID already taken.\n";
       +                err->type = ERR_WIDGET_ID_IN_USE;
       +                err->arg = 1;
                        return 1;
                }
                if (!strcmp(tokens[0], "menu")) {
       t@@ -1134,36 +1133,36 @@ ltk_menu_cmd_insert_entry(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_menu *menu;
                ltk_menuentry *e;
                const char *errstr_num;
                if (num_tokens != 5) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        /* FIXME: actually use this errstr */
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
                if (!menu) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, errstr);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, err);
                if (!e) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 3;
                        return 1;
                }
                size_t idx = (size_t)ltk_strtonum(tokens[5], 0, (long long)menu->num_entries, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid index.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 5;
                        return 1;
                }
       -        *errstr = NULL;
       -        ltk_menu_insert_entry(menu, e, idx, errstr);
       -        if (*errstr)
       +        if (ltk_menu_insert_entry(menu, e, idx, err)) {
       +                err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 3 : 5;
                        return 1;
       -
       +        }
                return 0;
        }
        
       t@@ -1173,29 +1172,29 @@ ltk_menu_cmd_add_entry(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_menu *menu;
                ltk_menuentry *e;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
                if (!menu) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, errstr);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[3], LTK_MENUENTRY, err);
                if (!e) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 3;
                        return 1;
                }
       -        *errstr = NULL;
       -        ltk_menu_add_entry(menu, e, errstr);
       -        if (*errstr)
       +        if (ltk_menu_add_entry(menu, e, err)) {
       +                err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 3 : 5;
                        return 1;
       -
       +        }
                return 0;
        }
        
       t@@ -1205,26 +1204,30 @@ ltk_menu_cmd_remove_entry_index(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_menu *menu;
                const char *errstr_num;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
                if (!menu) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
                size_t idx = (size_t)ltk_strtonum(tokens[3], 0, (long long)menu->num_entries, &errstr_num);
                if (errstr_num) {
       -                *errstr = "Invalid index.\n";
       +                err->type = ERR_INVALID_ARGUMENT;
       +                err->arg = 3;
                        return 1;
                }
       -        if (!ltk_menu_remove_entry_index(menu, idx, errstr))
       +        if (!ltk_menu_remove_entry_index(menu, idx, err)) {
       +                err->arg = 3;
                        return 1;
       +        }
        
                return 0;
        }
       t@@ -1235,20 +1238,23 @@ ltk_menu_cmd_remove_entry_id(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_menu *menu;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
                if (!menu) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
       -        if (!ltk_menu_remove_entry_id(menu, tokens[3], errstr))
       +        if (!ltk_menu_remove_entry_id(menu, tokens[3], err)) {
       +                err->arg = 3;
                        return 1;
       +        }
        
                return 0;
        }
       t@@ -1259,16 +1265,17 @@ ltk_menu_cmd_remove_all_entries(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_menu *menu;
                if (num_tokens != 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, errstr);
       +        menu = (ltk_menu *)ltk_get_widget(tokens[1], LTK_MENU, err);
                if (!menu) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
                ltk_menu_remove_all_entries(menu);
       t@@ -1282,14 +1289,16 @@ ltk_menuentry_cmd_create(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                ltk_menuentry *e;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (!ltk_widget_id_free(tokens[1])) {
       -                *errstr = "Widget ID already taken.\n";
       +                err->type = ERR_WIDGET_ID_IN_USE;
       +                err->arg = 1;
                        return 1;
                }
                e = ltk_menuentry_create(window, tokens[1], tokens[3]);
       t@@ -1304,30 +1313,30 @@ ltk_menuentry_cmd_attach_submenu(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_menuentry *e;
                ltk_menu *submenu;
                if (num_tokens != 4) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, errstr);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, err);
                if (!e) {
       -                *errstr = "Invalid entry widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
       -        submenu = (ltk_menu *)ltk_get_widget(tokens[3], LTK_MENU, errstr);
       +        submenu = (ltk_menu *)ltk_get_widget(tokens[3], LTK_MENU, err);
                if (!submenu) {
       -                *errstr = "Invalid submenu widget ID.\n";
       +                err->arg = 3;
                        return 1;
                }
        
       -        *errstr = NULL;
       -        ltk_menuentry_attach_submenu(e, submenu, errstr);
       -        if (*errstr)
       +        if (ltk_menuentry_attach_submenu(e, submenu, err)) {
       +                err->arg = err->type == ERR_MENU_NOT_SUBMENU ? 3 : 1;
                        return 1;
       -
       +        }
                return 0;
        }
        
       t@@ -1337,16 +1346,17 @@ ltk_menuentry_cmd_detach_submenu(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                ltk_menuentry *e;
                if (num_tokens != 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
       -        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, errstr);
       +        e = (ltk_menuentry *)ltk_get_widget(tokens[1], LTK_MENUENTRY, err);
                if (!e) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
        
       t@@ -1364,25 +1374,27 @@ ltk_menu_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                if (num_tokens < 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (strcmp(tokens[2], "create") == 0) {
       -                return ltk_menu_cmd_create(window, tokens, num_tokens, errstr);
       +                return ltk_menu_cmd_create(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "insert-entry") == 0) {
       -                return ltk_menu_cmd_insert_entry(window, tokens, num_tokens, errstr);
       +                return ltk_menu_cmd_insert_entry(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "add-entry") == 0) {
       -                return ltk_menu_cmd_add_entry(window, tokens, num_tokens, errstr);
       +                return ltk_menu_cmd_add_entry(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "remove-entry-index") == 0) {
       -                return ltk_menu_cmd_remove_entry_index(window, tokens, num_tokens, errstr);
       +                return ltk_menu_cmd_remove_entry_index(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "remove-entry-id") == 0) {
       -                return ltk_menu_cmd_remove_entry_id(window, tokens, num_tokens, errstr);
       +                return ltk_menu_cmd_remove_entry_id(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "remove-all-entries") == 0) {
       -                return ltk_menu_cmd_remove_all_entries(window, tokens, num_tokens, errstr);
       +                return ltk_menu_cmd_remove_all_entries(window, tokens, num_tokens, err);
                } else {
       -                *errstr = "Invalid command.\n";
       +                err->type = ERR_INVALID_COMMAND;
       +                err->arg = -1;
                        return 1;
                }
        
       t@@ -1395,19 +1407,21 @@ ltk_menuentry_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                if (num_tokens < 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (strcmp(tokens[2], "create") == 0) {
       -                return ltk_menuentry_cmd_create(window, tokens, num_tokens, errstr);
       +                return ltk_menuentry_cmd_create(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "attach-submenu") == 0) {
       -                return ltk_menuentry_cmd_attach_submenu(window, tokens, num_tokens, errstr);
       +                return ltk_menuentry_cmd_attach_submenu(window, tokens, num_tokens, err);
                } else if (strcmp(tokens[2], "detach-submenu") == 0) {
       -                return ltk_menuentry_cmd_detach_submenu(window, tokens, num_tokens, errstr);
       +                return ltk_menuentry_cmd_detach_submenu(window, tokens, num_tokens, err);
                } else {
       -                *errstr = "Invalid command.\n";
       +                err->type = ERR_INVALID_COMMAND;
       +                err->arg = -1;
                        return 1;
                }
        
   DIR diff --git a/src/menu.h b/src/menu.h
       t@@ -14,8 +14,8 @@
         * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
         */
        
       -#ifndef _LTK_MENU_H_
       -#define _LTK_MENU_H_
       +#ifndef LTK_MENU_H
       +#define LTK_MENU_H
        
        #include "ltk.h"
        #include "text.h"
       t@@ -72,14 +72,14 @@ int ltk_menu_cmd(
                ltk_window *window,
                char **tokens,
                size_t num_tokens,
       -        char **errstr
       +        ltk_error *err
        );
        
        int ltk_menuentry_cmd(
                ltk_window *window,
                char **tokens,
                size_t num_tokens,
       -        char **errstr
       +        ltk_error *err
        );
        
       -#endif /* _LTK_MENU_H_ */
       +#endif /* LTK_MENU_H */
   DIR diff --git a/src/util.c b/src/util.c
       t@@ -47,14 +47,13 @@ ltk_read_file(const char *path, unsigned long *len) {
        
        /* If `needed` is larger than `*alloc_size`, resize `*str` to
           `max(needed, *alloc_size * 2)`. Aborts program on error. */
       -int
       +void
        ltk_grow_string(char **str, int *alloc_size, int needed) {
       -        if (needed <= *alloc_size) return 0;
       +        if (needed <= *alloc_size) return;
                int new_size = needed > (*alloc_size * 2) ? needed : (*alloc_size * 2);
                char *new = ltk_realloc(*str, new_size);
                *str = new;
                *alloc_size = new_size;
       -        return 0;
        }
        
        /* Get the directory to store ltk files in and create it if it doesn't exist yet.
   DIR diff --git a/src/util.h b/src/util.h
       t@@ -25,7 +25,7 @@ long long ltk_strtonum(
        );
        
        char *ltk_read_file(const char *path, unsigned long *len);
       -int ltk_grow_string(char **str, int *alloc_size, int needed);
       +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);
        
   DIR diff --git a/src/widget.c b/src/widget.c
       t@@ -42,12 +42,12 @@ ltk_destroy_widget_hash(void) {
                hash_locked = 1;
                khint_t k;
                ltk_widget *ptr;
       -        char *errstr;
       +        ltk_error err;
                for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
                        if (kh_exist(widget_hash, k)) {
                                ptr = kh_value(widget_hash, k);
                                ltk_free((char *)kh_key(widget_hash, k));
       -                        ltk_widget_destroy(ptr, 1, &errstr);
       +                        ltk_widget_destroy(ptr, 1, &err);
                        }
                }
                kh_destroy(widget, widget_hash);
       t@@ -320,17 +320,17 @@ ltk_widget_id_free(const char *id) {
        }
        
        ltk_widget *
       -ltk_get_widget(const char *id, ltk_widget_type type, char **errstr) {
       +ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err) {
                khint_t k;
                ltk_widget *widget;
                k = kh_get(widget, widget_hash, id);
                if (k == kh_end(widget_hash)) {
       -                *errstr = "Widget with given ID doesn't exist.\n";
       +                err->type = ERR_INVALID_WIDGET_ID;
                        return NULL;
                }
                widget = kh_value(widget_hash, k);
                if (type != LTK_WIDGET && widget->vtable->type != type) {
       -                *errstr = "Widget with given ID has wrong type.\n";
       +                err->type = ERR_INVALID_WIDGET_TYPE;
                        return NULL;
                }
                return widget;
       t@@ -359,18 +359,18 @@ ltk_remove_widget(const char *id) {
        }
        
        int
       -ltk_widget_destroy(ltk_widget *widget, int shallow, char **errstr) {
       +ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err) {
                /* widget->parent->remove_child should never be NULL because of the fact that
                   the widget is set as parent, but let's just check anyways... */
       -        int err = 0;
       +        int invalid = 0;
                if (widget->parent && widget->parent->vtable->remove_child) {
       -                err = widget->parent->vtable->remove_child(
       -                    widget, widget->parent, errstr
       +                invalid = widget->parent->vtable->remove_child(
       +                    widget, widget->parent, err
                        );
                }
                widget->vtable->destroy(widget, shallow);
        
       -        return err;
       +        return invalid;
        }
        
        int
       t@@ -378,11 +378,12 @@ ltk_widget_destroy_cmd(
            ltk_window *window,
            char **tokens,
            size_t num_tokens,
       -    char **errstr) {
       +    ltk_error *err) {
                (void)window;
                int shallow = 1;
                if (num_tokens != 2 && num_tokens != 3) {
       -                *errstr = "Invalid number of arguments.\n";
       +                err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                err->arg = -1;
                        return 1;
                }
                if (num_tokens == 3) {
       t@@ -391,14 +392,19 @@ ltk_widget_destroy_cmd(
                        } else if (strcmp(tokens[2], "shallow") == 0) {
                                shallow = 1;
                        } else {
       -                        *errstr = "Invalid argument: must be 'shallow' or 'deep'.\n";
       +                        err->type = ERR_INVALID_ARGUMENT;
       +                        err->arg = 2;
                                return 1;
                        }
                }
       -        ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET, errstr);
       +        ltk_widget *widget = ltk_get_widget(tokens[1], LTK_WIDGET, err);
                if (!widget) {
       -                *errstr = "Invalid widget ID.\n";
       +                err->arg = 1;
                        return 1;
                }
       -        return ltk_widget_destroy(widget, shallow, errstr);
       +        if (ltk_widget_destroy(widget, shallow, err)) {
       +                err->arg = -1;
       +                return 1;
       +        }
       +        return 0;
        }
   DIR diff --git a/src/widget.h b/src/widget.h
       t@@ -17,11 +17,13 @@
        #ifndef LTK_WIDGET_H
        #define LTK_WIDGET_H
        
       +#include "err.h"
        #include "rect.h"
        #include "event.h"
        
        /* FIXME: SORT OUT INCLUDES PROPERLY! */
        
       +
        typedef struct ltk_widget ltk_widget;
        
        typedef enum {
       t@@ -125,7 +127,7 @@ struct ltk_widget_vtable {
                struct ltk_widget *(*first_child)(struct ltk_widget *self);
        
                void (*child_size_change) (struct ltk_widget *, struct ltk_widget *);
       -        int (*remove_child)(struct ltk_widget *, struct ltk_widget *, char **);
       +        int (*remove_child)(struct ltk_widget *, struct ltk_widget *, ltk_error *);
                struct ltk_widget *(*get_child_at_pos)(struct ltk_widget *, int x, int y);
        
                ltk_widget_type type;
       t@@ -133,8 +135,8 @@ struct ltk_widget_vtable {
        };
        
        void ltk_widget_hide(ltk_widget *widget);
       -int ltk_widget_destroy(ltk_widget *widget, int shallow, char **errstr);
       -int ltk_widget_destroy_cmd(struct ltk_window *window, char **tokens, size_t num_tokens, char **errstr);
       +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);
        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);
       t@@ -144,7 +146,7 @@ void ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event)
        void ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event);
        void ltk_window_fake_motion_event(ltk_window *window, int x, int y);
        int ltk_widget_id_free(const char *id);
       -ltk_widget *ltk_get_widget(const char *id, ltk_widget_type type, char **errstr);
       +ltk_widget *ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err);
        void ltk_set_widget(ltk_widget *widget, const char *id);
        void ltk_remove_widget(const char *id);
        void ltk_widgets_cleanup();