summaryrefslogtreecommitdiff
path: root/src/linux
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrik.ramaekers@protonmail.com>2020-01-30 21:11:12 +0100
committerAldrik Ramaekers <aldrik.ramaekers@protonmail.com>2020-01-30 21:11:12 +0100
commit260f05025631031b7cc4904805d5017feaf53eda (patch)
treed5a723bb7bbbc9f8b598712723fe3d8290c0a54c /src/linux
initial commit
Diffstat (limited to 'src/linux')
-rw-r--r--src/linux/platform.c1592
-rw-r--r--src/linux/thread.c129
2 files changed, 1721 insertions, 0 deletions
diff --git a/src/linux/platform.c b/src/linux/platform.c
new file mode 100644
index 0000000..e650097
--- /dev/null
+++ b/src/linux/platform.c
@@ -0,0 +1,1592 @@
+/*
+* BSD 2-Clause “Simplified” License
+* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com
+* All rights reserved.
+*/
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/Xatom.h>
+#include <time.h>
+#include <X11/XKBlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <dirent.h>
+#include <errno.h>
+#include <dlfcn.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <X11/cursorfont.h>
+
+#define GET_ATOM(X) window.X = XInternAtom(window.display, #X, False)
+
+struct t_platform_window
+{
+ Display *display;
+ Window parent;
+ XVisualInfo *visual_info;
+ Colormap cmap;
+ Window window;
+ GLXContext gl_context;
+ XWindowAttributes window_attributes;
+ XEvent event;
+ char *clipboard_str;
+ s32 clipboard_strlen;
+
+ Atom xdnd_req;
+ Atom xdnd_source;
+ Atom XdndEnter;
+ Atom XdndPosition;
+ Atom XdndStatus;
+ Atom XdndTypeList;
+ Atom XdndActionCopy;
+ Atom XdndDrop;
+ Atom XdndFinished;
+ Atom XdndSelection;
+ Atom XdndLeave;
+ Atom quit;
+ Atom PRIMARY;
+ Atom CLIPBOARD;
+ Atom UTF8_STRING;
+ Atom COMPOUND_STRING;
+ Atom TARGETS;
+ Atom MULTIPLE;
+ Atom _NET_WM_STATE;
+
+ // shared window properties
+ s32 width;
+ s32 height;
+ bool is_open;
+ bool has_focus;
+ cursor_type curr_cursor_type;
+ cursor_type next_cursor_type;
+};
+
+bool platform_get_clipboard(platform_window *window, char *buffer)
+{
+ char *result;
+ unsigned long ressize, restail;
+ int resbits;
+ Atom bufid = XInternAtom(window->display, "CLIPBOARD", False),
+ fmtid = XInternAtom(window->display, "UTF8_STRING", False),
+ propid = XInternAtom(window->display, "XSEL_DATA", False),
+ incrid = XInternAtom(window->display, "INCR", False);
+ XEvent event;
+
+ if(window->CLIPBOARD != None) {
+
+ if (settings_window && XGetSelectionOwner(window->display, window->CLIPBOARD) == settings_window->window)
+ {
+ snprintf(buffer, MAX_INPUT_LENGTH, "%s", settings_window->clipboard_str);
+ return true;
+ }
+ else if (XGetSelectionOwner(window->display, window->CLIPBOARD) ==
+ main_window->window)
+ {
+ snprintf(buffer, MAX_INPUT_LENGTH, "%s", main_window->clipboard_str);
+ return true;
+ }
+ }
+
+ XConvertSelection(window->display, bufid, fmtid, propid, window->window, CurrentTime);
+ do {
+ XNextEvent(window->display, &event);
+ } while (event.type != SelectionNotify || event.xselection.selection != bufid);
+
+ if (event.xselection.property)
+ {
+ XGetWindowProperty(window->display, window->window, propid, 0, LONG_MAX/4, False, AnyPropertyType,
+ &fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);
+
+ if (fmtid == incrid)
+ printf("Buffer is too large and INCR reading is not implemented yet.\n");
+ else
+ snprintf(buffer, MAX_INPUT_LENGTH, "%s", result);
+
+ XFree(result);
+ return True;
+ }
+ else // request failed, e.g. owner can't convert to the target format
+ return False;
+}
+
+bool platform_set_clipboard(platform_window *window, char *buffer)
+{
+ if (buffer)
+ {
+ if(window->CLIPBOARD != None && XGetSelectionOwner(window->display, window->CLIPBOARD) != window->window) {
+ XSetSelectionOwner(window->display, window->CLIPBOARD, window->window, CurrentTime);
+ }
+
+ window->clipboard_strlen = strlen(buffer);
+ if(!window->clipboard_str) {
+ window->clipboard_str = mem_alloc(window->clipboard_strlen+1);
+ } else {
+ window->clipboard_str = mem_realloc(window->clipboard_str, window->clipboard_strlen+1);
+ }
+ string_copyn(window->clipboard_str, buffer, window->clipboard_strlen+1);
+
+ return true;
+ }
+
+ return false;
+}
+
+void platform_create_config_directory()
+{
+ char *env = getenv("HOME");
+ char tmp[PATH_MAX];
+ snprintf(tmp, PATH_MAX, "%s%s", env, "/.config/text-search");
+
+ if (!platform_directory_exists(tmp))
+ {
+ mkdir(tmp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ }
+}
+
+char* get_config_save_location(char *buffer)
+{
+ char *env = getenv("HOME");
+ snprintf(buffer, PATH_MAX, "%s%s", env, "/.config/text-search/config.txt");
+ return buffer;
+}
+
+inline void platform_set_cursor(platform_window *window, cursor_type type)
+{
+ if (window->next_cursor_type != type)
+ {
+ window->next_cursor_type = type;
+ }
+}
+
+bool is_platform_in_darkmode()
+{
+ return false;
+}
+
+bool get_active_directory(char *buffer)
+{
+ char cwd[PATH_MAX];
+ if (getcwd(cwd, sizeof(cwd)) != NULL) {
+ string_copyn(buffer, cwd, PATH_MAX);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool set_active_directory(char *path)
+{
+ return !chdir(path);
+}
+
+bool platform_write_file_content(char *path, const char *mode, char *buffer, s32 len)
+{
+ bool result = false;
+
+ FILE *file = fopen(path, mode);
+
+ if (!file)
+ {
+ goto done_failure;
+ }
+ else
+ {
+ fprintf(file, "%s", buffer);
+ }
+
+ //done:
+ fclose(file);
+ done_failure:
+ return result;
+}
+
+void platform_window_set_title(platform_window *window, char *name)
+{
+ Atom WM_NAME = XInternAtom(window->display, "WM_NAME", False);
+ Atom _NET_WM_NAME = XInternAtom(window->display, "_NET_WM_NAME", False);
+ Atom _NET_WM_ICON_NAME = XInternAtom(window->display, "_NET_WM_ICON_NAME", False);
+
+ char *list[1] = { (char *) name };
+ XTextProperty property;
+
+ XStoreName(window->display, window->window, name);
+
+ Xutf8TextListToTextProperty(window->display, list, 1, XUTF8StringStyle,
+ &property);
+ XSetTextProperty(window->display, window->window, &property, WM_NAME);
+ XSetTextProperty(window->display, window->window, &property, _NET_WM_NAME);
+ XSetTextProperty(window->display, window->window, &property, XA_WM_NAME);
+ XSetTextProperty(window->display, window->window, &property, _NET_WM_ICON_NAME);
+ XFree(property.value);
+}
+
+bool platform_file_exists(char *path)
+{
+ if(access(path, F_OK) != -1) {
+ return 1;
+ }
+
+ return 0;
+}
+
+bool platform_directory_exists(char *path)
+{
+ DIR* dir = opendir(path);
+ if (dir) {
+ /* Directory exists. */
+ closedir(dir);
+ return 1;
+ } else if (ENOENT == errno) {
+ return 0; // does not exist
+ } else {
+ return 0; // error opening dir
+ }
+}
+
+file_content platform_read_file_content(char *path, const char *mode)
+{
+ file_content result;
+ result.content = 0;
+ result.content_length = 0;
+ result.file_error = 0;
+
+ FILE *file = fopen(path, mode);
+
+ if (!file)
+ {
+ if (errno == EMFILE)
+ result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_PROCESS;
+ else if (errno == ENFILE)
+ result.file_error = FILE_ERROR_TOO_MANY_OPEN_FILES_SYSTEM;
+ else if (errno == EACCES)
+ result.file_error = FILE_ERROR_NO_ACCESS;
+ else if (errno == EPERM)
+ result.file_error = FILE_ERROR_NO_ACCESS;
+ else if (errno == ENOENT)
+ result.file_error = FILE_ERROR_NOT_FOUND;
+ else if (errno == ECONNABORTED)
+ result.file_error = FILE_ERROR_CONNECTION_ABORTED;
+ else if (errno == ECONNREFUSED)
+ result.file_error = FILE_ERROR_CONNECTION_REFUSED;
+ else if (errno == ENETDOWN)
+ result.file_error = FILE_ERROR_NETWORK_DOWN;
+ else if (errno == EREMOTEIO)
+ result.file_error = FILE_ERROR_REMOTE_IO_ERROR;
+ else if (errno == ESTALE)
+ result.file_error = FILE_ERROR_STALE;
+ else
+ {
+ result.file_error = FILE_ERROR_GENERIC;
+ printf("ERROR: %d\n", errno);
+ }
+
+ goto done_failure;
+ }
+
+ fseek(file, 0 , SEEK_END);
+ int length = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ s32 length_to_alloc = length+1;
+
+ result.content = mem_alloc(length_to_alloc);
+ if (!result.content) goto done;
+
+ memset(result.content, 0, length);
+ s32 read_result = fread(result.content, 1, length, file);
+ if (read_result == 0 && length != 0)
+ {
+ mem_free(result.content);
+ result.content = 0;
+ return result;
+ }
+
+ result.content_length = read_result;
+
+ ((char*)result.content)[length] = 0;
+
+ done:
+ fclose(file);
+ done_failure:
+ return result;
+}
+
+inline void platform_destroy_file_content(file_content *content)
+{
+ assert(content);
+ mem_free(content->content);
+}
+
+// Translate an X11 key code to a GLFW key code.
+//
+static s32 translate_keycode(platform_window *window, s32 scancode)
+{
+ s32 keySym;
+
+ // Valid key code range is [8,255], according to the Xlib manual
+
+ if (1)
+ {
+ // Try secondary keysym, for numeric keypad keys
+ // Note: This way we always force "NumLock = ON", which is intentional
+ // since the returned key code should correspond to a physical
+ // location.
+ keySym = XkbKeycodeToKeysym(window->display, scancode, 0, 1);
+ switch (keySym)
+ {
+ case XK_KP_0: return KEY_KP_0;
+ case XK_KP_1: return KEY_KP_1;
+ case XK_KP_2: return KEY_KP_2;
+ case XK_KP_3: return KEY_KP_3;
+ case XK_KP_4: return KEY_KP_4;
+ case XK_KP_5: return KEY_KP_5;
+ case XK_KP_6: return KEY_KP_6;
+ case XK_KP_7: return KEY_KP_7;
+ case XK_KP_8: return KEY_KP_8;
+ case XK_KP_9: return KEY_KP_9;
+ case XK_KP_Separator:
+ case XK_KP_Decimal: return KEY_KP_DECIMAL;
+ case XK_KP_Equal: return KEY_KP_EQUAL;
+ case XK_KP_Enter: return KEY_KP_ENTER;
+ default: break;
+ }
+
+ // Now try primary keysym for function keys (non-printable keys)
+ // These should not depend on the current keyboard layout
+ keySym = XkbKeycodeToKeysym(window->display, scancode, 0, 0);
+ }
+
+ switch (keySym)
+ {
+ case XK_Escape: return KEY_ESCAPE;
+ case XK_Tab: return KEY_TAB;
+ case XK_Shift_L: return KEY_LEFT_SHIFT;
+ case XK_Shift_R: return KEY_RIGHT_SHIFT;
+ case XK_Control_L: return KEY_LEFT_CONTROL;
+ case XK_Control_R: return KEY_RIGHT_CONTROL;
+ case XK_Meta_L:
+ case XK_Alt_L: return KEY_LEFT_ALT;
+ case XK_Mode_switch: // Mapped to Alt_R on many keyboards
+ case XK_ISO_Level3_Shift: // AltGr on at least some machines
+ case XK_Meta_R:
+ case XK_Alt_R: return KEY_RIGHT_ALT;
+ case XK_Super_L: return KEY_LEFT_SUPER;
+ case XK_Super_R: return KEY_RIGHT_SUPER;
+ case XK_Menu: return KEY_MENU;
+ case XK_Num_Lock: return KEY_NUM_LOCK;
+ case XK_Caps_Lock: return KEY_CAPS_LOCK;
+ case XK_Print: return KEY_PRINT_SCREEN;
+ case XK_Scroll_Lock: return KEY_SCROLL_LOCK;
+ case XK_Pause: return KEY_PAUSE;
+ case XK_Delete: return KEY_DELETE;
+ case XK_BackSpace: return KEY_BACKSPACE;
+ case XK_Return: return KEY_ENTER;
+ case XK_Home: return KEY_HOME;
+ case XK_End: return KEY_END;
+ case XK_Page_Up: return KEY_PAGE_UP;
+ case XK_Page_Down: return KEY_PAGE_DOWN;
+ case XK_Insert: return KEY_INSERT;
+ case XK_Left: return KEY_LEFT;
+ case XK_Right: return KEY_RIGHT;
+ case XK_Down: return KEY_DOWN;
+ case XK_Up: return KEY_UP;
+ case XK_F1: return KEY_F1;
+ case XK_F2: return KEY_F2;
+ case XK_F3: return KEY_F3;
+ case XK_F4: return KEY_F4;
+ case XK_F5: return KEY_F5;
+ case XK_F6: return KEY_F6;
+ case XK_F7: return KEY_F7;
+ case XK_F8: return KEY_F8;
+ case XK_F9: return KEY_F9;
+ case XK_F10: return KEY_F10;
+ case XK_F11: return KEY_F11;
+ case XK_F12: return KEY_F12;
+ case XK_F13: return KEY_F13;
+ case XK_F14: return KEY_F14;
+ case XK_F15: return KEY_F15;
+ case XK_F16: return KEY_F16;
+ case XK_F17: return KEY_F17;
+ case XK_F18: return KEY_F18;
+ case XK_F19: return KEY_F19;
+ case XK_F20: return KEY_F20;
+ case XK_F21: return KEY_F21;
+ case XK_F22: return KEY_F22;
+ case XK_F23: return KEY_F23;
+ case XK_F24: return KEY_F24;
+ case XK_F25: return KEY_F25;
+
+ // Numeric keypad
+ case XK_KP_Divide: return KEY_KP_DIVIDE;
+ case XK_KP_Multiply: return KEY_KP_MULTIPLY;
+ case XK_KP_Subtract: return KEY_KP_SUBTRACT;
+ case XK_KP_Add: return KEY_KP_ADD;
+
+ // These should have been detected in secondary keysym test above!
+ case XK_KP_Insert: return KEY_KP_0;
+ case XK_KP_End: return KEY_KP_1;
+ case XK_KP_Down: return KEY_KP_2;
+ case XK_KP_Page_Down: return KEY_KP_3;
+ case XK_KP_Left: return KEY_KP_4;
+ case XK_KP_Right: return KEY_KP_6;
+ case XK_KP_Home: return KEY_KP_7;
+ case XK_KP_Up: return KEY_KP_8;
+ case XK_KP_Page_Up: return KEY_KP_9;
+ case XK_KP_Delete: return KEY_KP_DECIMAL;
+ case XK_KP_Equal: return KEY_KP_EQUAL;
+ case XK_KP_Enter: return KEY_KP_ENTER;
+
+ // Last resort: Check for printable keys (should not happen if the XKB
+ // extension is available). This will give a layout dependent mapping
+ // (which is wrong, and we may miss some keys, especially on non-US
+ // keyboards), but it's better than nothing...
+ case XK_a: return KEY_A;
+ case XK_b: return KEY_B;
+ case XK_c: return KEY_C;
+ case XK_d: return KEY_D;
+ case XK_e: return KEY_E;
+ case XK_f: return KEY_F;
+ case XK_g: return KEY_G;
+ case XK_h: return KEY_H;
+ case XK_i: return KEY_I;
+ case XK_j: return KEY_J;
+ case XK_k: return KEY_K;
+ case XK_l: return KEY_L;
+ case XK_m: return KEY_M;
+ case XK_n: return KEY_N;
+ case XK_o: return KEY_O;
+ case XK_p: return KEY_P;
+ case XK_q: return KEY_Q;
+ case XK_r: return KEY_R;
+ case XK_s: return KEY_S;
+ case XK_t: return KEY_T;
+ case XK_u: return KEY_U;
+ case XK_v: return KEY_V;
+ case XK_w: return KEY_W;
+ case XK_x: return KEY_X;
+ case XK_y: return KEY_Y;
+ case XK_z: return KEY_Z;
+ case XK_1: return KEY_1;
+ case XK_2: return KEY_2;
+ case XK_3: return KEY_3;
+ case XK_4: return KEY_4;
+ case XK_5: return KEY_5;
+ case XK_6: return KEY_6;
+ case XK_7: return KEY_7;
+ case XK_8: return KEY_8;
+ case XK_9: return KEY_9;
+ case XK_0: return KEY_0;
+ case XK_space: return KEY_SPACE;
+ case XK_minus: return KEY_MINUS;
+ case XK_equal: return KEY_EQUAL;
+ case XK_bracketleft: return KEY_LEFT_BRACKET;
+ case XK_bracketright: return KEY_RIGHT_BRACKET;
+ case XK_backslash: return KEY_BACKSLASH;
+ case XK_semicolon: return KEY_SEMICOLON;
+ case XK_apostrophe: return KEY_APOSTROPHE;
+ case XK_grave: return KEY_GRAVE_ACCENT;
+ case XK_comma: return KEY_COMMA;
+ case XK_period: return KEY_PERIOD;
+ case XK_slash: return KEY_SLASH;
+ case XK_less: return KEY_WORLD_1; // At least in some layouts...
+ default: break;
+ }
+
+ // No matching translation was found
+ return KEY_UNKNOWN;
+}
+
+static void create_key_tables(platform_window window)
+{
+ s32 scancode, key;
+ char name[XkbKeyNameLength + 1];
+ XkbDescPtr desc = XkbGetMap(window.display, 0, XkbUseCoreKbd);
+ XkbGetNames(window.display, XkbKeyNamesMask, desc);
+
+ // uncomment for layout independant input for games.
+#if 0
+ for (scancode = desc->min_key_code; scancode <= desc->max_key_code; scancode++)
+ {
+ memcpy(name, desc->names->keys[scancode].name, XkbKeyNameLength);
+ name[XkbKeyNameLength] = '\0';
+
+ // Map the key name to a GLFW key code. Note: We only map printable
+ // keys here, and we use the US keyboard layout. The rest of the
+ // keys (function keys) are mapped using traditional KeySym
+ // translations.
+ if (strcmp(name, "TLDE") == 0) key = KEY_GRAVE_ACCENT;
+ else if (strcmp(name, "AE01") == 0) key = KEY_1;
+ else if (strcmp(name, "AE02") == 0) key = KEY_2;
+ else if (strcmp(name, "AE03") == 0) key = KEY_3;
+ else if (strcmp(name, "AE04") == 0) key = KEY_4;
+ else if (strcmp(name, "AE05") == 0) key = KEY_5;
+ else if (strcmp(name, "AE06") == 0) key = KEY_6;
+ else if (strcmp(name, "AE07") == 0) key = KEY_7;
+ else if (strcmp(name, "AE08") == 0) key = KEY_8;
+ else if (strcmp(name, "AE09") == 0) key = KEY_9;
+ else if (strcmp(name, "AE10") == 0) key = KEY_0;
+ else if (strcmp(name, "AE11") == 0) key = KEY_MINUS;
+ else if (strcmp(name, "AE12") == 0) key = KEY_EQUAL;
+ else if (strcmp(name, "AD01") == 0) key = KEY_Q;
+ else if (strcmp(name, "AD02") == 0) key = KEY_W;
+ else if (strcmp(name, "AD03") == 0) key = KEY_E;
+ else if (strcmp(name, "AD04") == 0) key = KEY_R;
+ else if (strcmp(name, "AD05") == 0) key = KEY_T;
+ else if (strcmp(name, "AD06") == 0) key = KEY_Y;
+ else if (strcmp(name, "AD07") == 0) key = KEY_U;
+ else if (strcmp(name, "AD08") == 0) key = KEY_I;
+ else if (strcmp(name, "AD09") == 0) key = KEY_O;
+ else if (strcmp(name, "AD10") == 0) key = KEY_P;
+ else if (strcmp(name, "AD11") == 0) key = KEY_LEFT_BRACKET;
+ else if (strcmp(name, "AD12") == 0) key = KEY_RIGHT_BRACKET;
+ else if (strcmp(name, "AC01") == 0) key = KEY_A;
+ else if (strcmp(name, "AC02") == 0) key = KEY_S;
+ else if (strcmp(name, "AC03") == 0) key = KEY_D;
+ else if (strcmp(name, "AC04") == 0) key = KEY_F;
+ else if (strcmp(name, "AC05") == 0) key = KEY_G;
+ else if (strcmp(name, "AC06") == 0) key = KEY_H;
+ else if (strcmp(name, "AC07") == 0) key = KEY_J;
+ else if (strcmp(name, "AC08") == 0) key = KEY_K;
+ else if (strcmp(name, "AC09") == 0) key = KEY_L;
+ else if (strcmp(name, "AC10") == 0) key = KEY_SEMICOLON;
+ else if (strcmp(name, "AC11") == 0) key = KEY_APOSTROPHE;
+ else if (strcmp(name, "AB01") == 0) key = KEY_Z;
+ else if (strcmp(name, "AB02") == 0) key = KEY_X;
+ else if (strcmp(name, "AB03") == 0) key = KEY_C;
+ else if (strcmp(name, "AB04") == 0) key = KEY_V;
+ else if (strcmp(name, "AB05") == 0) key = KEY_B;
+ else if (strcmp(name, "AB06") == 0) key = KEY_N;
+ else if (strcmp(name, "AB07") == 0) key = KEY_M;
+ else if (strcmp(name, "AB08") == 0) key = KEY_COMMA;
+ else if (strcmp(name, "AB09") == 0) key = KEY_PERIOD;
+ else if (strcmp(name, "AB10") == 0) key = KEY_SLASH;
+ else if (strcmp(name, "BKSL") == 0) key = KEY_BACKSLASH;
+ else if (strcmp(name, "LSGT") == 0) key = KEY_WORLD_1;
+ else key = KEY_UNKNOWN;
+
+ if ((scancode >= 0) && (scancode < 256))
+ keycode_map[scancode] = key;
+ }
+#endif
+
+ for (scancode = 0; scancode < MAX_KEYCODE; scancode++)
+ {
+ // Translate the un-translated key codes using traditional X11 KeySym
+ // lookups
+
+ keycode_map[scancode] = translate_keycode(&window, scancode);
+ }
+
+ XkbFreeNames(desc, XkbKeyNamesMask, True);
+ XkbFreeKeyboard(desc, 0, True);
+}
+
+inline void platform_init(int argc, char **argv)
+{
+#if 0
+ dlerror(); // clear error
+ void *x11 = dlopen("libX11.so.6", RTLD_NOW | RTLD_GLOBAL);
+ void *randr = dlopen("libXrandr.so", RTLD_NOW | RTLD_GLOBAL);
+#endif
+
+ setlocale(LC_ALL, "en_US.UTF-8");
+
+ XInitThreads();
+
+ // get fullpath of the directory the binary is residing in
+ binary_path = platform_get_full_path(argv[0]);
+
+ platform_create_config_directory();
+
+ char buf[MAX_INPUT_LENGTH];
+ get_directory_from_path(buf, binary_path);
+ string_copyn(binary_path, buf, MAX_INPUT_LENGTH);
+
+ assets_create();
+}
+
+inline void platform_destroy()
+{
+ assets_destroy();
+
+#if defined(MODE_DEVELOPER)
+ memory_print_leaks();
+#endif
+}
+
+inline void platform_window_make_current(platform_window *window)
+{
+ glXMakeCurrent(window->display, window->window, window->gl_context);
+}
+
+void platform_window_set_size(platform_window *window, u16 width, u16 height)
+{
+ XResizeWindow(window->display, window->window, width, height);
+}
+
+void platform_window_set_position(platform_window *window, u16 x, u16 y)
+{
+ XMoveWindow(window->display, window->window, x, y);
+}
+
+
+vec2 platform_get_window_size(platform_window *window)
+{
+ vec2 res;
+ res.x = window->width;
+ res.y = window->height;
+ return res;
+}
+
+platform_window platform_open_window(char *name, u16 width, u16 height, u16 max_w, u16 max_h, u16 min_w, u16 min_h)
+{
+ bool has_max_size = max_w || max_h;
+
+ platform_window window;
+ window.has_focus = true;
+ window.curr_cursor_type = CURSOR_DEFAULT;
+ window.next_cursor_type = CURSOR_DEFAULT;
+ window.clipboard_str = 0;
+ window.clipboard_strlen = 0;
+
+ static int att[] =
+ {
+ GLX_X_RENDERABLE , True,
+ GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT,
+ GLX_RENDER_TYPE , GLX_RGBA_BIT,
+ GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR,
+ GLX_RED_SIZE , 8,
+ GLX_GREEN_SIZE , 8,
+ GLX_BLUE_SIZE , 8,
+ GLX_ALPHA_SIZE , 8,
+ GLX_DEPTH_SIZE , 24,
+ GLX_STENCIL_SIZE , 8,
+ GLX_DOUBLEBUFFER , True,
+ //GLX_SAMPLE_BUFFERS , 1,
+ //GLX_SAMPLES , 4,
+ None
+ };
+
+ window.display = XOpenDisplay(NULL);
+
+ if(window.display == NULL) {
+ return window;
+ }
+
+ window.parent = DefaultRootWindow(window.display);
+
+ int fbcount;
+ GLXFBConfig* fbc = glXChooseFBConfig(window.display, DefaultScreen(window.display), att, &fbcount);
+ int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999;
+
+ int i;
+ for (i=0; i<fbcount; ++i)
+ {
+ XVisualInfo *vi = glXGetVisualFromFBConfig(window.display, fbc[i] );
+ if ( vi )
+ {
+ int samp_buf, samples;
+ glXGetFBConfigAttrib(window.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf );
+ glXGetFBConfigAttrib(window.display, fbc[i], GLX_SAMPLES , &samples );
+
+ if ( best_fbc < 0 || (samp_buf && samples > best_num_samp))
+ best_fbc = i, best_num_samp = samples;
+ if ( worst_fbc < 0 || !samp_buf || samples < worst_num_samp )
+ worst_fbc = i, worst_num_samp = samples;
+ }
+ XFree(vi);
+ }
+
+ GLXFBConfig bestFbc = fbc[best_fbc];
+ XFree(fbc);
+
+ XVisualInfo *vi = glXGetVisualFromFBConfig(window.display, bestFbc );
+ window.visual_info = vi;
+
+ if(window.visual_info == NULL) {
+ return window;
+ }
+
+ window.cmap = XCreateColormap(window.display, window.parent, window.visual_info->visual, AllocNone);
+
+ // calculate window center
+ XRRScreenResources *screens = XRRGetScreenResources(window.display, window.parent);
+ XRRCrtcInfo *info = XRRGetCrtcInfo(window.display, screens, screens->crtcs[0]);
+
+ s32 center_x = (info->width / 2) - (width / 2);
+ s32 center_y = (info->height / 2) - (height / 2);
+
+ XRRFreeCrtcInfo(info);
+ XRRFreeScreenResources(screens);
+
+ XSetWindowAttributes window_attributes;
+ window_attributes.colormap = window.cmap;
+ window_attributes.border_pixel = 0;
+ window_attributes.event_mask = KeyPressMask | KeyReleaseMask | PointerMotionMask |
+ ButtonPressMask | ButtonReleaseMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask;
+
+ window.window = XCreateWindow(window.display, window.parent, center_x, center_y, width, height, 0, window.visual_info->depth, InputOutput, window.visual_info->visual, CWColormap | CWEventMask | CWBorderPixel, &window_attributes);
+
+ XMapWindow(window.display, window.window);
+ XFlush(window.display);
+
+ XSync(window.display, False);
+
+ XSizeHints hints;
+
+ if (has_max_size)
+ hints.flags = PMaxSize | PMinSize | USPosition;
+ else
+ hints.flags = PMinSize | USPosition;
+ hints.x = center_x;
+ hints.y = center_y;
+ hints.max_width = width;
+ hints.max_height = height;
+ hints.min_width = min_w;
+ hints.min_height = min_h;
+
+ XSetWMNormalHints(window.display, window.window, &hints);
+
+ // window name
+ {
+ Atom WM_NAME = XInternAtom(window.display, "WM_NAME", False);
+ Atom _NET_WM_NAME = XInternAtom(window.display, "_NET_WM_NAME", False);
+ Atom _NET_WM_ICON_NAME = XInternAtom(window.display, "_NET_WM_ICON_NAME", False);
+
+ char *list[1] = { (char *) name };
+ XTextProperty property;
+
+ XStoreName(window.display, window.window, name);
+
+ Xutf8TextListToTextProperty(window.display, list, 1, XUTF8StringStyle,
+ &property);
+ XSetTextProperty(window.display, window.window, &property, WM_NAME);
+ XSetTextProperty(window.display, window.window, &property, _NET_WM_NAME);
+ XSetTextProperty(window.display, window.window, &property, XA_WM_NAME);
+ XSetTextProperty(window.display, window.window, &property, _NET_WM_ICON_NAME);
+ XFree(property.value);
+
+ XClassHint class_hint;
+ class_hint.res_name = name;
+ class_hint.res_class = name;
+ XSetClassHint(window.display, window.window, &class_hint);
+ }
+
+
+ // hide taskbar icon and stay on top
+#if 0
+ {
+ Atom wm_state = XInternAtom(window.display,
+ "_NET_WM_STATE", False);
+ Atom taskbar_atom = XInternAtom(window.display,
+ "_NET_WM_STATE_SKIP_TASKBAR", False);
+ Atom above_atom = XInternAtom(window.display,
+ "_NET_WM_STATE_ABOVE", False);
+
+ Atom atom_list[2] = {taskbar_atom, above_atom};
+ XChangeProperty(window.display, window.window, wm_state, XA_ATOM, 32,
+ PropModeReplace, (const unsigned char*)&atom_list, 2);
+ }
+#endif
+
+ {
+ XWMHints* win_hints = XAllocWMHints();
+ win_hints->flags = StateHint | IconPositionHint;
+ win_hints->initial_state = IconicState;
+ win_hints->icon_x = 0;
+ win_hints->icon_y = 0;
+
+ /* pass the hints to the window manager. */
+ XSetWMHints(window.display, window.window, win_hints);
+ XFree(win_hints);
+ }
+
+ static GLXContext share_list = 0;
+
+ // get opengl context
+ window.gl_context = glXCreateContext(window.display, window.visual_info,
+ share_list, GL_TRUE);
+
+ if (share_list == 0)
+ share_list = window.gl_context;
+ glXMakeCurrent(window.display, window.window, window.gl_context);
+
+ // blending
+ glEnable(GL_DEPTH_TEST);
+ //glDepthMask(true);
+ //glClearDepth(50);
+ glDepthFunc(GL_LEQUAL);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ // setup multisampling
+#if 0
+ glEnable(GL_ALPHA_TEST);
+ glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+ glEnable(GL_SAMPLE_ALPHA_TO_ONE);
+ glEnable(GL_MULTISAMPLE);
+ glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST);
+#endif
+
+ window.is_open = true;
+ window.width = width;
+ window.height = height;
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, width, height, 0, -1, 1);
+
+ glMatrixMode(GL_MODELVIEW);
+
+ create_key_tables(window);
+
+ // recieve window close event
+ window.quit = XInternAtom(window.display, "WM_DELETE_WINDOW", False);
+
+ GET_ATOM(XdndEnter);
+ GET_ATOM(XdndPosition);
+ GET_ATOM(XdndStatus);
+ GET_ATOM(XdndTypeList);
+ GET_ATOM(XdndActionCopy);
+ GET_ATOM(XdndDrop);
+ GET_ATOM(XdndFinished);
+ GET_ATOM(XdndSelection);
+ GET_ATOM(XdndLeave);
+ GET_ATOM(PRIMARY);
+ GET_ATOM(CLIPBOARD);
+ GET_ATOM(UTF8_STRING);
+ GET_ATOM(COMPOUND_STRING);
+ GET_ATOM(TARGETS);
+ GET_ATOM(MULTIPLE);
+ GET_ATOM(_NET_WM_STATE);
+
+ array atoms = array_create(sizeof(Atom));
+ array_push(&atoms, &window.quit);
+ array_push(&atoms, &window.XdndEnter);
+ array_push(&atoms, &window.XdndPosition);
+ array_push(&atoms, &window.XdndStatus);
+ array_push(&atoms, &window.XdndTypeList);
+ array_push(&atoms, &window.XdndActionCopy);
+ array_push(&atoms, &window.XdndDrop);
+ array_push(&atoms, &window.XdndFinished);
+ array_push(&atoms, &window.XdndSelection);
+ array_push(&atoms, &window.XdndLeave);
+ array_push(&atoms, &window.PRIMARY);
+ array_push(&atoms, &window.CLIPBOARD);
+ array_push(&atoms, &window.UTF8_STRING);
+ array_push(&atoms, &window.COMPOUND_STRING);
+ array_push(&atoms, &window.TARGETS);
+ array_push(&atoms, &window.MULTIPLE);
+ array_push(&atoms, &window._NET_WM_STATE);
+
+ XSetWMProtocols(window.display, window.window, atoms.data, atoms.length);
+ array_destroy(&atoms);
+
+ Atom XdndAware = XInternAtom(window.display, "XdndAware", False);
+ Atom xdnd_version = 5;
+ XChangeProperty(window.display, window.window, XdndAware, XA_ATOM, 32,
+ PropModeReplace, (unsigned char*)&xdnd_version, 1);
+
+
+ XFlush(window.display);
+ XSync(window.display, True);
+
+ return window;
+}
+
+inline bool platform_window_is_valid(platform_window *window)
+{
+ return window->window && window->display;
+}
+
+void platform_destroy_window(platform_window *window)
+{
+ glXMakeCurrent(window->display, None, NULL);
+ glXDestroyContext(window->display, window->gl_context);
+ XDestroyWindow(window->display, window->window);
+ XCloseDisplay(window->display);
+ XFree(window->visual_info);
+ mem_free(window->clipboard_str);
+
+ window->window = 0;
+ window->display = 0;
+}
+
+void platform_hide_window_taskbar_icon(platform_window *window)
+{
+ XClientMessageEvent m;
+ memset(&m, 0, sizeof(XClientMessageEvent));
+ m.type = ClientMessage;
+ m.display = window->display;
+ m.window = window->window;
+ m.message_type = window->_NET_WM_STATE;
+ m.format=32;
+ m.data.l[0] = 1;
+ m.data.l[1] = XInternAtom(window->display, "_NET_WM_STATE_SKIP_TASKBAR", False);
+ m.data.l[2] = None;
+ m.data.l[3] = 1;
+ m.data.l[4] = 0;
+ XSendEvent(window->display, window->window, False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&m);
+
+ XFlush(window->display);
+}
+
+void platform_handle_events(platform_window *window, mouse_input *mouse, keyboard_input *keyboard)
+{
+ mouse->left_state &= ~MOUSE_CLICK;
+ mouse->right_state &= ~MOUSE_CLICK;
+ mouse->left_state &= ~MOUSE_DOUBLE_CLICK;
+ mouse->right_state &= ~MOUSE_DOUBLE_CLICK;
+ mouse->left_state &= ~MOUSE_RELEASE;
+ mouse->right_state &= ~MOUSE_RELEASE;
+ memset(keyboard->input_keys, 0, MAX_KEYCODE);
+ mouse->move_x = 0;
+ mouse->move_y = 0;
+ mouse->scroll_state = 0;
+ keyboard->text_changed = false;
+
+ XClientMessageEvent m;
+
+ s32 pending_events = XPending(window->display);
+ for (s32 i = 0; i < pending_events; i++)
+ {
+ XNextEvent(window->display, &window->event);
+ if (window->event.type == ClientMessage)
+ {
+ static int xdnd_version=0;
+
+ if ((Atom)window->event.xclient.data.l[0] == window->quit) {
+ window->is_open = false;
+ }
+
+ if (window->event.xclient.message_type == window->XdndDrop)
+ {
+ if (window->xdnd_req == None) {
+ /* say again - not interested! */
+ memset(&m, 0, sizeof(XClientMessageEvent));
+ m.type = ClientMessage;
+ m.display = window->event.xclient.display;
+ m.window = window->event.xclient.data.l[0];
+ m.message_type = window->XdndFinished;
+ m.format=32;
+ m.data.l[0] = window->window;
+ m.data.l[1] = 0;
+ m.data.l[2] = None; /* fail! */
+ XSendEvent(window->display, window->event.xclient.data.l[0], False, NoEventMask, (XEvent*)&m);
+ } else {
+ /* convert */
+ if(xdnd_version >= 1) {
+ XConvertSelection(window->display, window->XdndSelection, window->xdnd_req, window->PRIMARY, window->window, window->event.xclient.data.l[2]);
+ } else {
+ printf("time to find the time.\n");
+ //XConvertSelection(window->display, window->XdndSelection, window->xdnd_req, window->PRIMARY, window->xwindow, CurrentTime);
+ }
+ }
+ }
+ }
+ else if (window->event.type == LeaveNotify)
+ {
+ mouse->x = MOUSE_OFFSCREEN;
+ mouse->y = MOUSE_OFFSCREEN;
+ }
+ else if (window->event.type == ConfigureNotify)
+ {
+ XConfigureEvent xce = window->event.xconfigure;
+ window->width = xce.width;
+ window->height = xce.height;
+ glViewport(0, 0, window->width, window->height);
+ }
+ else if (window->event.type == FocusIn)
+ {
+ window->has_focus = true;
+ }
+ else if (window->event.type == FocusOut)
+ {
+ mouse->x = MOUSE_OFFSCREEN;
+ mouse->y = MOUSE_OFFSCREEN;
+ window->has_focus = false;
+
+ // if windows loses focus, set all keys to not pressed
+ memset(keyboard->keys, 0, MAX_KEYCODE);
+ }
+ else if (window->event.type == MotionNotify)
+ {
+ s32 x = mouse->x;
+ s32 y = mouse->y;
+
+ mouse->total_move_x += window->event.xmotion.x - mouse->x;
+ mouse->total_move_y += window->event.xmotion.y - mouse->y;
+
+ mouse->x = window->event.xmotion.x;
+ mouse->y = window->event.xmotion.y;
+
+ mouse->move_x = mouse->x - x;
+ mouse->move_y = mouse->y - y;
+ }
+ else if (window->event.type == ButtonPress)
+ {
+ Time ev_time = window->event.xbutton.time;
+ static Time last_ev_time = 0;
+
+ bool is_left_down = window->event.xbutton.button == Button1;
+ bool is_right_down = window->event.xbutton.button == Button3;
+ bool is_middle_down = window->event.xbutton.button == Button2;
+ bool scroll_up = window->event.xbutton.button == Button4;
+ bool scroll_down = window->event.xbutton.button == Button5;
+
+ if (scroll_up)
+ mouse->scroll_state = SCROLL_UP;
+ if (scroll_down)
+ mouse->scroll_state = SCROLL_DOWN;
+
+ if (is_left_down)
+ {
+ if (ev_time - last_ev_time < 200)
+ {
+ mouse->left_state |= MOUSE_DOUBLE_CLICK;
+ }
+
+ mouse->left_state |= MOUSE_DOWN;
+ mouse->left_state |= MOUSE_CLICK;
+
+ mouse->total_move_x = 0;
+ mouse->total_move_y = 0;
+ last_ev_time = ev_time;
+ }
+ if (is_right_down)
+ {
+ mouse->right_state |= MOUSE_DOWN;
+ mouse->right_state |= MOUSE_CLICK;
+ }
+ }
+ else if (window->event.type == ButtonRelease)
+ {
+ bool is_left_up = window->event.xbutton.button == Button1;
+ bool is_right_up = window->event.xbutton.button == Button3;
+ bool is_middle_up = window->event.xbutton.button == Button2;
+
+ if (is_left_up)
+ {
+ mouse->left_state = MOUSE_RELEASE;
+ }
+ if (is_right_up)
+ {
+ mouse->right_state = MOUSE_RELEASE;
+ }
+ }
+ else if(window->event.type == KeyPress)
+ {
+ s32 key = window->event.xkey.keycode;
+
+ keyboard->keys[keycode_map[key]] = true;
+ keyboard->input_keys[keycode_map[key]] = true;
+
+ // https://gist.github.com/rickyzhang82/8581a762c9f9fc6ddb8390872552c250
+ //printf("state: %d\n", window->event.xkey.state);
+
+ // remove key control key from mask so it doesnt block input
+ window->event.xkey.state &= ~ControlMask;
+
+ // replace capslock with shiftkey else keylookup returns 0...
+ if (window->event.xkey.state == 2)
+ window->event.xkey.state = 1;
+
+ KeySym ksym = XLookupKeysym(&window->event.xkey, window->event.xkey.state);
+
+ if (keyboard->take_input)
+ {
+ char *ch = 0;
+ switch(ksym)
+ {
+ case XK_space: ch = " "; break;
+ case XK_exclam: ch = "!"; break;
+ case XK_quotedbl: ch = "\""; break;
+ case XK_numbersign: ch = "#"; break;
+ case XK_dollar: ch = "$"; break;
+ case XK_percent: ch = "%"; break;
+ case XK_ampersand: ch = "&"; break;
+ case XK_apostrophe: ch = "`"; break;
+ case XK_parenleft: ch = "("; break;
+ case XK_parenright: ch = ")"; break;
+ case XK_asterisk: ch = "*"; break;
+ case XK_plus: ch = "+"; break;
+ case XK_comma: ch = ","; break;
+ case XK_minus: ch = "-"; break;
+ case XK_period: ch = "."; break;
+ case XK_slash: ch = "/"; break;
+ case XK_0: ch = "0"; break;
+ case XK_1: ch = "1"; break;
+ case XK_2: ch = "2"; break;
+ case XK_3: ch = "3"; break;
+ case XK_4: ch = "4"; break;
+ case XK_5: ch = "5"; break;
+ case XK_6: ch = "6"; break;
+ case XK_7: ch = "7"; break;
+ case XK_8: ch = "8"; break;
+ case XK_9: ch = "9"; break;
+
+ case XK_colon: ch = ":"; break;
+ case XK_semicolon: ch = ";"; break;
+ case XK_less: ch = "<"; break;
+ case XK_equal: ch = "="; break;
+ case XK_greater: ch = ">"; break;
+ case XK_question: ch = "?"; break;
+ case XK_at: ch = "@"; break;
+ case XK_bracketleft: ch = "["; break;
+ case XK_backslash: ch = "\\"; break;
+ case XK_bracketright: ch = "]"; break;
+ case XK_asciicircum: ch = "^"; break;
+ case XK_underscore: ch = "_"; break;
+ case XK_grave: ch = "`"; break;
+ case XK_braceleft: ch = "{"; break;
+ case XK_bar: ch = "|"; break;
+ case XK_braceright: ch = "}"; break;
+ case XK_asciitilde: ch = "~"; break;
+ }
+
+ if ((ksym >= XK_A && ksym <= XK_Z) || (ksym >= XK_a && ksym <= XK_z))
+ {
+ ch = XKeysymToString(ksym);
+ }
+
+ if (ch && keyboard->input_mode == INPUT_NUMERIC)
+ {
+ if (!(*ch >= 48 && *ch <= 57))
+ {
+ ch = 0;
+ }
+ }
+
+ keyboard_handle_input_string(window, keyboard, ch);
+ }
+ }
+ else if (window->event.type == KeyRelease)
+ {
+ s32 key = window->event.xkey.keycode;
+ keyboard->keys[keycode_map[key]] = false;
+
+ KeySym ksym = XLookupKeysym(&window->event.xkey, 0);
+ }
+ else if (window->event.type == SelectionClear)
+ {
+ window->clipboard_str = 0;
+ window->clipboard_strlen = 0;
+ }
+ else if (window->event.type == SelectionRequest)
+ {
+ Atom formats[] = {window->UTF8_STRING, window->COMPOUND_STRING, XA_STRING};
+ Atom targets[] = {window->TARGETS, window->MULTIPLE, window->UTF8_STRING, window->COMPOUND_STRING, XA_STRING};
+ int formatCount = sizeof(formats) / sizeof(formats[0]);
+
+ XSelectionEvent event = {.type = SelectionNotify, .selection = window->event.xselectionrequest.selection, .target = window->event.xselectionrequest.target, .display = window->event.xselectionrequest.display, .requestor = window->event.xselectionrequest.requestor, .time = window->event.xselectionrequest.time};
+
+ if(window->event.xselectionrequest.target == window->TARGETS) {
+ XChangeProperty(window->display, window->event.xselectionrequest.requestor, window->event.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, sizeof(targets) / sizeof(targets[0]));
+
+ event.property = window->event.xselectionrequest.property;
+ } else {
+ event.property = None;
+ int i;
+ for(i = 0; i < formatCount; i++) {
+ if(window->event.xselectionrequest.target == formats[i]) {
+ XChangeProperty(window->display, window->event.xselectionrequest.requestor, window->event.xselectionrequest.property, window->event.xselectionrequest.target, 8, PropModeReplace, (unsigned char*)window->clipboard_str, window->clipboard_strlen);
+
+ event.property = window->event.xselectionrequest.property;
+ break;
+ }
+ }
+ }
+
+ XSendEvent(window->display, window->event.xselectionrequest.requestor, False, 0, (XEvent*)&event);
+ XFlush(window->display);
+ }
+ }
+}
+
+inline void platform_show_alert(char *title, char *message)
+{
+ char command[MAX_INPUT_LENGTH];
+ snprintf(command, MAX_INPUT_LENGTH, "notify-send \"%s\" \"%s\"", title, message);
+ platform_run_command(command);
+}
+
+inline void platform_window_swap_buffers(platform_window *window)
+{
+ // set cursor if changed
+ if (window->curr_cursor_type != window->next_cursor_type)
+ {
+ int cursor_shape = 0;
+ switch(window->next_cursor_type)
+ {
+ case CURSOR_DEFAULT: cursor_shape = XC_arrow; break;
+ case CURSOR_POINTER: cursor_shape = XC_hand1; break;
+ }
+ Cursor cursor = XCreateFontCursor(window->display, cursor_shape);
+ XDefineCursor(window->display, window->window, cursor);
+ window->curr_cursor_type = window->next_cursor_type;
+ }
+
+ glXSwapBuffers(window->display, window->window);
+}
+
+u64 platform_get_time(time_type time_type, time_precision precision)
+{
+ s32 type = CLOCK_REALTIME;
+ switch(time_type)
+ {
+ case TIME_FULL: type = CLOCK_REALTIME; break;
+ case TIME_THREAD: type = CLOCK_THREAD_CPUTIME_ID; break;
+ case TIME_PROCESS: type = CLOCK_PROCESS_CPUTIME_ID; break;
+ }
+
+ struct timespec tms;
+ if (clock_gettime(type,&tms)) {
+ return -1;
+ }
+
+ long result = 0;
+
+ if (precision == TIME_NS)
+ {
+ result = tms.tv_sec * 1000000000;
+ result += tms.tv_nsec;
+ if (tms.tv_nsec % 1000 >= 500) {
+ ++result;
+ }
+ }
+ else if (precision == TIME_US)
+ {
+ result = tms.tv_sec * 1000000;
+ result += tms.tv_nsec/1000;
+ if (tms.tv_nsec % 1000 >= 500) {
+ ++result;
+ }
+ }
+ else if (precision == TIME_MILI_S)
+ {
+ result = tms.tv_sec * 1000;
+ result += tms.tv_nsec/1000000;
+ if (tms.tv_nsec % 1000 >= 500) {
+ ++result;
+ }
+ }
+ else if (precision == TIME_S)
+ {
+ result = tms.tv_sec;
+ result += tms.tv_nsec/1000000000;
+ if (tms.tv_nsec % 1000 >= 500) {
+ ++result;
+ }
+ }
+
+ return result;
+}
+
+inline s32 platform_get_cpu_count()
+{
+ return (int)sysconf(_SC_NPROCESSORS_ONLN);
+}
+
+inline s32 platform_get_memory_size()
+{
+ uint64_t aid = (uint64_t) sysconf(_SC_PHYS_PAGES);
+ aid *= (uint64_t) sysconf(_SC_PAGESIZE);
+ aid /= (uint64_t) (1024 * 1024);
+ return (int)(aid);
+}
+
+void platform_show_message(platform_window *window, char *message, char *title)
+{
+ char command[MAX_INPUT_LENGTH];
+ snprintf(command, MAX_INPUT_LENGTH, "zenity --info --text=\"%s\" --title=\"%s\" --width=240", message, title);
+ FILE *f = popen(command, "r");
+}
+
+static void* platform_open_file_dialog_thread(void *data)
+{
+ struct open_dialog_args *args = data;
+
+ FILE *f;
+
+ char current_val[MAX_INPUT_LENGTH];
+ string_copyn(current_val, args->buffer, MAX_INPUT_LENGTH);
+
+ char file_filter[MAX_INPUT_LENGTH];
+ file_filter[0] = 0;
+ if (args->file_filter)
+ snprintf(file_filter, MAX_INPUT_LENGTH, "--file-filter=\"%s\"", args->file_filter);
+
+ char start_path[MAX_INPUT_LENGTH];
+ start_path[0] = 0;
+ if (args->start_path)
+ snprintf(start_path, MAX_INPUT_LENGTH, "--filename=\"%s\"", args->start_path);
+
+
+ char command[MAX_INPUT_LENGTH];
+
+ if (args->type == OPEN_FILE)
+ {
+ snprintf(command, MAX_INPUT_LENGTH, "zenity --file-selection %s %s", file_filter, start_path);
+ }
+ else if (args->type == OPEN_DIRECTORY)
+ {
+ snprintf(command, MAX_INPUT_LENGTH, "zenity --file-selection --directory %s %s", file_filter, start_path);
+ }
+ else if (args->type == SAVE_FILE)
+ {
+ snprintf(command, MAX_INPUT_LENGTH, "zenity --file-selection --save --confirm-overwrite %s %s", file_filter, start_path);
+ }
+
+ f = popen(command, "r");
+
+ char buffer[MAX_INPUT_LENGTH];
+ char *result = fgets(buffer, MAX_INPUT_LENGTH, f);
+
+ if (!result)
+ return 0;
+
+ // replace newlines with 0, we only want one file path
+ s32 len = strlen(buffer);
+ for (s32 x = 0; x < len; x++)
+ {
+ if (buffer[x] == '\n') buffer[x] = 0;
+ }
+
+ if (strcmp(buffer, current_val) != 0 && strcmp(buffer, "") != 0)
+ {
+ string_copyn(args->buffer, buffer, MAX_INPUT_LENGTH);
+ s32 len = strlen(args->buffer);
+ args->buffer[len] = 0;
+ }
+
+ return 0;
+}
+
+void *platform_open_file_dialog_block(void *arg)
+{
+ thread thr = thread_start(platform_open_file_dialog_thread, arg);
+ thread_join(&thr);
+ mem_free(arg);
+ return 0;
+}
+
+void platform_list_files_block(array *list, char *start_dir, array filters, bool recursive, memory_bucket *bucket, bool include_directories, bool *is_cancelled)
+{
+ assert(list);
+
+ s32 len = 0;
+ char *matched_filter = 0;
+
+ char *subdirname_buf;
+ if (bucket)
+ subdirname_buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH);
+ else
+ subdirname_buf = mem_alloc(MAX_INPUT_LENGTH);
+
+ DIR *d;
+ struct dirent *dir;
+ d = opendir(start_dir);
+ if (d) {
+ set_active_directory(start_dir);
+ while ((dir = readdir(d)) != NULL) {
+ if (*is_cancelled) break;
+ set_active_directory(start_dir);
+
+ if (dir->d_type == DT_DIR)
+ {
+ if ((strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0))
+ continue;
+
+ if (include_directories)
+ {
+ if ((len = filter_matches(&filters, dir->d_name,
+ &matched_filter)) && len != -1)
+ {
+ char *buf;
+ if (bucket)
+ buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH);
+ else
+ buf = mem_alloc(MAX_INPUT_LENGTH);
+
+ //realpath(dir->d_name, buf);
+ snprintf(buf, MAX_INPUT_LENGTH, "%s%s",start_dir, dir->d_name);
+
+ found_file f;
+ f.path = buf;
+
+ if (bucket)
+ f.matched_filter = memory_bucket_reserve(bucket, len+1);
+ else
+ f.matched_filter = mem_alloc(len+1);
+
+ string_copyn(f.matched_filter, matched_filter, len+1);
+
+ mutex_lock(&list->mutex);
+ array_push_size(list, &f, sizeof(found_file));
+ mutex_unlock(&list->mutex);
+ }
+ }
+
+ if (recursive)
+ {
+ string_copyn(subdirname_buf, start_dir, MAX_INPUT_LENGTH);
+ string_appendn(subdirname_buf, dir->d_name, MAX_INPUT_LENGTH);
+ string_appendn(subdirname_buf, "/", MAX_INPUT_LENGTH);
+
+ // do recursive search
+ platform_list_files_block(list, subdirname_buf, filters, recursive, bucket, include_directories, is_cancelled);
+ }
+ }
+ // we handle DT_UNKNOWN for file systems that do not support type lookup.
+ else if (dir->d_type == DT_REG || dir->d_type == DT_UNKNOWN)
+ {
+ // check if name matches pattern
+ if ((len = filter_matches(&filters, dir->d_name,
+ &matched_filter)) && len != -1)
+ {
+ char *buf;
+ if (bucket)
+ buf = memory_bucket_reserve(bucket, MAX_INPUT_LENGTH);
+ else
+ buf = mem_alloc(MAX_INPUT_LENGTH);
+
+ //realpath(dir->d_name, buf);
+ snprintf(buf, MAX_INPUT_LENGTH, "%s%s",start_dir, dir->d_name);
+
+ found_file f;
+ f.path = buf;
+
+ if (bucket)
+ f.matched_filter = memory_bucket_reserve(bucket, len+1);
+ else
+ f.matched_filter = mem_alloc(len+1);
+
+ string_copyn(f.matched_filter, matched_filter, len+1);
+
+ mutex_lock(&list->mutex);
+ array_push_size(list, &f, sizeof(found_file));
+ mutex_unlock(&list->mutex);
+
+ }
+ }
+ }
+ closedir(d);
+ }
+
+ if (!bucket)
+ mem_free(subdirname_buf);
+}
+
+char *platform_get_full_path(char *file)
+{
+ char *buf = mem_alloc(PATH_MAX);
+ buf[0] = 0;
+
+ char *result = realpath(file, buf);
+
+ if (!result)
+ {
+ buf[0] = 0;
+ return buf;
+ }
+
+ return buf;
+}
+
+inline u64 string_to_u64(char *str)
+{
+ return (u64)strtoull(str, 0, 10);
+}
+
+inline u32 string_to_u32(char *str)
+{
+ return (u32)strtoul(str, 0, 10);
+}
+
+inline u16 string_to_u16(char *str)
+{
+ return (u16)strtoul(str, 0, 10);
+}
+
+inline u8 string_to_u8(char *str)
+{
+ return (u8)strtoul(str, 0, 10);
+}
+
+inline s64 string_to_s64(char *str)
+{
+ return (s64)strtoll(str, 0, 10);
+}
+
+inline s32 string_to_s32(char *str)
+{
+ return (u32)strtol(str, 0, 10);
+}
+
+inline s16 string_to_s16(char *str)
+{
+ return (s16)strtol(str, 0, 10);
+}
+
+inline s8 string_to_s8(char *str)
+{
+ return (s8)strtol(str, 0, 10);
+}
+
+inline void platform_open_url(char *url)
+{
+ char buffer[MAX_INPUT_LENGTH];
+ snprintf(buffer, MAX_INPUT_LENGTH, "xdg-open %s", url);
+ platform_run_command(buffer);
+}
+
+inline void platform_run_command(char *command)
+{
+ s32 result = system(command);
+}
+
+void platform_set_icon(platform_window *window, image *img)
+{
+ s32 w = img->width;
+ s32 h = img->height;
+
+ s32 nelements = (w * h) + 2;
+
+ unsigned long data[nelements];
+ int i = 0;
+ (data)[i++] = w;
+ (data)[i++] = h;
+
+ for (s32 y = 0; y < h; y++)
+ {
+ for (s32 x = 0; x < w; x++)
+ {
+ s32 *pixel = (s32*)(&((data)[i++]));
+
+ s32 img_pixel = *(((s32*)img->data+(x+(y*w))));
+
+ // 0xAABBGGRR
+ s32 a = (img_pixel>>24) & 0x000000FF;
+ s32 b = (img_pixel>>16) & 0x000000FF;
+ s32 g = (img_pixel>> 8) & 0x000000FF;
+ s32 r = (img_pixel>> 0) & 0x000000FF;
+
+ //s32 c = (r << 24) | (g << 16) | (b << 8) | (a << 0);
+ s32 c = (a << 24) | (r << 16) | (g << 8) | (b << 0);
+ *pixel = c;
+ }
+ }
+
+ Atom property = XInternAtom(window->display, "_NET_WM_ICON", 0);
+ Atom cardinal = XInternAtom(window->display, "CARDINAL", False);
+
+ int result = XChangeProperty(window->display, window->window,
+ property, cardinal, 32, PropModeReplace,
+ (unsigned char *)data, nelements);
+}
diff --git a/src/linux/thread.c b/src/linux/thread.c
new file mode 100644
index 0000000..b6295ef
--- /dev/null
+++ b/src/linux/thread.c
@@ -0,0 +1,129 @@
+/*
+* BSD 2-Clause “Simplified” License
+* Copyright (c) 2019, Aldrik Ramaekers, aldrik.ramaekers@protonmail.com
+* All rights reserved.
+*/
+
+// stop gcc from reporting implicit declaration warning..
+extern long int syscall (long int __sysno, ...);
+extern int pthread_tryjoin_np(pthread_t thread, void **retval);
+
+thread thread_start(void *(*start_routine) (void *), void *arg)
+{
+ thread result;
+ result.valid = false;
+
+ pthread_attr_t attr;
+ int attr_init_result = pthread_attr_init(&attr);
+ if (attr_init_result)
+ return result;
+
+ int start_thread_result = pthread_create(&result.thread, &attr, start_routine, arg);
+ if (start_thread_result)
+ {
+ pthread_attr_destroy(&attr);
+ return result;
+ }
+
+ result.valid = true;
+ pthread_attr_destroy(&attr);
+
+ return result;
+}
+
+inline void thread_detach(thread *thread)
+{
+ if (thread->valid)
+ {
+ pthread_detach(thread->thread);
+ }
+}
+
+inline void thread_join(thread *thread)
+{
+ if (thread->valid)
+ {
+ void *retval;
+ pthread_join(thread->thread, &retval);
+ }
+}
+
+bool thread_tryjoin(thread *thread)
+{
+ if (thread->valid)
+ {
+ void *retval;
+ bool thread_joined = !pthread_tryjoin_np(thread->thread, &retval);
+ return thread_joined;
+ }
+ return false;
+}
+
+inline void thread_stop(thread *thread)
+{
+ if (thread->valid)
+ {
+ pthread_cancel(thread->thread);
+ }
+}
+
+mutex mutex_create()
+{
+ mutex result;
+
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
+
+ pthread_mutex_init(&result.mutex, &attr);
+
+ pthread_mutexattr_destroy(&attr);
+
+ return result;
+}
+
+mutex mutex_create_recursive()
+{
+ mutex result;
+
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+
+ pthread_mutex_init(&result.mutex, &attr);
+
+ pthread_mutexattr_destroy(&attr);
+
+ return result;
+}
+
+inline void mutex_lock(mutex *mutex)
+{
+ pthread_mutex_lock(&mutex->mutex);
+}
+
+inline bool mutex_trylock(mutex *mutex)
+{
+ return !pthread_mutex_trylock(&mutex->mutex);
+}
+
+inline void mutex_unlock(mutex *mutex)
+{
+ pthread_mutex_unlock(&mutex->mutex);
+}
+
+inline void mutex_destroy(mutex *mutex)
+{
+ mutex_unlock(mutex);
+ pthread_mutex_destroy(&mutex->mutex);
+}
+
+inline u32 thread_get_id()
+{
+ return (u32)syscall(__NR_gettid);
+}
+
+inline void thread_sleep(u64 microseconds)
+{
+ usleep(microseconds);
+}