URI: 
       ltk.c - ltkx - GUI toolkit for X11 (old)
  HTML git clone git://lumidify.org/ltkx.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltkx.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltkx.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       ltk.c (16253B)
       ---
            1 /*
            2  * This file is part of the Lumidify ToolKit (LTK)
            3  * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
            4  *
            5  * Permission is hereby granted, free of charge, to any person obtaining a copy
            6  * of this software and associated documentation files (the "Software"), to deal
            7  * in the Software without restriction, including without limitation the rights
            8  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            9  * copies of the Software, and to permit persons to whom the Software is
           10  * furnished to do so, subject to the following conditions:
           11  *
           12  * The above copyright notice and this permission notice shall be included in all
           13  * copies or substantial portions of the Software.
           14  *
           15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
           16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
           17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
           18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
           19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           21  * SOFTWARE.
           22  */
           23 
           24 #include <stdio.h>
           25 #include <stdlib.h>
           26 #include "ini.h"
           27 #include <X11/Xlib.h>
           28 #include <X11/Xutil.h>
           29 #include "khash.h"
           30 #include "stb_truetype.h"
           31 #include <fribidi.h>
           32 #include <harfbuzz/hb.h>
           33 #include <fontconfig/fontconfig.h>
           34 #include "array.h"
           35 #include "text_common.h"
           36 #include "ltk.h"
           37 #include "text_buffer.h"
           38 
           39 /* Window hash */
           40 KHASH_MAP_INIT_INT(winhash, LtkWindow*)
           41 
           42 static struct {
           43         LtkTheme *theme;
           44         LtkTextManager *tm;
           45         Display *display;
           46         int screen;
           47         Colormap colormap;
           48         khash_t(winhash) *window_hash;
           49         int window_num;
           50         Atom wm_delete_msg;
           51 } ltk_global;
           52 
           53 struct ltk_redraw_queue {
           54         LtkWindow *window;
           55         LtkRect rect;
           56 };
           57 
           58 LtkTextManager *
           59 ltk_get_text_manager(void) {
           60         return ltk_global.tm;
           61 }
           62 
           63 LtkTheme *
           64 ltk_get_theme(void) {
           65         return ltk_global.theme;
           66 }
           67 
           68 Display *
           69 ltk_get_display(void) {
           70         return ltk_global.display;
           71 }
           72 
           73 Colormap
           74 ltk_get_colormap(void) {
           75         return ltk_global.colormap;
           76 }
           77 
           78 static struct ltk_redraw_queue *redraw_queue = NULL;
           79 static int num_redraws = 0;
           80 static int redraw_queue_bufsize = 0;
           81 
           82 static LtkRect
           83 ltk_rect_union(LtkRect r1, LtkRect r2) {
           84         LtkRect u;
           85         u.x = r1.x < r2.x ? r1.x : r2.x;
           86         u.y = r1.y < r2.y ? r1.y : r2.y;
           87         int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w;
           88         int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h;
           89         u.w = x2 - u.x;
           90         u.h = y2 - u.y;
           91         return u;
           92 }
           93 
           94 void
           95 ltk_window_invalidate_rect(LtkWindow *window, LtkRect rect) {
           96         int index = -1;
           97         if (redraw_queue) {
           98                 for (int i = 0; i < num_redraws; i++) {
           99                         if (redraw_queue[i].window == window) {
          100                                 index = i;
          101                                 break;
          102                         }
          103                 }
          104         }
          105         if (index == -1) {
          106                 if (num_redraws == redraw_queue_bufsize) {
          107                         struct ltk_redraw_queue *tmp = realloc(
          108                             redraw_queue, sizeof(struct ltk_redraw_queue) * (redraw_queue_bufsize + 1));
          109                         if (!tmp) goto error;
          110                         redraw_queue = tmp;
          111                         redraw_queue_bufsize++;
          112                 }
          113                 index = num_redraws;
          114                 num_redraws++;
          115                 redraw_queue[index].window = window;
          116                 redraw_queue[index].rect = rect;
          117         } else {
          118                 redraw_queue[index].rect = ltk_rect_union(rect, redraw_queue[index].rect);
          119         }
          120 
          121         return;
          122 error:
          123         (void)fprintf(stderr, "Out of memory\n");
          124         exit(1);
          125 }
          126 
          127 void
          128 ltk_widget_set_floating_collision(LtkWidget *widget) {
          129         LtkWindow *window = widget->window;
          130         for (int i = 0; i < window->num_float_widgets; i++) {
          131                 if (window->float_widgets[i] == widget)
          132                         return;
          133         }
          134         if (window->num_float_widgets + 1 > window->max_float_widgets) {
          135                 LtkWidget **new = realloc(
          136                     window->float_widgets,
          137                     sizeof(LtkWidget *) * (window->max_float_widgets + 1));
          138                 if (!new) goto error;
          139                 window->float_widgets = new;
          140                 window->max_float_widgets++;
          141         }
          142         window->float_widgets[window->num_float_widgets] = widget;
          143         window->num_float_widgets++;
          144         return;
          145 error:
          146         (void)fprintf(stderr, "Out of memory\n");
          147         exit(1);
          148 }
          149 
          150 void
          151 ltk_widget_unset_floating_collision(LtkWidget *widget) {
          152         LtkWindow *window = widget->window;
          153         for (int i = 0; i < window->num_float_widgets; i++) {
          154                 if (window->float_widgets[i] == widget) {
          155                         for (int j = i + 1; j < window->num_float_widgets; j++) {
          156                                 window->float_widgets[j - 1] = window->float_widgets[j];
          157                         }
          158                         window->num_float_widgets--;
          159                         break;
          160                 }
          161         }
          162 }
          163 
          164 void
          165 ltk_init(const char *theme_path) {
          166         ltk_global.display = XOpenDisplay(NULL);
          167         ltk_global.screen = DefaultScreen(ltk_global.display);
          168         ltk_global.colormap = DefaultColormap(ltk_global.display, ltk_global.screen);
          169         ltk_global.theme = ltk_load_theme(theme_path);
          170         ltk_global.window_hash = kh_init(winhash);
          171         ltk_global.wm_delete_msg = XInternAtom(ltk_global.display, "WM_DELETE_WINDOW", False);
          172         ltk_global.tm = ltk_init_text(ltk_global.theme->window->font);
          173 }
          174 
          175 void
          176 ltk_clean_up(void) {
          177         LtkWindow *window;
          178         for (int k = kh_begin(ltk_global.window_hash); k != kh_end(ltk_global.window_hash); k++) {
          179                 if (kh_exist(ltk_global.window_hash, k)) {
          180                         ltk_destroy_window(kh_value(ltk_global.window_hash, k));
          181                 }
          182         }
          183         kh_destroy(winhash, ltk_global.window_hash);
          184         XCloseDisplay(ltk_global.display);
          185         ltk_destroy_theme(ltk_global.theme);
          186         ltk_destroy_text_manager(ltk_global.tm);
          187         if (redraw_queue) free(redraw_queue);
          188 }
          189 
          190 void
          191 ltk_quit(void) {
          192         ltk_clean_up();
          193         exit(0);
          194 }
          195 
          196 void
          197 ltk_fatal(const char *msg) {
          198         (void)fprintf(stderr, msg);
          199         ltk_clean_up();
          200         exit(1);
          201 };
          202 
          203 XColor
          204 ltk_create_xcolor(const char *hex) {
          205         XColor color;
          206         XParseColor(ltk_global.display, ltk_global.colormap, hex,
          207                     &color);
          208         XAllocColor(ltk_global.display, ltk_global.colormap, &color);
          209 
          210         return color;
          211 }
          212 
          213 void
          214 ltk_mainloop(void) {
          215         XEvent event;
          216         KeySym key;
          217         char text[255];
          218 
          219         /* FIXME: compress motion events */
          220         while (1) {
          221                 if (XPending(ltk_global.display) || !num_redraws) {
          222                         XNextEvent(ltk_global.display, &event);
          223                         ltk_handle_event(event);
          224                 } else if (num_redraws) {
          225                         for (int i = 0; i < num_redraws; i++)
          226                                 ltk_redraw_window(redraw_queue[i].window, redraw_queue[i].rect);
          227                         num_redraws = 0;
          228                 }
          229         }
          230 }
          231 
          232 void
          233 ltk_redraw_window(LtkWindow *window, LtkRect rect) {
          234         LtkWidget *ptr;
          235         if (!window) return;
          236         if (rect.x >= window->rect.w) return;
          237         if (rect.y >= window->rect.h) return;
          238         if (rect.x + rect.w > window->rect.w)
          239                 rect.w -= rect.x + rect.w - window->rect.w;
          240         if (rect.y + rect.h > window->rect.h)
          241                 rect.h -= rect.y + rect.h - window->rect.h;
          242         XClearArea(ltk_global.display, window->xwindow, rect.x, rect.y, rect.w, rect.h, False);
          243         if (!window->root_widget) return;
          244         ptr = window->root_widget;
          245         ptr->draw(ptr);
          246 }
          247 
          248 LtkWindow *
          249 ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) {
          250         LtkWindow *window = malloc(sizeof(LtkWindow));
          251         if (!window)
          252                 ltk_fatal("Not enough memory left for window!\n");
          253         LtkWindowTheme *wtheme = ltk_global.theme->window;        /* For convenience */
          254         Display *display = ltk_global.display;        /* For convenience */
          255         window->xwindow =
          256             XCreateSimpleWindow(display, DefaultRootWindow(display), x, y,
          257                                 w, h, wtheme->border_width,
          258                                 wtheme->fg.pixel, wtheme->bg.pixel);
          259         window->gc = XCreateGC(display, window->xwindow, 0, 0);
          260         XSetForeground(display, window->gc, wtheme->fg.pixel);
          261         XSetBackground(display, window->gc, wtheme->bg.pixel);
          262         XSetStandardProperties(display, window->xwindow, title, NULL, None,
          263                                NULL, 0, NULL);
          264         XSetWMProtocols(display, window->xwindow,
          265                         &ltk_global.wm_delete_msg, 1);
          266         window->root_widget = NULL;
          267 
          268         window->other_event = &ltk_window_other_event;
          269 
          270         window->rect.w = 0;
          271         window->rect.h = 0;
          272         window->rect.x = 0;
          273         window->rect.y = 0;
          274 
          275         window->num_float_widgets = 0;
          276         window->max_float_widgets = 0;
          277         window->float_widgets = NULL;
          278 
          279         XClearWindow(display, window->xwindow);
          280         XMapRaised(display, window->xwindow);
          281         XSelectInput(display, window->xwindow,
          282                      ExposureMask | KeyPressMask | KeyReleaseMask |
          283                      ButtonPressMask | ButtonReleaseMask |
          284                      StructureNotifyMask | PointerMotionMask);
          285 
          286         int ret;
          287         int k = kh_put(winhash, ltk_global.window_hash, window->xwindow, &ret);
          288         kh_value(ltk_global.window_hash, k) = window;
          289         ltk_global.window_num++;
          290 
          291         return window;
          292 }
          293 
          294 void
          295 ltk_remove_window(LtkWindow *window) {
          296         /* FIXME: also remove from event queue */
          297         ltk_destroy_window(window);
          298         if (ltk_global.window_num == 0)
          299                 ltk_quit();
          300 }
          301 
          302 void
          303 ltk_destroy_window(LtkWindow * window) {
          304         int k = kh_get(winhash, ltk_global.window_hash, window->xwindow);
          305         kh_del(winhash, ltk_global.window_hash, k);
          306         LtkWidget *ptr = window->root_widget;
          307         if (ptr)
          308                 ptr->destroy(ptr);
          309         XDestroyWindow(ltk_global.display, window->xwindow);
          310         free(window);
          311         ltk_global.window_num--;
          312 }
          313 
          314 void
          315 ltk_window_other_event(LtkWindow *window, XEvent event) {
          316         LtkWidget *ptr = window->root_widget;
          317         int retval = 0;
          318         if (event.type == ConfigureNotify) {
          319                 unsigned int w, h;
          320                 w = event.xconfigure.width;
          321                 h = event.xconfigure.height;
          322                 int orig_w = window->rect.w;
          323                 int orig_h = window->rect.h;
          324                 if (ptr && ptr->resize
          325                     && (orig_w != w || orig_h != h)) {
          326                         window->rect.w = w;
          327                         window->rect.h = h;
          328                         ptr->rect.w = w;
          329                         ptr->rect.h = h;
          330                         ptr->resize(ptr, orig_w, orig_h);
          331                 }
          332         } else if (event.type == Expose && event.xexpose.count == 0) {
          333                 LtkRect r;
          334                 r.x = event.xexpose.x;
          335                 r.y = event.xexpose.y;
          336                 r.w = event.xexpose.width;
          337                 r.h = event.xexpose.height;
          338                 ltk_window_invalidate_rect(window, r);
          339         } else if (event.type == ClientMessage
          340             && event.xclient.data.l[0] == ltk_global.wm_delete_msg) {
          341                 ltk_remove_window(window);
          342         }
          343 }
          344 
          345 void
          346 ltk_window_ini_handler(LtkTheme *theme, const char *prop, const char *value) {
          347         if (strcmp(prop, "border_width") == 0) {
          348                 theme->window->border_width = atoi(value);
          349         } else if (strcmp(prop, "bg") == 0) {
          350                 theme->window->bg = ltk_create_xcolor(value);
          351         } else if (strcmp(prop, "fg") == 0) {
          352                 theme->window->fg = ltk_create_xcolor(value);
          353         } else if (strcmp(prop, "font") == 0) {
          354                 theme->window->font = strdup(value);
          355         }
          356 }
          357 
          358 int
          359 ltk_ini_handler(void *theme, const char *widget, const char *prop, const char *value) {
          360         if (strcmp(widget, "window") == 0) {
          361                 ltk_window_ini_handler(theme, prop, value);
          362         } else if (strcmp(widget, "button") == 0) {
          363                 ltk_button_ini_handler(theme, prop, value);
          364         }
          365 }
          366 
          367 LtkTheme *
          368 ltk_load_theme(const char *path) {
          369         LtkTheme *theme = malloc(sizeof(LtkTheme));
          370         theme->window = malloc(sizeof(LtkWindowTheme));
          371         theme->button = NULL;
          372         if (ini_parse(path, ltk_ini_handler, theme) < 0) {
          373                 (void)fprintf(stderr, "ERROR: Can't load theme %s\n.", path);
          374                 exit(1);
          375         }
          376 
          377         return theme;
          378 }
          379 
          380 void
          381 ltk_destroy_theme(LtkTheme * theme) {
          382         free(theme->button);
          383         free(theme->window);
          384         free(theme);
          385 }
          386 
          387 char *
          388 ltk_read_file(const char *path, unsigned long *len) {
          389         FILE *f;
          390         char *file_contents;
          391         f = fopen(path, "rb");
          392         fseek(f, 0, SEEK_END);
          393         *len = ftell(f);
          394         fseek(f, 0, SEEK_SET);
          395         file_contents = malloc(*len + 1);
          396         fread(file_contents, 1, *len, f);
          397         file_contents[*len] = '\0';
          398         fclose(f);
          399 
          400         return file_contents;
          401 }
          402 
          403 int
          404 ltk_collide_rect(LtkRect rect, int x, int y) {
          405         return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y
          406                 && (rect.y + rect.h) >= y);
          407 }
          408 
          409 void
          410 ltk_remove_active_widget(void *widget) {
          411         if (!widget)
          412                 return;
          413         LtkWidget *parent = widget;
          414         LtkWidget *child;
          415         while (parent->active_widget) {
          416                 child = parent->active_widget;
          417                 child->state = LTK_NORMAL;
          418                 if (child->needs_redraw)
          419                         child->draw(child);
          420                 parent->active_widget = NULL;
          421                 parent = child;
          422         }
          423 }
          424 
          425 void
          426 ltk_change_active_widget_state(void *widget, LtkWidgetState state) {
          427         if (!widget)
          428                 return;
          429         LtkWidget *ptr = widget;
          430         while (ptr = ptr->active_widget) {
          431                 ptr->state = state;
          432                 ptr->draw(ptr);
          433         }
          434 }
          435 
          436 void
          437 ltk_remove_hover_widget(void *widget) {
          438         if (!widget)
          439                 return;
          440         LtkWidget *parent = widget;
          441         LtkWidget *child;
          442         while (parent->hover_widget) {
          443                 child = parent->hover_widget;
          444                 child->state =
          445                     child->state ==
          446                     LTK_HOVERACTIVE ? LTK_ACTIVE : LTK_NORMAL;
          447                 child->draw(child);
          448                 parent->hover_widget = NULL;
          449                 parent = child;
          450         }
          451 }
          452 
          453 void
          454 ltk_fill_widget_defaults(LtkWidget *widget, LtkWindow *window,
          455     void (*draw) (void *), void (*destroy) (void *), unsigned int needs_redraw) {
          456         widget->window = window;
          457         widget->active_widget = NULL;
          458         widget->hover_widget = NULL;
          459         widget->parent = NULL;
          460 
          461         widget->key_press = NULL;
          462         widget->key_release = NULL;
          463         widget->mouse_press = NULL;
          464         widget->mouse_release = NULL;
          465         widget->motion_notify = NULL;
          466 
          467         widget->resize = NULL;
          468         widget->draw = draw;
          469         widget->destroy = destroy;
          470 
          471         widget->needs_redraw = needs_redraw;
          472         widget->state = LTK_NORMAL;
          473         widget->row = 0;
          474         widget->rect.x = 0;
          475         widget->rect.y = 0;
          476         widget->rect.w = 100;
          477         widget->rect.h = 100;
          478         widget->collision_rect.x = 0;
          479         widget->collision_rect.y = 0;
          480         widget->collision_rect.w = 0;
          481         widget->collision_rect.h = 0;
          482 
          483         widget->row = NULL;
          484         widget->column = NULL;
          485         widget->row_span = NULL;
          486         widget->column_span = NULL;
          487         widget->sticky = 0;
          488 }
          489 
          490 void
          491 ltk_widget_mouse_press_event(void *widget, XEvent event) {
          492         LtkWidget *ptr = widget;
          493         if (!ptr || ptr->state == LTK_DISABLED)
          494                 return;
          495         if (event.xbutton.button == 1) {
          496                 LtkWidget *parent = ptr->parent;
          497                 if (parent) {
          498                         ltk_remove_active_widget(parent);
          499                         parent->active_widget = ptr;
          500                 }
          501                 ptr->state = LTK_PRESSED;
          502                 if (ptr->needs_redraw)
          503                         ptr->draw(ptr);
          504         }
          505         if (ptr->mouse_press) {
          506                 ptr->mouse_press(ptr, event);
          507         }
          508 }
          509 
          510 void
          511 ltk_window_mouse_press_event(LtkWindow *window, XEvent event) {
          512         if (window->float_widgets) {
          513                 int x = event.xbutton.x;
          514                 int y = event.xbutton.y;
          515                 for (int i = 0; i < window->num_float_widgets; i++) {
          516                         if (ltk_collide_rect(window->float_widgets[i]->collision_rect, x, y)) {
          517                                 ltk_widget_mouse_press_event(window->float_widgets[i], event);
          518                                 return;
          519                         }
          520                 }
          521         }
          522         if (window->root_widget) {
          523                 ltk_widget_mouse_press_event(window->root_widget, event);
          524         }
          525 }
          526 
          527 void
          528 ltk_widget_mouse_release_event(void *widget, XEvent event) {
          529         LtkWidget *ptr = widget;
          530         if (!ptr || ptr->state == LTK_DISABLED)
          531                 return;
          532         if (ptr->state == LTK_PRESSED) {
          533                 ptr->state = LTK_HOVERACTIVE;
          534                 if (ptr->needs_redraw)
          535                         ptr->draw(ptr);
          536         }
          537         if (ptr->mouse_release) {
          538                 ptr->mouse_release(ptr, event);
          539         }
          540 }
          541 
          542 void
          543 ltk_window_mouse_release_event(LtkWindow *window, XEvent event) {
          544         if (window->float_widgets) {
          545                 int x = event.xbutton.x;
          546                 int y = event.xbutton.y;
          547                 for (int i = 0; i < window->num_float_widgets; i++) {
          548                         if (ltk_collide_rect(window->float_widgets[i]->collision_rect, x, y)) {
          549                                 ltk_widget_mouse_release_event(window->float_widgets[i], event);
          550                                 return;
          551                         }
          552                 }
          553         }
          554         if (window->root_widget) {
          555                 ltk_widget_mouse_release_event(window->root_widget, event);
          556         }
          557 }
          558 
          559 void
          560 ltk_widget_motion_notify_event(void *widget, XEvent event) {
          561         LtkWidget *ptr = widget;
          562         LtkWidget *parent;
          563         if (!ptr) return;
          564         short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
          565         if ((ptr->state == LTK_NORMAL || ptr->state == LTK_ACTIVE)
          566             && !pressed) {
          567                 ptr->state =
          568                     ptr->state == LTK_ACTIVE ? LTK_HOVERACTIVE : LTK_HOVER;
          569                 parent = ptr->parent;
          570                 if (parent) {
          571                         ltk_remove_hover_widget(parent);
          572                         parent->hover_widget = ptr;
          573                 }
          574                 if (ptr->needs_redraw)
          575                         ptr->draw(ptr);
          576         }
          577         if (ptr->motion_notify)
          578                 ptr->motion_notify(ptr, event);
          579 }
          580 
          581 void
          582 ltk_window_motion_notify_event(LtkWindow *window, XEvent event) {
          583         if (window->float_widgets) {
          584                 int x = event.xbutton.x;
          585                 int y = event.xbutton.y;
          586                 for (int i = 0; i < window->num_float_widgets; i++) {
          587                         if (ltk_collide_rect(window->float_widgets[i]->collision_rect, x, y)) {
          588                                 ltk_widget_motion_notify_event(window->float_widgets[i], event);
          589                                 return;
          590                         }
          591                 }
          592         }
          593         if (window->root_widget) {
          594                 ltk_widget_motion_notify_event(window->root_widget, event);
          595         }
          596 }
          597 
          598 void
          599 ltk_handle_event(XEvent event) {
          600         LtkWidget *root_widget;
          601         int k = kh_get(winhash, ltk_global.window_hash, event.xany.window);
          602         LtkWindow *window = kh_value(ltk_global.window_hash, k);
          603         if (!window) return;
          604         switch (event.type) {
          605         case KeyPress:
          606                 break;
          607         case KeyRelease:
          608                 break;
          609         case ButtonPress:
          610                 ltk_window_mouse_press_event(window, event);
          611                 break;
          612         case ButtonRelease:
          613                 ltk_window_mouse_release_event(window, event);
          614                 break;
          615         case MotionNotify:
          616                 ltk_window_motion_notify_event(window, event);
          617                 break;
          618         default:
          619                 /* FIXME: users should be able to register other events like closing the window */
          620                 if (window->other_event)
          621                         window->other_event(window, event);
          622         }
          623 }