URI:
       tInitial commit - 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 a4ab655cc97c6c7f7383e9567b413fd34819bb42
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Mon,  1 Jun 2020 21:14:20 +0200
       
       Initial commit
       
       Diffstat:
         A .gitignore                          |       3 +++
         A LICENSE                             |      22 ++++++++++++++++++++++
         A Makefile                            |      15 +++++++++++++++
         A README.md                           |       1 +
         A button.c                            |     131 +++++++++++++++++++++++++++++++
         A button.h                            |      58 ++++++++++++++++++++++++++++++
         A grid.c                              |     270 +++++++++++++++++++++++++++++++
         A grid.h                              |     122 +++++++++++++++++++++++++++++++
         A ini.c                               |     201 ++++++++++++++++++++++++++++++
         A ini.h                               |     104 +++++++++++++++++++++++++++++++
         A khash.h                             |     627 +++++++++++++++++++++++++++++++
         A ltk.c                               |     563 +++++++++++++++++++++++++++++++
         A ltk.h                               |     158 +++++++++++++++++++++++++++++++
         A test.sh                             |       6 ++++++
         A theme.ini                           |      18 ++++++++++++++++++
       
       15 files changed, 2299 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -0,0 +1,3 @@
       +ltk
       +*.o
       +*.core
   DIR diff --git a/LICENSE b/LICENSE
       t@@ -0,0 +1,22 @@
       +MIT/X Consortium License
       +
       +The Lumidify ToolKit (LTK)
       +Copyright (c) 2016, 2017, 2018, 2019, 2020 lumidify <nobody@lumidify.org>
       +
       +Permission is hereby granted, free of charge, to any person obtaining a copy
       +of this software and associated documentation files (the "Software"), to deal
       +in the Software without restriction, including without limitation the rights
       +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       +copies of the Software, and to permit persons to whom the Software is
       +furnished to do so, subject to the following conditions:
       +
       +The above copyright notice and this permission notice shall be included in all
       +copies or substantial portions of the Software.
       +
       +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +SOFTWARE.
   DIR diff --git a/Makefile b/Makefile
       t@@ -0,0 +1,15 @@
       +LIBS = -lm `pkg-config --libs x11`
       +STD = -std=c99
       +CFLAGS = -g -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11` -pedantic
       +OBJ = ltk.o ini.o grid.o button.o
       +
       +ltk: $(OBJ)
       +        $(CC) $(STD) -o $@ $(OBJ) $(LIBS)
       +
       +%.o: %.c
       +        $(CC) -c -o $@ $< $(CFLAGS)
       +
       +.PHONY: clean
       +
       +clean:
       +        rm -f $(OBJ) ltk ltk_in
   DIR diff --git a/README.md b/README.md
       t@@ -0,0 +1 @@
       +Not much to see here.
   DIR diff --git a/button.c b/button.c
       t@@ -0,0 +1,131 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include "khash.h"
       +#include "ltk.h"
       +#include "button.h"
       +
       +void
       +ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        ltk_theme *theme = window->theme;
       +        if (!theme->button)
       +                theme->button = malloc(sizeof(ltk_button_theme));
       +        if (!theme->button)
       +                ltk_fatal("Unable to allocate ltk_button_theme.\n");
       +        if (strcmp(prop, "border_width") == 0) {
       +                theme->button->border_width = atoi(value);
       +        } else if (strcmp(prop, "pad") == 0) {
       +                theme->button->pad = atoi(value);
       +        } else if (strcmp(prop, "border") == 0) {
       +                theme->button->border = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "fill") == 0) {
       +                theme->button->fill = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "border_pressed") == 0) {
       +                theme->button->border_pressed = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "fill_pressed") == 0) {
       +                theme->button->fill_pressed = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "border_active") == 0) {
       +                theme->button->border_active = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "fill_active") == 0) {
       +                theme->button->fill_active = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "border_disabled") == 0) {
       +                theme->button->border_disabled = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "fill_disabled") == 0) {
       +                theme->button->fill_disabled = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "text_color") == 0) {
       +                theme->button->text_color = ltk_create_xcolor(window, value);
       +        } else {
       +                (void)printf("WARNING: Unknown property \"%s\" for button style.\n", prop);
       +        }
       +}
       +
       +void
       +ltk_button_draw(ltk_button *button) {
       +        ltk_window *window = button->widget.window;
       +        ltk_button_theme *theme = window->theme->button;
       +        ltk_rect rect = button->widget.rect;
       +        int bw = theme->border_width;
       +        XColor border;
       +        XColor fill;
       +        switch (button->widget.state) {
       +        case LTK_NORMAL:
       +                border = theme->border;
       +                fill = theme->fill;
       +                break;
       +        case LTK_PRESSED:
       +                border = theme->border_pressed;
       +                fill = theme->fill_pressed;
       +                break;
       +        case LTK_ACTIVE:
       +                border = theme->border_active;
       +                fill = theme->fill_active;
       +                break;
       +        case LTK_DISABLED:
       +                border = theme->border_disabled;
       +                fill = theme->fill_disabled;
       +                break;
       +        default:
       +                ltk_fatal("No style found for button!\n");
       +        }
       +        XSetForeground(window->dpy, window->gc, fill.pixel);
       +        XFillRectangle(window->dpy, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h);
       +        /* FIXME: Why did I do this? */
       +        if (bw < 1) return;
       +        XSetForeground(window->dpy, window->gc, border.pixel);
       +        XSetLineAttributes(window->dpy, window->gc, bw, LineSolid, CapButt, JoinMiter);
       +        XDrawRectangle(window->dpy, window->xwindow, window->gc, rect.x + bw / 2, rect.y + bw / 2, rect.w - bw, rect.h - bw);
       +}
       +
       +
       +void
       +ltk_button_mouse_release(ltk_button *button, XEvent event) {
       +        ltk_queue_event(button->widget.window, button->widget.id, "button_click");
       +}
       +
       +ltk_button *
       +ltk_button_create(ltk_window *window, const char *id, const char *text) {
       +        ltk_button *button = malloc(sizeof(ltk_button));
       +        if (!button) ltk_fatal("ERROR: Unable to allocate memory for ltk_button.\n");
       +
       +        ltk_fill_widget_defaults(&button->widget, id, window, &ltk_button_draw, &ltk_button_destroy, 1);
       +        button->widget.mouse_release = &ltk_button_mouse_release;
       +        button->text = strdup(text);
       +
       +        return button;
       +}
       +
       +void
       +ltk_button_destroy(ltk_button *button) {
       +        if (!button) {
       +                (void)printf("WARNING: Tried to destroy NULL button.\n");
       +                return;
       +        }
       +        free(button->text);
       +        free(button->widget.id);
       +        free(button);
       +}
   DIR diff --git a/button.h b/button.h
       t@@ -0,0 +1,58 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +#ifndef _LTK_BUTTON_H_
       +#define _LTK_BUTTON_H_
       +
       +/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */
       +
       +typedef struct {
       +        ltk_widget widget;
       +        char *text;
       +} ltk_button;
       +
       +typedef struct ltk_button_theme {
       +        int border_width;
       +        XColor text_color;
       +        int pad;
       +
       +        XColor border;
       +        XColor fill;
       +
       +        XColor border_pressed;
       +        XColor fill_pressed;
       +
       +        XColor border_active;
       +        XColor fill_active;
       +
       +        XColor border_disabled;
       +        XColor fill_disabled;
       +} ltk_button_theme;
       +
       +void ltk_button_draw(ltk_button *button);
       +
       +ltk_button *ltk_button_create(ltk_window *window, const char *id, const char *text);
       +
       +void ltk_button_destroy(ltk_button *button);
       +
       +#endif
   DIR diff --git a/grid.c b/grid.c
       t@@ -0,0 +1,270 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +/* TODO: remove_widget function that also adjusts static width */
       +/* TODO: widget size request */
       +
       +#include <stdlib.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include "khash.h"
       +#include "ltk.h"
       +#include "grid.h"
       +
       +void ltk_set_row_weight(ltk_grid * grid, int row, int weight) {
       +        grid->row_weights[row] = weight;
       +        ltk_recalculate_grid(grid);
       +}
       +
       +void ltk_set_column_weight(ltk_grid * grid, int column, int weight) {
       +        grid->column_weights[column] = weight;
       +        ltk_recalculate_grid(grid);
       +}
       +
       +void ltk_draw_grid(ltk_grid *grid) {
       +        int i;
       +        for (i = 0; i < grid->rows * grid->columns; i++) {
       +                if (!grid->widget_grid[i])
       +                        continue;
       +                ltk_widget *ptr = grid->widget_grid[i];
       +                ptr->draw(ptr);
       +        }
       +}
       +
       +ltk_grid *ltk_create_grid(ltk_window *window, const char *id, int rows, int columns) {
       +        ltk_grid *grid = malloc(sizeof(ltk_grid));
       +
       +        ltk_fill_widget_defaults(&grid->widget, id, window, &ltk_draw_grid, &ltk_destroy_grid, 0);
       +        grid->widget.mouse_press = &ltk_grid_mouse_press;
       +        grid->widget.mouse_release = &ltk_grid_mouse_release;
       +        grid->widget.motion_notify = &ltk_grid_motion_notify;
       +        grid->widget.resize = &ltk_recalculate_grid;
       +
       +        grid->rows = rows;
       +        grid->columns = columns;
       +        grid->widget_grid = malloc(rows * columns * sizeof(ltk_widget));
       +        grid->row_heights = malloc(rows * sizeof(int));
       +        grid->column_widths = malloc(rows * sizeof(int));
       +        grid->row_weights = malloc(rows * sizeof(int));
       +        grid->column_weights = malloc(columns * sizeof(int));
       +        /* Positions have one extra for the end */
       +        grid->row_pos = malloc((rows + 1) * sizeof(int));
       +        grid->column_pos = malloc((columns + 1) * sizeof(int));
       +        int i;
       +        for (i = 0; i < rows; i++) {
       +                grid->row_heights[i] = 0;
       +                grid->row_weights[i] = 0;
       +                grid->row_pos[i] = 0;
       +        }
       +        grid->row_pos[rows] = 0;
       +        for (i = 0; i < columns; i++) {
       +                grid->column_widths[i] = 0;
       +                grid->column_weights[i] = 0;
       +                grid->column_pos[i] = 0;
       +        }
       +        grid->column_pos[columns] = 0;
       +        for (i = 0; i < rows * columns; i++) {
       +                grid->widget_grid[i] = NULL;
       +        }
       +
       +        ltk_recalculate_grid(grid);
       +        return grid;
       +}
       +
       +void ltk_destroy_grid(ltk_grid *grid) {
       +        ltk_widget *ptr;
       +        int i;
       +        for (i = 0; i < grid->rows * grid->columns; i++) {
       +                if (grid->widget_grid[i]) {
       +                        ptr = grid->widget_grid[i];
       +                        ptr->destroy(ptr);
       +                }
       +        }
       +        free(grid->widget_grid);
       +        free(grid->row_heights);
       +        free(grid->column_widths);
       +        free(grid->row_weights);
       +        free(grid->column_weights);
       +        free(grid->row_pos);
       +        free(grid->column_pos);
       +        free(grid);
       +}
       +
       +void ltk_recalculate_grid(ltk_grid *grid) {
       +        unsigned int height_static = 0, width_static = 0;
       +        unsigned int total_row_weight = 0, total_column_weight = 0;
       +        float height_unit = 0, width_unit = 0;
       +        unsigned int currentx = 0, currenty = 0;
       +        int i, j;
       +        for (i = 0; i < grid->rows; i++) {
       +                total_row_weight += grid->row_weights[i];
       +                if (grid->row_weights[i] == 0) {
       +                        height_static += grid->row_heights[i];
       +                }
       +        }
       +        for (i = 0; i < grid->columns; i++) {
       +                total_column_weight += grid->column_weights[i];
       +                if (grid->column_weights[i] == 0) {
       +                        width_static += grid->column_widths[i];
       +                }
       +        }
       +        if (total_row_weight > 0) {
       +                height_unit = (float) (grid->widget.rect.h - height_static) / (float) total_row_weight;
       +        }
       +        if (total_column_weight > 0) {
       +                width_unit = (float) (grid->widget.rect.w - width_static) / (float) total_column_weight;
       +        }
       +        for (i = 0; i < grid->rows; i++) {
       +                grid->row_pos[i] = currenty;
       +                if (grid->row_weights[i] > 0) {
       +                        grid->row_heights[i] = grid->row_weights[i] * height_unit;
       +                }
       +                currenty += grid->row_heights[i];
       +        }
       +        grid->row_pos[grid->rows] = currenty;
       +        for (i = 0; i < grid->columns; i++) {
       +                grid->column_pos[i] = currentx;
       +                if (grid->column_weights[i] > 0) {
       +                        grid->column_widths[i] = grid->column_weights[i] * width_unit;
       +                }
       +                currentx += grid->column_widths[i];
       +        }
       +        grid->column_pos[grid->columns] = currentx;
       +        int orig_width, orig_height;
       +        int end_column, end_row;
       +        for (i = 0; i < grid->rows; i++) {
       +                for (j = 0; j < grid->columns; j++) {
       +                        if (!grid->widget_grid[i * grid->columns + j]) {
       +                                continue;
       +                        }
       +                        ltk_widget *ptr = grid->widget_grid[i * grid->columns + j];
       +                        orig_width = ptr->rect.w;
       +                        orig_height = ptr->rect.h;
       +                        end_row = i + ptr->row_span;
       +                        end_column = j + ptr->column_span;
       +                        if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT) {
       +                                ptr->rect.w = grid->column_pos[end_column] - grid->column_pos[j];
       +                        }
       +                        if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM) {
       +                                ptr->rect.h = grid->row_pos[end_row] - grid->row_pos[i];
       +                        }
       +                        if (orig_width != ptr->rect.w || orig_height != ptr->rect.h) {
       +                                if (ptr->resize) {
       +                                        ptr->resize(ptr, orig_width, orig_height);
       +                                }
       +                        }
       +
       +                        if (ptr->sticky & LTK_STICKY_RIGHT) {
       +                                ptr->rect.x = grid->column_pos[end_column] - ptr->rect.w;
       +                        } else if (ptr->sticky & LTK_STICKY_LEFT) {
       +                                ptr->rect.x = grid->column_pos[j];
       +                        } else {
       +                                ptr->rect.x = grid->column_pos[j] + ((grid->column_pos[end_column] - grid->column_pos[j]) / 2 - ptr->rect.w / 2);
       +                        }
       +
       +                        if (ptr->sticky & LTK_STICKY_BOTTOM) {
       +                                ptr->rect.y = grid->row_pos[end_row] - ptr->rect.h;
       +                        } else if (ptr->sticky & LTK_STICKY_TOP) {
       +                                ptr->rect.y = grid->row_pos[i];
       +                        } else {
       +                                ptr->rect.y = grid->row_pos[i] + ((grid->row_pos[end_row] - grid->row_pos[i]) / 2 - ptr->rect.h / 2);
       +                        }
       +                }
       +        }
       +}
       +
       +void ltk_grid_widget(ltk_widget *widget, ltk_grid *grid, int row, int column, int row_span, int column_span, unsigned short sticky) {
       +        widget->sticky = sticky;
       +        widget->row = row;
       +        widget->column = column;
       +        widget->row_span = row_span;
       +        widget->column_span = column_span;
       +        if (grid->column_weights[column] == 0 && widget->rect.w > grid->column_widths[column]) {
       +                grid->column_widths[column] = widget->rect.w;
       +        }
       +        if (grid->row_weights[row] == 0 && widget->rect.h > grid->row_heights[row]) {
       +                grid->row_heights[row] = widget->rect.h;
       +        }
       +        grid->widget_grid[widget->row * grid->columns + widget->column] = widget;
       +        widget->parent = grid;
       +        ltk_recalculate_grid(grid);
       +}
       +
       +static int ltk_grid_find_nearest_column(ltk_grid *grid, int x) {
       +        int i;
       +        for (i = 0; i < grid->columns; i++) {
       +                if (grid->column_pos[i] <= x && grid->column_pos[i + 1] >= x) {
       +                        return i;
       +                }
       +        }
       +        return -1;
       +}
       +
       +static int ltk_grid_find_nearest_row(ltk_grid *grid, int y) {
       +        int i;
       +        for (i = 0; i < grid->rows; i++) {
       +                if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) {
       +                        return i;
       +                }
       +        }
       +        return -1;
       +}
       +
       +void ltk_grid_mouse_press(ltk_grid *grid, XEvent event) {
       +        int x = event.xbutton.x;
       +        int y = event.xbutton.y;
       +        int row = ltk_grid_find_nearest_row(grid, y);
       +        int column = ltk_grid_find_nearest_column(grid, x);
       +        if (row == -1 || column == -1)
       +                return;
       +        ltk_widget *ptr = grid->widget_grid[row * grid->columns + column];
       +        if (ptr && ltk_collide_rect(ptr->rect, x, y))
       +                ltk_widget_mouse_press_event(ptr, event);
       +}
       +
       +void ltk_grid_mouse_release(ltk_grid *grid, XEvent event) {
       +        int x = event.xbutton.x;
       +        int y = event.xbutton.y;
       +        int row = ltk_grid_find_nearest_row(grid, y);
       +        int column = ltk_grid_find_nearest_column(grid, x);
       +        if (row == -1 || column == -1)
       +                return;
       +        ltk_widget *ptr = grid->widget_grid[row * grid->columns + column];
       +        if (ptr && ltk_collide_rect(ptr->rect, x, y))
       +                ltk_widget_mouse_release_event(ptr, event);
       +}
       +
       +void ltk_grid_motion_notify(ltk_grid *grid, XEvent event) {
       +        short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
       +        if (pressed)
       +                return;
       +        int x = event.xbutton.x;
       +        int y = event.xbutton.y;
       +        int row = ltk_grid_find_nearest_row(grid, y);
       +        int column = ltk_grid_find_nearest_column(grid, x);
       +        if (row == -1 || column == -1)
       +                return;
       +        ltk_widget *ptr = grid->widget_grid[row * grid->columns + column];
       +        if (ptr && ltk_collide_rect(ptr->rect, x, y))
       +                ltk_widget_motion_notify_event(ptr, event);
       +}
   DIR diff --git a/grid.h b/grid.h
       t@@ -0,0 +1,122 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +#ifndef _LTK_GRID_H_
       +#define _LTK_GRID_H_
       +
       +/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */
       +
       +/*
       + * Struct to represent a grid widget.
       + */
       +typedef struct {
       +        ltk_widget widget;
       +        unsigned int rows;
       +        unsigned int columns;
       +        ltk_widget **widget_grid;
       +        unsigned int *row_heights;
       +        unsigned int *column_widths;
       +        unsigned int *row_weights;
       +        unsigned int *column_weights;
       +        unsigned int *row_pos;
       +        unsigned int *column_pos;
       +} ltk_grid;
       +
       +/*
       + * Set the weight of a row in a grid.
       + * grid: The grid.
       + * row: The row.
       + * weight: The weight to set the row to.
       + */
       +void ltk_set_row_weight(ltk_grid *grid, int row, int weight);
       +
       +/*
       + * Set the weight of a column in a grid.
       + * grid: The grid.
       + * column: The column.
       + * weight: The weight to set the row to.
       + */
       +void ltk_set_column_weight(ltk_grid *grid, int column, int weight);
       +
       +/*
       + * Draw all the widgets in a grid.
       + * grid: The grid to draw the widgets of.
       + */
       +void ltk_draw_grid(ltk_grid *grid);
       +
       +/*
       + * Create a grid.
       + * window: The window the grid will displayed on.
       + * rows: The number of rows in the grid.
       + * columns: The number of columns in the grid.
       + */
       +ltk_grid *ltk_create_grid(ltk_window *window, const char *id, int rows, int columns);
       +
       +/*
       + * Destroy a grid.
       + * grid: Pointer to the grid.
       + */
       +void ltk_destroy_grid(ltk_grid *grid);
       +
       +/*
       + * Recalculate the positions and dimensions of the
       + * columns, rows, and widgets in a grid.
       + * grid: Pointer to the grid.
       + */
       +void ltk_recalculate_grid(ltk_grid *grid);
       +
       +/*
       + * Grid a widget.
       + * widget: Pointer to the widget.
       + * grid: The grid.
       + * row: The row to grid the widget in.
       + * column: The column to grid the widget in.
       + * rowspan: The amount of rows the widget should span.
       + * columnspan: The amount of columns the widget should span.
       + * sticky: Mask of the sticky values (LTK_STICKY_*).
       + */
       +void ltk_grid_widget(ltk_widget *widget, ltk_grid *grid, int row, int column,
       +                     int rowspan, int columnspan, unsigned short sticky);
       +
       +/*
       + * Delegate a mouse press event on the grid to the proper widget.
       + * grid: The grid.
       + * event: The event to be handled.
       + */
       +void ltk_grid_mouse_press(ltk_grid *grid, XEvent event);
       +
       +/*
       + * Delegate a mouse release event on the grid to the proper widget.
       + * grid: The grid.
       + * event: The event to be handled.
       + */
       +void ltk_grid_mouse_release(ltk_grid *grid, XEvent event);
       +
       +/*
       + * Delegate a mouse motion event on the grid to the proper widget.
       + * grid: The grid.
       + * event: The event to be handled.
       + */
       +void ltk_grid_motion_notify(ltk_grid *grid, XEvent event);
       +
       +#endif
   DIR diff --git a/ini.c b/ini.c
       t@@ -0,0 +1,201 @@
       +/* inih -- simple .INI file parser
       +
       +inih is released under the New BSD license (see LICENSE.txt). Go to the project
       +home page for more info:
       +
       +https://github.com/benhoyt/inih
       +
       +*/
       +
       +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
       +#define _CRT_SECURE_NO_WARNINGS
       +#endif
       +
       +#include <stdio.h>
       +#include <ctype.h>
       +#include <string.h>
       +
       +#include "ini.h"
       +
       +#if !INI_USE_STACK
       +#include <stdlib.h>
       +#endif
       +
       +#define MAX_SECTION 50
       +#define MAX_NAME 50
       +
       +/* Strip whitespace chars off end of given string, in place. Return s. */
       +static char* rstrip(char* s)
       +{
       +    char* p = s + strlen(s);
       +    while (p > s && isspace((unsigned char)(*--p)))
       +        *p = '\0';
       +    return s;
       +}
       +
       +/* Return pointer to first non-whitespace char in given string. */
       +static char* lskip(const char* s)
       +{
       +    while (*s && isspace((unsigned char)(*s)))
       +        s++;
       +    return (char*)s;
       +}
       +
       +/* Return pointer to first char (of chars) or inline comment in given string,
       +   or pointer to null at end of string if neither found. Inline comment must
       +   be prefixed by a whitespace character to register as a comment. */
       +static char* find_chars_or_comment(const char* s, const char* chars)
       +{
       +#if INI_ALLOW_INLINE_COMMENTS
       +    int was_space = 0;
       +    while (*s && (!chars || !strchr(chars, *s)) &&
       +           !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
       +        was_space = isspace((unsigned char)(*s));
       +        s++;
       +    }
       +#else
       +    while (*s && (!chars || !strchr(chars, *s))) {
       +        s++;
       +    }
       +#endif
       +    return (char*)s;
       +}
       +
       +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
       +static char* strncpy0(char* dest, const char* src, size_t size)
       +{
       +    strncpy(dest, src, size);
       +    dest[size - 1] = '\0';
       +    return dest;
       +}
       +
       +/* See documentation in header file. */
       +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
       +                     void* user)
       +{
       +    /* Uses a fair bit of stack (use heap instead if you need to) */
       +#if INI_USE_STACK
       +    char line[INI_MAX_LINE];
       +#else
       +    char* line;
       +#endif
       +    char section[MAX_SECTION] = "";
       +    char prev_name[MAX_NAME] = "";
       +
       +    char* start;
       +    char* end;
       +    char* name;
       +    char* value;
       +    int lineno = 0;
       +    int error = 0;
       +
       +#if !INI_USE_STACK
       +    line = (char*)malloc(INI_MAX_LINE);
       +    if (!line) {
       +        return -2;
       +    }
       +#endif
       +
       +#if INI_HANDLER_LINENO
       +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
       +#else
       +#define HANDLER(u, s, n, v) handler(u, s, n, v)
       +#endif
       +
       +    /* Scan through stream line by line */
       +    while (reader(line, INI_MAX_LINE, stream) != NULL) {
       +        lineno++;
       +
       +        start = line;
       +#if INI_ALLOW_BOM
       +        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
       +                           (unsigned char)start[1] == 0xBB &&
       +                           (unsigned char)start[2] == 0xBF) {
       +            start += 3;
       +        }
       +#endif
       +        start = lskip(rstrip(start));
       +
       +        if (*start == ';' || *start == '#') {
       +            /* Per Python configparser, allow both ; and # comments at the
       +               start of a line */
       +        }
       +#if INI_ALLOW_MULTILINE
       +        else if (*prev_name && *start && start > line) {
       +            /* Non-blank line with leading whitespace, treat as continuation
       +               of previous name's value (as per Python configparser). */
       +            if (!HANDLER(user, section, prev_name, start) && !error)
       +                error = lineno;
       +        }
       +#endif
       +        else if (*start == '[') {
       +            /* A "[section]" line */
       +            end = find_chars_or_comment(start + 1, "]");
       +            if (*end == ']') {
       +                *end = '\0';
       +                strncpy0(section, start + 1, sizeof(section));
       +                *prev_name = '\0';
       +            }
       +            else if (!error) {
       +                /* No ']' found on section line */
       +                error = lineno;
       +            }
       +        }
       +        else if (*start) {
       +            /* Not a comment, must be a name[=:]value pair */
       +            end = find_chars_or_comment(start, "=:");
       +            if (*end == '=' || *end == ':') {
       +                *end = '\0';
       +                name = rstrip(start);
       +                value = end + 1;
       +#if INI_ALLOW_INLINE_COMMENTS
       +                end = find_chars_or_comment(value, NULL);
       +                if (*end)
       +                    *end = '\0';
       +#endif
       +                value = lskip(value);
       +                rstrip(value);
       +
       +                /* Valid name[=:]value pair found, call handler */
       +                strncpy0(prev_name, name, sizeof(prev_name));
       +                if (!HANDLER(user, section, name, value) && !error)
       +                    error = lineno;
       +            }
       +            else if (!error) {
       +                /* No '=' or ':' found on name[=:]value line */
       +                error = lineno;
       +            }
       +        }
       +
       +#if INI_STOP_ON_FIRST_ERROR
       +        if (error)
       +            break;
       +#endif
       +    }
       +
       +#if !INI_USE_STACK
       +    free(line);
       +#endif
       +
       +    return error;
       +}
       +
       +/* See documentation in header file. */
       +int ini_parse_file(FILE* file, ini_handler handler, void* user)
       +{
       +    return ini_parse_stream((ini_reader)fgets, file, handler, user);
       +}
       +
       +/* See documentation in header file. */
       +int ini_parse(const char* filename, ini_handler handler, void* user)
       +{
       +    FILE* file;
       +    int error;
       +
       +    file = fopen(filename, "r");
       +    if (!file)
       +        return -1;
       +    error = ini_parse_file(file, handler, user);
       +    fclose(file);
       +    return error;
       +}
   DIR diff --git a/ini.h b/ini.h
       t@@ -0,0 +1,104 @@
       +/* inih -- simple .INI file parser
       +
       +inih is released under the New BSD license (see LICENSE.txt). Go to the project
       +home page for more info:
       +
       +https://github.com/benhoyt/inih
       +
       +*/
       +
       +#ifndef __INI_H__
       +#define __INI_H__
       +
       +/* Make this header file easier to include in C++ code */
       +#ifdef __cplusplus
       +extern "C" {
       +#endif
       +
       +#include <stdio.h>
       +
       +/* Nonzero if ini_handler callback should accept lineno parameter. */
       +#ifndef INI_HANDLER_LINENO
       +#define INI_HANDLER_LINENO 0
       +#endif
       +
       +/* Typedef for prototype of handler function. */
       +#if INI_HANDLER_LINENO
       +typedef int (*ini_handler)(void* user, const char* section,
       +                           const char* name, const char* value,
       +                           int lineno);
       +#else
       +typedef int (*ini_handler)(void* user, const char* section,
       +                           const char* name, const char* value);
       +#endif
       +
       +/* Typedef for prototype of fgets-style reader function. */
       +typedef char* (*ini_reader)(char* str, int num, void* stream);
       +
       +/* Parse given INI-style file. May have [section]s, name=value pairs
       +   (whitespace stripped), and comments starting with ';' (semicolon). Section
       +   is "" if name=value pair parsed before any section heading. name:value
       +   pairs are also supported as a concession to Python's configparser.
       +
       +   For each name=value pair parsed, call handler function with given user
       +   pointer as well as section, name, and value (data only valid for duration
       +   of handler call). Handler should return nonzero on success, zero on error.
       +
       +   Returns 0 on success, line number of first error on parse error (doesn't
       +   stop on first error), -1 on file open error, or -2 on memory allocation
       +   error (only when INI_USE_STACK is zero).
       +*/
       +int ini_parse(const char* filename, ini_handler handler, void* user);
       +
       +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
       +   close the file when it's finished -- the caller must do that. */
       +int ini_parse_file(FILE* file, ini_handler handler, void* user);
       +
       +/* Same as ini_parse(), but takes an ini_reader function pointer instead of
       +   filename. Used for implementing custom or string-based I/O. */
       +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
       +                     void* user);
       +
       +/* Nonzero to allow multi-line value parsing, in the style of Python's
       +   configparser. If allowed, ini_parse() will call the handler with the same
       +   name for each subsequent line parsed. */
       +#ifndef INI_ALLOW_MULTILINE
       +#define INI_ALLOW_MULTILINE 1
       +#endif
       +
       +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
       +   the file. See http://code.google.com/p/inih/issues/detail?id=21 */
       +#ifndef INI_ALLOW_BOM
       +#define INI_ALLOW_BOM 1
       +#endif
       +
       +/* Nonzero to allow inline comments (with valid inline comment characters
       +   specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
       +   Python 3.2+ configparser behaviour. */
       +#ifndef INI_ALLOW_INLINE_COMMENTS
       +#define INI_ALLOW_INLINE_COMMENTS 1
       +#endif
       +#ifndef INI_INLINE_COMMENT_PREFIXES
       +#define INI_INLINE_COMMENT_PREFIXES ";"
       +#endif
       +
       +/* Nonzero to use stack, zero to use heap (malloc/free). */
       +#ifndef INI_USE_STACK
       +#define INI_USE_STACK 1
       +#endif
       +
       +/* Stop parsing on first error (default is to keep parsing). */
       +#ifndef INI_STOP_ON_FIRST_ERROR
       +#define INI_STOP_ON_FIRST_ERROR 0
       +#endif
       +
       +/* Maximum line length for any line in INI file. */
       +#ifndef INI_MAX_LINE
       +#define INI_MAX_LINE 200
       +#endif
       +
       +#ifdef __cplusplus
       +}
       +#endif
       +
       +#endif /* __INI_H__ */
   DIR diff --git a/khash.h b/khash.h
       t@@ -0,0 +1,627 @@
       +/* The MIT License
       +
       +   Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
       +
       +   Permission is hereby granted, free of charge, to any person obtaining
       +   a copy of this software and associated documentation files (the
       +   "Software"), to deal in the Software without restriction, including
       +   without limitation the rights to use, copy, modify, merge, publish,
       +   distribute, sublicense, and/or sell copies of the Software, and to
       +   permit persons to whom the Software is furnished to do so, subject to
       +   the following conditions:
       +
       +   The above copyright notice and this permission notice shall be
       +   included in all copies or substantial portions of the Software.
       +
       +   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +   SOFTWARE.
       +*/
       +
       +/*
       +  An example:
       +
       +#include "khash.h"
       +KHASH_MAP_INIT_INT(32, char)
       +int main() {
       +        int ret, is_missing;
       +        khiter_t k;
       +        khash_t(32) *h = kh_init(32);
       +        k = kh_put(32, h, 5, &ret);
       +        kh_value(h, k) = 10;
       +        k = kh_get(32, h, 10);
       +        is_missing = (k == kh_end(h));
       +        k = kh_get(32, h, 5);
       +        kh_del(32, h, k);
       +        for (k = kh_begin(h); k != kh_end(h); ++k)
       +                if (kh_exist(h, k)) kh_value(h, k) = 1;
       +        kh_destroy(32, h);
       +        return 0;
       +}
       +*/
       +
       +/*
       +  2013-05-02 (0.2.8):
       +
       +        * Use quadratic probing. When the capacity is power of 2, stepping function
       +          i*(i+1)/2 guarantees to traverse each bucket. It is better than double
       +          hashing on cache performance and is more robust than linear probing.
       +
       +          In theory, double hashing should be more robust than quadratic probing.
       +          However, my implementation is probably not for large hash tables, because
       +          the second hash function is closely tied to the first hash function,
       +          which reduce the effectiveness of double hashing.
       +
       +        Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
       +
       +  2011-12-29 (0.2.7):
       +
       +    * Minor code clean up; no actual effect.
       +
       +  2011-09-16 (0.2.6):
       +
       +        * The capacity is a power of 2. This seems to dramatically improve the
       +          speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
       +
       +           - http://code.google.com/p/ulib/
       +           - http://nothings.org/computer/judy/
       +
       +        * Allow to optionally use linear probing which usually has better
       +          performance for random input. Double hashing is still the default as it
       +          is more robust to certain non-random input.
       +
       +        * Added Wang's integer hash function (not used by default). This hash
       +          function is more robust to certain non-random input.
       +
       +  2011-02-14 (0.2.5):
       +
       +    * Allow to declare global functions.
       +
       +  2009-09-26 (0.2.4):
       +
       +    * Improve portability
       +
       +  2008-09-19 (0.2.3):
       +
       +        * Corrected the example
       +        * Improved interfaces
       +
       +  2008-09-11 (0.2.2):
       +
       +        * Improved speed a little in kh_put()
       +
       +  2008-09-10 (0.2.1):
       +
       +        * Added kh_clear()
       +        * Fixed a compiling error
       +
       +  2008-09-02 (0.2.0):
       +
       +        * Changed to token concatenation which increases flexibility.
       +
       +  2008-08-31 (0.1.2):
       +
       +        * Fixed a bug in kh_get(), which has not been tested previously.
       +
       +  2008-08-31 (0.1.1):
       +
       +        * Added destructor
       +*/
       +
       +
       +#ifndef __AC_KHASH_H
       +#define __AC_KHASH_H
       +
       +/*!
       +  @header
       +
       +  Generic hash table library.
       + */
       +
       +#define AC_VERSION_KHASH_H "0.2.8"
       +
       +#include <stdlib.h>
       +#include <string.h>
       +#include <limits.h>
       +
       +/* compiler specific configuration */
       +
       +#if UINT_MAX == 0xffffffffu
       +typedef unsigned int khint32_t;
       +#elif ULONG_MAX == 0xffffffffu
       +typedef unsigned long khint32_t;
       +#endif
       +
       +#if ULONG_MAX == ULLONG_MAX
       +typedef unsigned long khint64_t;
       +#else
       +typedef unsigned long long khint64_t;
       +#endif
       +
       +#ifndef kh_inline
       +#ifdef _MSC_VER
       +#define kh_inline __inline
       +#else
       +#define kh_inline inline
       +#endif
       +#endif /* kh_inline */
       +
       +#ifndef klib_unused
       +#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)
       +#define klib_unused __attribute__ ((__unused__))
       +#else
       +#define klib_unused
       +#endif
       +#endif /* klib_unused */
       +
       +typedef khint32_t khint_t;
       +typedef khint_t khiter_t;
       +
       +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
       +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
       +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
       +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
       +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
       +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
       +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
       +
       +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
       +
       +#ifndef kroundup32
       +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
       +#endif
       +
       +#ifndef kcalloc
       +#define kcalloc(N,Z) calloc(N,Z)
       +#endif
       +#ifndef kmalloc
       +#define kmalloc(Z) malloc(Z)
       +#endif
       +#ifndef krealloc
       +#define krealloc(P,Z) realloc(P,Z)
       +#endif
       +#ifndef kfree
       +#define kfree(P) free(P)
       +#endif
       +
       +static const double __ac_HASH_UPPER = 0.77;
       +
       +#define __KHASH_TYPE(name, khkey_t, khval_t) \
       +        typedef struct kh_##name##_s { \
       +                khint_t n_buckets, size, n_occupied, upper_bound; \
       +                khint32_t *flags; \
       +                khkey_t *keys; \
       +                khval_t *vals; \
       +        } kh_##name##_t;
       +
       +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t)                                                 \
       +        extern kh_##name##_t *kh_init_##name(void);                                                        \
       +        extern void kh_destroy_##name(kh_##name##_t *h);                                        \
       +        extern void kh_clear_##name(kh_##name##_t *h);                                                \
       +        extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key);         \
       +        extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
       +        extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
       +        extern void kh_del_##name(kh_##name##_t *h, khint_t x);
       +
       +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
       +        SCOPE kh_##name##_t *kh_init_##name(void) {                                                        \
       +                return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t));                \
       +        }                                                                                                                                        \
       +        SCOPE void kh_destroy_##name(kh_##name##_t *h)                                                \
       +        {                                                                                                                                        \
       +                if (h) {                                                                                                                \
       +                        kfree((void *)h->keys); kfree(h->flags);                                        \
       +                        kfree((void *)h->vals);                                                                                \
       +                        kfree(h);                                                                                                        \
       +                }                                                                                                                                \
       +        }                                                                                                                                        \
       +        SCOPE void kh_clear_##name(kh_##name##_t *h)                                                \
       +        {                                                                                                                                        \
       +                if (h && h->flags) {                                                                                        \
       +                        memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
       +                        h->size = h->n_occupied = 0;                                                                \
       +                }                                                                                                                                \
       +        }                                                                                                                                        \
       +        SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key)         \
       +        {                                                                                                                                        \
       +                if (h->n_buckets) {                                                                                                \
       +                        khint_t k, i, last, mask, step = 0; \
       +                        mask = h->n_buckets - 1;                                                                        \
       +                        k = __hash_func(key); i = k & mask;                                                        \
       +                        last = i; \
       +                        while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
       +                                i = (i + (++step)) & mask; \
       +                                if (i == last) return h->n_buckets;                                                \
       +                        }                                                                                                                        \
       +                        return __ac_iseither(h->flags, i)? h->n_buckets : i;                \
       +                } else return 0;                                                                                                \
       +        }                                                                                                                                        \
       +        SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
       +        { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
       +                khint32_t *new_flags = 0;                                                                                \
       +                khint_t j = 1;                                                                                                        \
       +                {                                                                                                                                \
       +                        kroundup32(new_n_buckets);                                                                         \
       +                        if (new_n_buckets < 4) new_n_buckets = 4;                                        \
       +                        if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0;        /* requested size is too small */ \
       +                        else { /* hash table size to be changed (shrink or expand); rehash */ \
       +                                new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t));        \
       +                                if (!new_flags) return -1;                                                                \
       +                                memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
       +                                if (h->n_buckets < new_n_buckets) {        /* expand */                \
       +                                        khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
       +                                        if (!new_keys) { kfree(new_flags); return -1; }                \
       +                                        h->keys = new_keys;                                                                        \
       +                                        if (kh_is_map) {                                                                        \
       +                                                khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
       +                                                if (!new_vals) { kfree(new_flags); return -1; }        \
       +                                                h->vals = new_vals;                                                                \
       +                                        }                                                                                                        \
       +                                } /* otherwise shrink */                                                                \
       +                        }                                                                                                                        \
       +                }                                                                                                                                \
       +                if (j) { /* rehashing is needed */                                                                \
       +                        for (j = 0; j != h->n_buckets; ++j) {                                                \
       +                                if (__ac_iseither(h->flags, j) == 0) {                                        \
       +                                        khkey_t key = h->keys[j];                                                        \
       +                                        khval_t val;                                                                                \
       +                                        khint_t new_mask;                                                                        \
       +                                        new_mask = new_n_buckets - 1;                                                 \
       +                                        if (kh_is_map) val = h->vals[j];                                        \
       +                                        __ac_set_isdel_true(h->flags, j);                                        \
       +                                        while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
       +                                                khint_t k, i, step = 0; \
       +                                                k = __hash_func(key);                                                        \
       +                                                i = k & new_mask;                                                                \
       +                                                while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
       +                                                __ac_set_isempty_false(new_flags, i);                        \
       +                                                if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
       +                                                        { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
       +                                                        if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
       +                                                        __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
       +                                                } else { /* write the element and jump out of the loop */ \
       +                                                        h->keys[i] = key;                                                        \
       +                                                        if (kh_is_map) h->vals[i] = val;                        \
       +                                                        break;                                                                                \
       +                                                }                                                                                                \
       +                                        }                                                                                                        \
       +                                }                                                                                                                \
       +                        }                                                                                                                        \
       +                        if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
       +                                h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
       +                                if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
       +                        }                                                                                                                        \
       +                        kfree(h->flags); /* free the working space */                                \
       +                        h->flags = new_flags;                                                                                \
       +                        h->n_buckets = new_n_buckets;                                                                \
       +                        h->n_occupied = h->size;                                                                        \
       +                        h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
       +                }                                                                                                                                \
       +                return 0;                                                                                                                \
       +        }                                                                                                                                        \
       +        SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
       +        {                                                                                                                                        \
       +                khint_t x;                                                                                                                \
       +                if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
       +                        if (h->n_buckets > (h->size<<1)) {                                                        \
       +                                if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
       +                                        *ret = -1; return h->n_buckets;                                                \
       +                                }                                                                                                                \
       +                        } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
       +                                *ret = -1; return h->n_buckets;                                                        \
       +                        }                                                                                                                        \
       +                } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
       +                {                                                                                                                                \
       +                        khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
       +                        x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
       +                        if (__ac_isempty(h->flags, i)) x = i; /* for speed up */        \
       +                        else {                                                                                                                \
       +                                last = i; \
       +                                while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
       +                                        if (__ac_isdel(h->flags, i)) site = i;                                \
       +                                        i = (i + (++step)) & mask; \
       +                                        if (i == last) { x = site; break; }                                        \
       +                                }                                                                                                                \
       +                                if (x == h->n_buckets) {                                                                \
       +                                        if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
       +                                        else x = i;                                                                                        \
       +                                }                                                                                                                \
       +                        }                                                                                                                        \
       +                }                                                                                                                                \
       +                if (__ac_isempty(h->flags, x)) { /* not present at all */                \
       +                        h->keys[x] = key;                                                                                        \
       +                        __ac_set_isboth_false(h->flags, x);                                                        \
       +                        ++h->size; ++h->n_occupied;                                                                        \
       +                        *ret = 1;                                                                                                        \
       +                } else if (__ac_isdel(h->flags, x)) { /* deleted */                                \
       +                        h->keys[x] = key;                                                                                        \
       +                        __ac_set_isboth_false(h->flags, x);                                                        \
       +                        ++h->size;                                                                                                        \
       +                        *ret = 2;                                                                                                        \
       +                } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
       +                return x;                                                                                                                \
       +        }                                                                                                                                        \
       +        SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x)                                \
       +        {                                                                                                                                        \
       +                if (x != h->n_buckets && !__ac_iseither(h->flags, x)) {                        \
       +                        __ac_set_isdel_true(h->flags, x);                                                        \
       +                        --h->size;                                                                                                        \
       +                }                                                                                                                                \
       +        }
       +
       +#define KHASH_DECLARE(name, khkey_t, khval_t)                                                         \
       +        __KHASH_TYPE(name, khkey_t, khval_t)                                                                 \
       +        __KHASH_PROTOTYPES(name, khkey_t, khval_t)
       +
       +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
       +        __KHASH_TYPE(name, khkey_t, khval_t)                                                                 \
       +        __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
       +
       +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
       +        KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
       +
       +/* --- BEGIN OF HASH FUNCTIONS --- */
       +
       +/*! @function
       +  @abstract     Integer hash function
       +  @param  key   The integer [khint32_t]
       +  @return       The hash value [khint_t]
       + */
       +#define kh_int_hash_func(key) (khint32_t)(key)
       +/*! @function
       +  @abstract     Integer comparison function
       + */
       +#define kh_int_hash_equal(a, b) ((a) == (b))
       +/*! @function
       +  @abstract     64-bit integer hash function
       +  @param  key   The integer [khint64_t]
       +  @return       The hash value [khint_t]
       + */
       +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
       +/*! @function
       +  @abstract     64-bit integer comparison function
       + */
       +#define kh_int64_hash_equal(a, b) ((a) == (b))
       +/*! @function
       +  @abstract     const char* hash function
       +  @param  s     Pointer to a null terminated string
       +  @return       The hash value
       + */
       +static kh_inline khint_t __ac_X31_hash_string(const char *s)
       +{
       +        khint_t h = (khint_t)*s;
       +        if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
       +        return h;
       +}
       +/*! @function
       +  @abstract     Another interface to const char* hash function
       +  @param  key   Pointer to a null terminated string [const char*]
       +  @return       The hash value [khint_t]
       + */
       +#define kh_str_hash_func(key) __ac_X31_hash_string(key)
       +/*! @function
       +  @abstract     Const char* comparison function
       + */
       +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
       +
       +static kh_inline khint_t __ac_Wang_hash(khint_t key)
       +{
       +    key += ~(key << 15);
       +    key ^=  (key >> 10);
       +    key +=  (key << 3);
       +    key ^=  (key >> 6);
       +    key += ~(key << 11);
       +    key ^=  (key >> 16);
       +    return key;
       +}
       +#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)
       +
       +/* --- END OF HASH FUNCTIONS --- */
       +
       +/* Other convenient macros... */
       +
       +/*!
       +  @abstract Type of the hash table.
       +  @param  name  Name of the hash table [symbol]
       + */
       +#define khash_t(name) kh_##name##_t
       +
       +/*! @function
       +  @abstract     Initiate a hash table.
       +  @param  name  Name of the hash table [symbol]
       +  @return       Pointer to the hash table [khash_t(name)*]
       + */
       +#define kh_init(name) kh_init_##name()
       +
       +/*! @function
       +  @abstract     Destroy a hash table.
       +  @param  name  Name of the hash table [symbol]
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       + */
       +#define kh_destroy(name, h) kh_destroy_##name(h)
       +
       +/*! @function
       +  @abstract     Reset a hash table without deallocating memory.
       +  @param  name  Name of the hash table [symbol]
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       + */
       +#define kh_clear(name, h) kh_clear_##name(h)
       +
       +/*! @function
       +  @abstract     Resize a hash table.
       +  @param  name  Name of the hash table [symbol]
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  s     New size [khint_t]
       + */
       +#define kh_resize(name, h, s) kh_resize_##name(h, s)
       +
       +/*! @function
       +  @abstract     Insert a key to the hash table.
       +  @param  name  Name of the hash table [symbol]
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  k     Key [type of keys]
       +  @param  r     Extra return code: -1 if the operation failed;
       +                0 if the key is present in the hash table;
       +                1 if the bucket is empty (never used); 2 if the element in
       +                                the bucket has been deleted [int*]
       +  @return       Iterator to the inserted element [khint_t]
       + */
       +#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
       +
       +/*! @function
       +  @abstract     Retrieve a key from the hash table.
       +  @param  name  Name of the hash table [symbol]
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  k     Key [type of keys]
       +  @return       Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
       + */
       +#define kh_get(name, h, k) kh_get_##name(h, k)
       +
       +/*! @function
       +  @abstract     Remove a key from the hash table.
       +  @param  name  Name of the hash table [symbol]
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  k     Iterator to the element to be deleted [khint_t]
       + */
       +#define kh_del(name, h, k) kh_del_##name(h, k)
       +
       +/*! @function
       +  @abstract     Test whether a bucket contains data.
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  x     Iterator to the bucket [khint_t]
       +  @return       1 if containing data; 0 otherwise [int]
       + */
       +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
       +
       +/*! @function
       +  @abstract     Get key given an iterator
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  x     Iterator to the bucket [khint_t]
       +  @return       Key [type of keys]
       + */
       +#define kh_key(h, x) ((h)->keys[x])
       +
       +/*! @function
       +  @abstract     Get value given an iterator
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  x     Iterator to the bucket [khint_t]
       +  @return       Value [type of values]
       +  @discussion   For hash sets, calling this results in segfault.
       + */
       +#define kh_val(h, x) ((h)->vals[x])
       +
       +/*! @function
       +  @abstract     Alias of kh_val()
       + */
       +#define kh_value(h, x) ((h)->vals[x])
       +
       +/*! @function
       +  @abstract     Get the start iterator
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @return       The start iterator [khint_t]
       + */
       +#define kh_begin(h) (khint_t)(0)
       +
       +/*! @function
       +  @abstract     Get the end iterator
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @return       The end iterator [khint_t]
       + */
       +#define kh_end(h) ((h)->n_buckets)
       +
       +/*! @function
       +  @abstract     Get the number of elements in the hash table
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @return       Number of elements in the hash table [khint_t]
       + */
       +#define kh_size(h) ((h)->size)
       +
       +/*! @function
       +  @abstract     Get the number of buckets in the hash table
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @return       Number of buckets in the hash table [khint_t]
       + */
       +#define kh_n_buckets(h) ((h)->n_buckets)
       +
       +/*! @function
       +  @abstract     Iterate over the entries in the hash table
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  kvar  Variable to which key will be assigned
       +  @param  vvar  Variable to which value will be assigned
       +  @param  code  Block of code to execute
       + */
       +#define kh_foreach(h, kvar, vvar, code) { khint_t __i;                \
       +        for (__i = kh_begin(h); __i != kh_end(h); ++__i) {                \
       +                if (!kh_exist(h,__i)) continue;                                                \
       +                (kvar) = kh_key(h,__i);                                                                \
       +                (vvar) = kh_val(h,__i);                                                                \
       +                code;                                                                                                \
       +        } }
       +
       +/*! @function
       +  @abstract     Iterate over the values in the hash table
       +  @param  h     Pointer to the hash table [khash_t(name)*]
       +  @param  vvar  Variable to which value will be assigned
       +  @param  code  Block of code to execute
       + */
       +#define kh_foreach_value(h, vvar, code) { khint_t __i;                \
       +        for (__i = kh_begin(h); __i != kh_end(h); ++__i) {                \
       +                if (!kh_exist(h,__i)) continue;                                                \
       +                (vvar) = kh_val(h,__i);                                                                \
       +                code;                                                                                                \
       +        } }
       +
       +/* More conenient interfaces */
       +
       +/*! @function
       +  @abstract     Instantiate a hash set containing integer keys
       +  @param  name  Name of the hash table [symbol]
       + */
       +#define KHASH_SET_INIT_INT(name)                                                                                \
       +        KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
       +
       +/*! @function
       +  @abstract     Instantiate a hash map containing integer keys
       +  @param  name  Name of the hash table [symbol]
       +  @param  khval_t  Type of values [type]
       + */
       +#define KHASH_MAP_INIT_INT(name, khval_t)                                                                \
       +        KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
       +
       +/*! @function
       +  @abstract     Instantiate a hash map containing 64-bit integer keys
       +  @param  name  Name of the hash table [symbol]
       + */
       +#define KHASH_SET_INIT_INT64(name)                                                                                \
       +        KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
       +
       +/*! @function
       +  @abstract     Instantiate a hash map containing 64-bit integer keys
       +  @param  name  Name of the hash table [symbol]
       +  @param  khval_t  Type of values [type]
       + */
       +#define KHASH_MAP_INIT_INT64(name, khval_t)                                                                \
       +        KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
       +
       +typedef const char *kh_cstr_t;
       +/*! @function
       +  @abstract     Instantiate a hash map containing const char* keys
       +  @param  name  Name of the hash table [symbol]
       + */
       +#define KHASH_SET_INIT_STR(name)                                                                                \
       +        KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
       +
       +/*! @function
       +  @abstract     Instantiate a hash map containing const char* keys
       +  @param  name  Name of the hash table [symbol]
       +  @param  khval_t  Type of values [type]
       + */
       +#define KHASH_MAP_INIT_STR(name, khval_t)                                                                \
       +        KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
       +
       +#endif /* __AC_KHASH_H */
   DIR diff --git a/ltk.c b/ltk.c
       t@@ -0,0 +1,563 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <fcntl.h>
       +#include <unistd.h>
       +#include <time.h>
       +#include <sys/stat.h>
       +#include <sys/select.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include "khash.h"
       +#include "ini.h"
       +#include "ltk.h"
       +#include "grid.h"
       +#include "button.h"
       +
       +static void ltk_load_theme(ltk_window *window, const char *path);
       +static void ltk_destroy_theme(ltk_theme *theme);
       +
       +static int running = 1;
       +
       +static ltk_rect
       +ltk_rect_union(ltk_rect r1, ltk_rect r2) {
       +        ltk_rect u;
       +        u.x = r1.x < r2.x ? r1.x : r2.x;
       +        u.y = r1.y < r2.y ? r1.y : r2.y;
       +        int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w;
       +        int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h;
       +        u.w = x2 - u.x;
       +        u.h = y2 - u.y;
       +        return u;
       +}
       +
       +void
       +ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
       +        if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0)
       +                window->dirty_rect = rect;
       +        else
       +                window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
       +}
       +
       +void
       +ltk_clean_up(ltk_window *window) {
       +        XCloseDisplay(window->dpy);
       +        ltk_destroy_theme(window->theme);
       +        ltk_destroy_window(window);
       +}
       +
       +void
       +ltk_quit(ltk_window *window) {
       +        ltk_clean_up(window);
       +        running = 0;
       +}
       +
       +void
       +ltk_fatal(const char *msg) {
       +        (void)fprintf(stderr, msg);
       +        /* FIXME: clean up first */
       +        exit(1);
       +};
       +
       +XColor
       +ltk_create_xcolor(ltk_window *window, const char *hex) {
       +        XColor color;
       +        XParseColor(window->dpy, window->cm, hex, &color);
       +        XAllocColor(window->dpy, window->cm, &color);
       +
       +        return color;
       +}
       +
       +void
       +ltk_queue_event(ltk_window *window, const char *id, const char *name) {
       +        struct ltk_event_queue *new = malloc(sizeof(struct ltk_event_queue));
       +        if (!new) ltk_fatal("Unable to queue event.\n");
       +        new->id = strdup(id);
       +        new->name = strdup(name);
       +        new->next = window->first_event;
       +        window->first_event = new;
       +        new->prev = NULL;
       +        if (!window->last_event)
       +                window->last_event = new;
       +}
       +
       +static void
       +create_widget(ltk_window *window, char tokens[10][20], size_t num_tokens) {
       +        if (num_tokens < 3) {
       +                (void)fprintf(stderr, "Invalid number of arguments.\n");
       +                return;
       +        }
       +        ltk_widget *widget;
       +        khint_t k = kh_get(widget, window->widget_hash, tokens[2]);
       +        if (k != kh_end(window->widget_hash)) {
       +                (void)fprintf(stderr, "Widget id already exists.\n");
       +                return;
       +        }
       +        if (strcmp(tokens[1], "button") == 0) {
       +                /* yeah, text is currently ignored... */
       +                widget = ltk_button_create(window, tokens[2], "I'm a button!");
       +        } else {
       +                (void)fprintf(stderr, "Invalid widget type.\n");
       +                return;
       +        }
       +        int ret;
       +        /* apparently, khash requires the string to stay accessible */
       +        /* FIXME: actually free this hash table in the end... */
       +        char *tmp = strdup(tokens[2]);
       +        k = kh_put(widget, window->widget_hash, tmp, &ret);
       +        kh_value(window->widget_hash, k) = widget;
       +}
       +
       +static void
       +grid_widget(ltk_window *window, char tokens[10][20], size_t num_tokens) {
       +        if (num_tokens < 4) {
       +                (void)fprintf(stderr, "Invalid number of arguments.\n");
       +                return;
       +        }
       +        ltk_widget *widget;
       +        khint_t k = kh_get(widget, window->widget_hash, tokens[1]);
       +        if (k == kh_end(window->widget_hash)) {
       +                (void)fprintf(stderr, "Widget with given id doesn't exist.\n");
       +                return;
       +        }
       +        widget = kh_value(window->widget_hash, k);
       +        /* FIXME: error checking */
       +        int row = atoi(tokens[2]);
       +        int column = atoi(tokens[3]);
       +        ltk_grid_widget(widget, window->root_widget, row, column, 1, 1, LTK_STICKY_LEFT | LTK_STICKY_RIGHT);
       +        ltk_window_invalidate_rect(window, window->root_widget->rect);
       +}
       +
       +/* copied from suckless ii */
       +static int
       +read_line(int fd, char *buf, size_t bufsiz)
       +{
       +        size_t i = 0;
       +        char c = '\0';
       +        do {
       +                if (read(fd, &c, sizeof(char)) != sizeof(char))
       +                        return -1;
       +                buf[i++] = c;
       +        } while (c != '\n' && i < bufsiz);
       +        buf[i - 1] = '\0'; /* eliminates '\n' */
       +        return 0;
       +}
       +
       +static size_t
       +tokenize(char tokens[10][20], char *buf) {
       +        char *c;
       +        if (!buf || buf[0] == '\0') return 0;
       +        for (c = buf; *c == ' '; c++)
       +                ;
       +        size_t cur_tok = 0;
       +        size_t cur_tok_len = 0;
       +        while (*c != '\0') {
       +                if (cur_tok >= 10) {
       +                        return 0;
       +                } else if (*c == ' ') {
       +                        tokens[cur_tok][cur_tok_len] = '\0';
       +                        cur_tok++;
       +                        cur_tok_len = 0;
       +                } else if (cur_tok_len >= 19) {
       +                        return 0;
       +                } else {
       +                        tokens[cur_tok][cur_tok_len++] = *c;
       +                }
       +                c++;
       +        }
       +        tokens[cur_tok][cur_tok_len] = '\0';
       +        return cur_tok + 1;
       +}
       +
       +static void
       +proc_cmds(ltk_window *window, char *buf) {
       +        char tokens[10][20];
       +        int num_tokens;
       +        if (!(num_tokens = tokenize(tokens, buf))) return;
       +        if (strcmp(tokens[0], "create") == 0) {
       +                create_widget(window, tokens, num_tokens);
       +        } else if (strcmp(tokens[0], "grid") == 0) {
       +                grid_widget(window, tokens, num_tokens);
       +        } else {
       +                (void)fprintf(stderr, "Invalid command.\n");
       +        }
       +}
       +
       +/* copied from suckless st */
       +#define TIMEDIFF(t1, t2)        ((t1.tv_sec-t2.tv_sec)*1000 + \
       +                                (t1.tv_nsec-t2.tv_nsec)/1E6)
       +
       +/* FIXME: destroy remaining widgets in hash on exit */
       +int
       +ltk_mainloop(ltk_window *window) {
       +        XEvent event;
       +        int fd_in;
       +        struct stat st;
       +        struct timespec tick;
       +        tick.tv_sec = 0;
       +        tick.tv_nsec = 10000;
       +        char buf[200]; /* FIXME: what would be sensible? */
       +
       +        if (lstat("ltk_in", &st) != -1) {
       +                /* FIXME... */
       +                unlink("ltk_in");
       +        }
       +
       +        if (mkfifo("ltk_in", S_IRWXU)) return -1;
       +        fd_in = open("ltk_in", O_RDONLY | O_NONBLOCK, 0);
       +        if (fd_in == -1) return -1;
       +        while (running) {
       +                while (XPending(window->dpy)) {
       +                        XNextEvent(window->dpy, &event);
       +                        ltk_handle_event(window, event);
       +                }
       +                if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) {
       +                        ltk_redraw_window(window);
       +                        window->dirty_rect.w = 0;
       +                        window->dirty_rect.h = 0;
       +                } else if (window->last_event) {
       +                        struct ltk_event_queue *cur = window->last_event;
       +                        struct ltk_event_queue *last;
       +                        do {
       +                                printf("%s %s\n", cur->id, cur->name);
       +                                free(cur->id);
       +                                free(cur->name);
       +                                last = cur;
       +                                cur = cur->prev;
       +                                free(last);
       +                        } while (cur);
       +                        window->first_event = window->last_event = NULL;
       +                } else if (!read_line(fd_in, buf, sizeof(buf))) {
       +                        proc_cmds(window, buf);
       +                }
       +                /* yes, this should be improved */
       +                nanosleep(&tick, NULL);
       +
       +        }
       +        unlink("ltk_in");
       +}
       +
       +void
       +ltk_redraw_window(ltk_window *window) {
       +        ltk_widget *ptr;
       +        if (!window) return;
       +        if (window->dirty_rect.x >= window->rect.w) return;
       +        if (window->dirty_rect.y >= window->rect.h) return;
       +        if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
       +                window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
       +        if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
       +                window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
       +        XClearArea(window->dpy, window->xwindow, window->dirty_rect.x, window->dirty_rect.y, window->dirty_rect.w, window->dirty_rect.h, False);
       +        if (!window->root_widget) return;
       +        /* FIXME: actually respect the dirty rect... */
       +        ptr = window->root_widget;
       +        ptr->draw(ptr);
       +}
       +
       +ltk_window *
       +ltk_create_window(const char *theme_path, const char *title, int x, int y, unsigned int w, unsigned int h) {
       +        ltk_window *window = malloc(sizeof(ltk_window));
       +        if (!window)
       +                ltk_fatal("Not enough memory left for window!\n");
       +
       +        window->dpy = XOpenDisplay(NULL);
       +        window->screen = DefaultScreen(window->dpy);
       +        window->cm = DefaultColormap(window->dpy, window->screen);
       +        ltk_load_theme(window, theme_path);
       +        window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False);
       +
       +        ltk_window_theme *wtheme = window->theme->window;
       +        window->xwindow =
       +            XCreateSimpleWindow(window->dpy, DefaultRootWindow(window->dpy), x, y,
       +                                w, h, wtheme->border_width,
       +                                wtheme->fg.pixel, wtheme->bg.pixel);
       +        window->gc = XCreateGC(window->dpy, window->xwindow, 0, 0);
       +        XSetForeground(window->dpy, window->gc, wtheme->fg.pixel);
       +        XSetBackground(window->dpy, window->gc, wtheme->bg.pixel);
       +        XSetStandardProperties(window->dpy, window->xwindow, title, NULL, None,
       +                               NULL, 0, NULL);
       +        XSetWMProtocols(window->dpy, window->xwindow, &window->wm_delete_msg, 1);
       +        window->root_widget = NULL;
       +
       +        window->other_event = &ltk_window_other_event;
       +
       +        window->rect.w = 0;
       +        window->rect.h = 0;
       +        window->rect.x = 0;
       +        window->rect.y = 0;
       +        window->dirty_rect.w = 0;
       +        window->dirty_rect.h = 0;
       +        window->dirty_rect.x = 0;
       +        window->dirty_rect.y = 0;
       +
       +        window->widget_hash = kh_init(widget);
       +
       +        XClearWindow(window->dpy, window->xwindow);
       +        XMapRaised(window->dpy, window->xwindow);
       +        XSelectInput(window->dpy, window->xwindow,
       +                     ExposureMask | KeyPressMask | KeyReleaseMask |
       +                     ButtonPressMask | ButtonReleaseMask |
       +                     StructureNotifyMask | PointerMotionMask);
       +
       +        return window;
       +}
       +
       +void
       +ltk_destroy_window(ltk_window *window) {
       +        ltk_widget *ptr = window->root_widget;
       +        if (ptr) ptr->destroy(ptr);
       +        XDestroyWindow(window->dpy, window->xwindow);
       +        free(window);
       +}
       +
       +void
       +ltk_window_other_event(ltk_window *window, XEvent event) {
       +        ltk_widget *ptr = window->root_widget;
       +        int retval = 0;
       +        if (event.type == ConfigureNotify) {
       +                unsigned int w, h;
       +                w = event.xconfigure.width;
       +                h = event.xconfigure.height;
       +                int orig_w = window->rect.w;
       +                int orig_h = window->rect.h;
       +                if (orig_w != w || orig_h != h) {
       +                        window->rect.w = w;
       +                        window->rect.h = h;
       +                        if (ptr && ptr->resize) {
       +                                ptr->rect.w = w;
       +                                ptr->rect.h = h;
       +                                ptr->resize(ptr, orig_w, orig_h);
       +                        }
       +                }
       +        } else if (event.type == Expose && event.xexpose.count == 0) {
       +                ltk_rect r;
       +                r.x = event.xexpose.x;
       +                r.y = event.xexpose.y;
       +                r.w = event.xexpose.width;
       +                r.h = event.xexpose.height;
       +                ltk_window_invalidate_rect(window, r);
       +        } else if (event.type == ClientMessage
       +            && event.xclient.data.l[0] == window->wm_delete_msg) {
       +                ltk_destroy_window(window);
       +                exit(0); /* FIXME */
       +        }
       +}
       +
       +void
       +ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        if (strcmp(prop, "border_width") == 0) {
       +                window->theme->window->border_width = atoi(value);
       +        } else if (strcmp(prop, "bg") == 0) {
       +                window->theme->window->bg = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "fg") == 0) {
       +                window->theme->window->fg = ltk_create_xcolor(window, value);
       +        } else if (strcmp(prop, "font") == 0) {
       +                window->theme->window->font = strdup(value);
       +        }
       +}
       +
       +int
       +ltk_ini_handler(ltk_window *window, const char *widget, const char *prop, const char *value) {
       +        if (strcmp(widget, "window") == 0) {
       +                ltk_window_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "button") == 0) {
       +                ltk_button_ini_handler(window, prop, value);
       +        }
       +}
       +
       +static void
       +ltk_load_theme(ltk_window *window, const char *path) {
       +        window->theme = malloc(sizeof(ltk_theme));
       +        if (!window->theme) ltk_fatal("Unable to allocate memory for theme.\n");
       +        window->theme->window = malloc(sizeof(ltk_window_theme));
       +        if (!window->theme->window) ltk_fatal("Unable to allocate memory for window theme.\n");
       +        window->theme->button = NULL;
       +        if (ini_parse(path, ltk_ini_handler, window) < 0) {
       +                (void)fprintf(stderr, "ERROR: Can't load theme %s\n.", path);
       +                exit(1);
       +        }
       +}
       +
       +static void
       +ltk_destroy_theme(ltk_theme *theme) {
       +        free(theme->button);
       +        free(theme->window);
       +        free(theme);
       +}
       +
       +int
       +ltk_collide_rect(ltk_rect rect, int x, int y) {
       +        return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y
       +                && (rect.y + rect.h) >= y);
       +}
       +
       +void
       +ltk_window_remove_active_widget(ltk_window *window) {
       +        ltk_widget *widget = window->active_widget;
       +        if (!widget) return;
       +        while (widget) {
       +                widget->state = LTK_NORMAL;
       +                widget->active_widget = NULL;
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(window, widget->rect);
       +                widget = widget->parent;
       +        }
       +        window->active_widget = NULL;
       +}
       +
       +void
       +ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
       +        window->active_widget = widget;
       +        ltk_widget *parent = widget->parent;
       +        widget->state = LTK_ACTIVE;
       +        while (parent) {
       +                widget->state = LTK_ACTIVE;
       +                parent->active_widget = widget;
       +                widget = parent;
       +                parent = widget->parent;
       +        }
       +}
       +
       +void
       +ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
       +    void (*draw) (void *), void (*destroy) (void *), unsigned int needs_redraw) {
       +        widget->id = strdup(id);
       +        widget->window = window;
       +        widget->active_widget = NULL;
       +        widget->parent = NULL;
       +
       +        widget->key_press = NULL;
       +        widget->key_release = NULL;
       +        widget->mouse_press = NULL;
       +        widget->mouse_release = NULL;
       +        widget->motion_notify = NULL;
       +        widget->mouse_enter = NULL;
       +        widget->mouse_leave = NULL;
       +
       +        widget->resize = NULL;
       +        widget->draw = draw;
       +        widget->destroy = destroy;
       +
       +        widget->needs_redraw = needs_redraw;
       +        widget->state = LTK_NORMAL;
       +        widget->row = 0;
       +        widget->rect.x = 0;
       +        widget->rect.y = 0;
       +        widget->rect.w = 100;
       +        widget->rect.h = 100;
       +
       +        widget->row = NULL;
       +        widget->column = NULL;
       +        widget->row_span = NULL;
       +        widget->column_span = NULL;
       +        widget->sticky = 0;
       +}
       +
       +void
       +ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
       +        if (!widget || widget->state == LTK_DISABLED)
       +                return;
       +        if (event.xbutton.button == 1) {
       +                ltk_widget *parent = widget->parent;
       +                widget->state = LTK_PRESSED;
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(widget->window, widget->rect);
       +        }
       +        if (widget->mouse_press) {
       +                widget->mouse_press(widget, event);
       +        }
       +}
       +
       +void
       +ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
       +        if (!widget || widget->state == LTK_DISABLED)
       +                return;
       +        if (widget->state == LTK_PRESSED) {
       +                widget->state = LTK_ACTIVE;
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(widget->window, widget->rect);
       +        }
       +        if (widget->mouse_release) {
       +                widget->mouse_release(widget, event);
       +        }
       +}
       +
       +void
       +ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
       +        if (!widget) return;
       +        short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
       +        if ((widget->state == LTK_NORMAL) && !pressed) {
       +                widget->state = LTK_ACTIVE;
       +                if (widget->mouse_enter)
       +                        widget->mouse_enter(widget, event);
       +                /* FIXME: do this properly */
       +                if (widget->window->active_widget != widget && !widget->motion_notify) {
       +                        ltk_window_remove_active_widget(widget->window);
       +                        ltk_window_set_active_widget(widget->window, widget);
       +                }
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(widget->window, widget->rect);
       +        }
       +        if (widget->motion_notify)
       +                widget->motion_notify(widget, event);
       +}
       +
       +void
       +ltk_handle_event(ltk_window *window, XEvent event) {
       +        ltk_widget *root_widget = window->root_widget;
       +        switch (event.type) {
       +        case KeyPress:
       +                break;
       +        case KeyRelease:
       +                break;
       +        case ButtonPress:
       +                if (root_widget)
       +                        ltk_widget_mouse_press_event(root_widget, event);
       +                break;
       +        case ButtonRelease:
       +                if (root_widget)
       +                        ltk_widget_mouse_release_event(root_widget, event);
       +                break;
       +        case MotionNotify:
       +                if (root_widget)
       +                        ltk_widget_motion_notify_event(root_widget, event);
       +                break;
       +        default:
       +                if (window->other_event)
       +                        window->other_event(window, event);
       +        }
       +}
       +
       +int main(int argc, char *argv[]) {
       +        ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500);
       +        ltk_grid *grid = ltk_create_grid(window, "grid", 2, 2);
       +        window->root_widget = grid;
       +        ltk_set_row_weight(grid, 0, 1);
       +        ltk_set_row_weight(grid, 1, 1);
       +        ltk_set_column_weight(grid, 0, 1);
       +        ltk_set_column_weight(grid, 1, 1);
       +        ltk_mainloop(window);
       +}
   DIR diff --git a/ltk.h b/ltk.h
       t@@ -0,0 +1,158 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +#ifndef _LTK_H_
       +#define _LTK_H_
       +
       +/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "drw.h" */
       +
       +typedef struct {
       +        int x;
       +        int y;
       +        int w;
       +        int h;
       +} ltk_rect;
       +
       +typedef enum {
       +        LTK_STICKY_LEFT = 1 << 0,
       +        LTK_STICKY_RIGHT = 1 << 1,
       +        LTK_STICKY_TOP = 1 << 2,
       +        LTK_STICKY_BOTTOM = 1 << 3
       +} ltk_sticky_mask;
       +
       +typedef enum {
       +        LTK_NORMAL,
       +        LTK_PRESSED,
       +        LTK_ACTIVE,
       +        LTK_DISABLED
       +} ltk_widget_state;
       +
       +typedef struct ltk_window ltk_window;
       +
       +typedef struct ltk_widget {
       +        ltk_window *window;
       +        struct ltk_widget *active_widget;
       +        struct ltk_widget *parent;
       +        char *id;
       +
       +        void (*key_press) (void *, XEvent event);
       +        void (*key_release) (void *, XEvent event);
       +        void (*mouse_press) (void *, XEvent event);
       +        void (*mouse_release) (void *, XEvent event);
       +        void (*motion_notify) (void *, XEvent event);
       +        void (*mouse_leave) (void *, XEvent event);
       +        void (*mouse_enter) (void *, XEvent event);
       +
       +        void (*resize) (void *, int, int);
       +        void (*draw) (void *);
       +        void (*destroy) (void *);
       +
       +        ltk_rect rect;
       +        unsigned int row;
       +        unsigned int column;
       +        unsigned int row_span;
       +        unsigned int column_span;
       +        unsigned int needs_redraw;
       +        ltk_widget_state state;
       +        unsigned short sticky;
       +} ltk_widget;
       +
       +typedef struct {
       +        int border_width;
       +        char *font;
       +        XColor fg;
       +        XColor bg;
       +} ltk_window_theme;
       +
       +typedef struct ltk_button_theme ltk_button_theme;
       +
       +typedef struct {
       +        ltk_window_theme *window;
       +        ltk_button_theme *button;
       +} ltk_theme;
       +
       +struct ltk_event_queue {
       +        char *id;
       +        char *name;
       +        struct ltk_event_queue *prev;
       +        struct ltk_event_queue *next;
       +};
       +
       +KHASH_MAP_INIT_STR(widget, ltk_widget *)
       +
       +typedef struct ltk_window {
       +        Display *dpy;
       +        Colormap cm;
       +        GC gc;
       +        int screen;
       +        Atom wm_delete_msg;
       +        Window xwindow;
       +        ltk_widget *root_widget;
       +        ltk_widget *active_widget;
       +        int (*other_event) (ltk_window *, XEvent event);
       +        ltk_rect rect;
       +        ltk_theme *theme;
       +        ltk_rect dirty_rect;
       +        struct ltk_event_queue *first_event;
       +        struct ltk_event_queue *last_event;
       +        khash_t(widget) *widget_hash;
       +} ltk_window;
       +
       +void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
       +
       +void ltk_fatal(const char *msg);
       +
       +XColor ltk_create_xcolor(ltk_window *window, const char *hex);
       +
       +void ltk_queue_event(ltk_window *window, const char *id, const char *name);
       +
       +int ltk_mainloop(ltk_window *window);
       +
       +ltk_window *ltk_create_window(
       +    const char *theme_path, const char *title,
       +    int x, int y, unsigned int w, unsigned int h);
       +
       +void ltk_redraw_window(ltk_window *window);
       +
       +void ltk_destroy_window(ltk_window *window);
       +
       +void ltk_window_other_event(ltk_window *window, XEvent event);
       +
       +int ltk_collide_rect(ltk_rect rect, int x, int y);
       +
       +void ltk_remove_active_widget(ltk_widget *widget);
       +
       +void ltk_set_active_widget(ltk_window *window, ltk_widget *widget);
       +
       +void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window * window,
       +        void (*draw) (void *), void (*destroy) (void *), unsigned int needs_redraw);
       +
       +void ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event);
       +
       +void ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event);
       +
       +void ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event);
       +
       +void ltk_handle_event(ltk_window *window, XEvent event);
       +
       +#endif
   DIR diff --git a/test.sh b/test.sh
       t@@ -0,0 +1,6 @@
       +#!/bin/sh
       +
       +echo "create button btn1" > ltk_in
       +echo "grid btn1 0 0" > ltk_in
       +echo "create button btn2" > ltk_in
       +echo "grid btn2 1 1" > ltk_in
   DIR diff --git a/theme.ini b/theme.ini
       t@@ -0,0 +1,18 @@
       +[window]
       +border_width = 0
       +bg = #000000
       +fg = #FFFFFF
       +font = Awami Nastaliq
       +
       +[button]
       +border_width = 2
       +text_color = #FFFFFF
       +pad = 5
       +border = #339999
       +fill = #113355
       +border_pressed = #FFFFFF
       +fill_pressed = #113355
       +border_active = #FFFFFF
       +fill_active = #738194
       +border_disabled = #FFFFFF
       +fill_disabled = #292929