#include #include #include #include #include #include #include #include "api.h" #include "rencache.h" #ifdef _WIN32 #include #include #endif #ifdef __amigaos4__ #include "platform/amigaos4.h" #endif extern SDL_Window *window; static const char* button_name(int button) { switch (button) { case 1 : return "left"; case 2 : return "middle"; case 3 : return "right"; default : return "?"; } } static char* key_name(char *dst, int sym) { strcpy(dst, SDL_GetKeyName(sym)); char *p = dst; while (*p) { *p = tolower(*p); p++; } return dst; } struct HitTestInfo { int title_height; int controls_width; int resize_border; }; typedef struct HitTestInfo HitTestInfo; static HitTestInfo window_hit_info[1] = {{0, 0}}; #define RESIZE_FROM_TOP 0 #define RESIZE_FROM_RIGHT 0 static SDL_HitTestResult SDLCALL hit_test(SDL_Window *window, const SDL_Point *pt, void *data) { const HitTestInfo *hit_info = (HitTestInfo *) data; const int resize_border = hit_info->resize_border; const int controls_width = hit_info->controls_width; int w, h; SDL_GetWindowSize(window, &w, &h); if (pt->y < hit_info->title_height && #if RESIZE_FROM_TOP pt->y > hit_info->resize_border && #endif pt->x > resize_border && pt->x < w - controls_width) { return SDL_HITTEST_DRAGGABLE; } #define REPORT_RESIZE_HIT(name) { \ return SDL_HITTEST_RESIZE_##name; \ } if (pt->x < resize_border && pt->y < resize_border) { REPORT_RESIZE_HIT(TOPLEFT); #if RESIZE_FROM_TOP } else if (pt->x > resize_border && pt->x < w - controls_width && pt->y < resize_border) { REPORT_RESIZE_HIT(TOP); #endif } else if (pt->x > w - resize_border && pt->y < resize_border) { REPORT_RESIZE_HIT(TOPRIGHT); #if RESIZE_FROM_RIGHT } else if (pt->x > w - resize_border && pt->y > resize_border && pt->y < h - resize_border) { REPORT_RESIZE_HIT(RIGHT); #endif } else if (pt->x > w - resize_border && pt->y > h - resize_border) { REPORT_RESIZE_HIT(BOTTOMRIGHT); } else if (pt->x < w - resize_border && pt->x > resize_border && pt->y > h - resize_border) { REPORT_RESIZE_HIT(BOTTOM); } else if (pt->x < resize_border && pt->y > h - resize_border) { REPORT_RESIZE_HIT(BOTTOMLEFT); } else if (pt->x < resize_border && pt->y < h - resize_border && pt->y > resize_border) { REPORT_RESIZE_HIT(LEFT); } return SDL_HITTEST_NORMAL; } static int f_poll_event(lua_State *L) { char buf[16]; int mx, my, wx, wy; SDL_Event e; top: if ( !SDL_PollEvent(&e) ) { return 0; } switch (e.type) { case SDL_QUIT: lua_pushstring(L, "quit"); return 1; case SDL_WINDOWEVENT: if (e.window.event == SDL_WINDOWEVENT_RESIZED) { ren_resize_window(); lua_pushstring(L, "resized"); /* The size below will be in points. */ lua_pushnumber(L, e.window.data1); lua_pushnumber(L, e.window.data2); return 3; } else if (e.window.event == SDL_WINDOWEVENT_EXPOSED) { rencache_invalidate(); lua_pushstring(L, "exposed"); return 1; } else if (e.window.event == SDL_WINDOWEVENT_MINIMIZED) { lua_pushstring(L, "minimized"); return 1; } else if (e.window.event == SDL_WINDOWEVENT_MAXIMIZED) { lua_pushstring(L, "maximized"); return 1; } else if (e.window.event == SDL_WINDOWEVENT_RESTORED) { lua_pushstring(L, "restored"); return 1; } if (e.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { lua_pushstring(L, "focuslost"); return 1; } /* on some systems, when alt-tabbing to the window SDL will queue up ** several KEYDOWN events for the `tab` key; we flush all keydown ** events on focus so these are discarded */ if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { SDL_FlushEvent(SDL_KEYDOWN); } goto top; case SDL_DROPFILE: SDL_GetGlobalMouseState(&mx, &my); SDL_GetWindowPosition(window, &wx, &wy); lua_pushstring(L, "filedropped"); lua_pushstring(L, e.drop.file); lua_pushnumber(L, mx - wx); lua_pushnumber(L, my - wy); SDL_free(e.drop.file); return 4; case SDL_KEYDOWN: #ifdef __APPLE__ /* on macos 11.2.3 with sdl 2.0.14 the keyup handler for cmd+w below ** was not enough. Maybe the quit event started to be triggered from the ** keydown handler? In any case, flushing the quit event here too helped. */ if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) { SDL_FlushEvent(SDL_QUIT); } #endif lua_pushstring(L, "keypressed"); lua_pushstring(L, key_name(buf, e.key.keysym.sym)); return 2; case SDL_KEYUP: #ifdef __APPLE__ /* on macos command+w will close the current window ** we want to flush this event and let the keymapper ** handle this key combination. ** Thanks to mathewmariani, taken from his lite-macos github repository. */ if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) { SDL_FlushEvent(SDL_QUIT); } #endif lua_pushstring(L, "keyreleased"); lua_pushstring(L, key_name(buf, e.key.keysym.sym)); return 2; case SDL_TEXTINPUT: lua_pushstring(L, "textinput"); lua_pushstring(L, e.text.text); return 2; case SDL_MOUSEBUTTONDOWN: if (e.button.button == 1) { SDL_CaptureMouse(1); } lua_pushstring(L, "mousepressed"); lua_pushstring(L, button_name(e.button.button)); lua_pushnumber(L, e.button.x); lua_pushnumber(L, e.button.y); lua_pushnumber(L, e.button.clicks); return 5; case SDL_MOUSEBUTTONUP: if (e.button.button == 1) { SDL_CaptureMouse(0); } lua_pushstring(L, "mousereleased"); lua_pushstring(L, button_name(e.button.button)); lua_pushnumber(L, e.button.x); lua_pushnumber(L, e.button.y); return 4; case SDL_MOUSEMOTION: SDL_PumpEvents(); SDL_Event event_plus; while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) { e.motion.x = event_plus.motion.x; e.motion.y = event_plus.motion.y; e.motion.xrel += event_plus.motion.xrel; e.motion.yrel += event_plus.motion.yrel; } lua_pushstring(L, "mousemoved"); lua_pushnumber(L, e.motion.x); lua_pushnumber(L, e.motion.y); lua_pushnumber(L, e.motion.xrel); lua_pushnumber(L, e.motion.yrel); return 5; case SDL_MOUSEWHEEL: lua_pushstring(L, "mousewheel"); lua_pushnumber(L, e.wheel.y); return 2; default: goto top; } return 0; } static int f_wait_event(lua_State *L) { int nargs = lua_gettop(L); if (nargs >= 1) { double n = luaL_checknumber(L, 1); lua_pushboolean(L, SDL_WaitEventTimeout(NULL, n * 1000)); } else { lua_pushboolean(L, SDL_WaitEvent(NULL)); } return 1; } static SDL_Cursor* cursor_cache[SDL_SYSTEM_CURSOR_HAND + 1]; static const char *cursor_opts[] = { "arrow", "ibeam", "sizeh", "sizev", "hand", NULL }; static const int cursor_enums[] = { SDL_SYSTEM_CURSOR_ARROW, SDL_SYSTEM_CURSOR_IBEAM, SDL_SYSTEM_CURSOR_SIZEWE, SDL_SYSTEM_CURSOR_SIZENS, SDL_SYSTEM_CURSOR_HAND }; static int f_set_cursor(lua_State *L) { int opt = luaL_checkoption(L, 1, "arrow", cursor_opts); int n = cursor_enums[opt]; SDL_Cursor *cursor = cursor_cache[n]; if (!cursor) { cursor = SDL_CreateSystemCursor(n); cursor_cache[n] = cursor; } SDL_SetCursor(cursor); return 0; } static int f_set_window_title(lua_State *L) { const char *title = luaL_checkstring(L, 1); SDL_SetWindowTitle(window, title); return 0; } static const char *window_opts[] = { "normal", "minimized", "maximized", "fullscreen", 0 }; enum { WIN_NORMAL, WIN_MINIMIZED, WIN_MAXIMIZED, WIN_FULLSCREEN }; static int f_set_window_mode(lua_State *L) { int n = luaL_checkoption(L, 1, "normal", window_opts); SDL_SetWindowFullscreen(window, n == WIN_FULLSCREEN ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); if (n == WIN_NORMAL) { SDL_RestoreWindow(window); } if (n == WIN_MAXIMIZED) { SDL_MaximizeWindow(window); } if (n == WIN_MINIMIZED) { SDL_MinimizeWindow(window); } return 0; } static int f_set_window_bordered(lua_State *L) { int bordered = lua_toboolean(L, 1); SDL_SetWindowBordered(window, bordered); return 0; } static int f_set_window_hit_test(lua_State *L) { if (lua_gettop(L) == 0) { SDL_SetWindowHitTest(window, NULL, NULL); return 0; } window_hit_info->title_height = luaL_checknumber(L, 1); window_hit_info->controls_width = luaL_checknumber(L, 2); window_hit_info->resize_border = luaL_checknumber(L, 3); SDL_SetWindowHitTest(window, hit_test, window_hit_info); return 0; } static int f_get_window_size(lua_State *L) { int x, y, w, h; SDL_GetWindowSize(window, &w, &h); SDL_GetWindowPosition(window, &x, &y); lua_pushnumber(L, w); lua_pushnumber(L, h); lua_pushnumber(L, x); lua_pushnumber(L, y); return 4; } static int f_set_window_size(lua_State *L) { double w = luaL_checknumber(L, 1); double h = luaL_checknumber(L, 2); double x = luaL_checknumber(L, 3); double y = luaL_checknumber(L, 4); SDL_SetWindowSize(window, w, h); SDL_SetWindowPosition(window, x, y); ren_resize_window(); return 0; } static int f_window_has_focus(lua_State *L) { unsigned flags = SDL_GetWindowFlags(window); lua_pushboolean(L, flags & SDL_WINDOW_INPUT_FOCUS); return 1; } static int f_get_window_mode(lua_State *L) { unsigned flags = SDL_GetWindowFlags(window); if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { lua_pushstring(L, "fullscreen"); } else if (flags & SDL_WINDOW_MINIMIZED) { lua_pushstring(L, "minimized"); } else if (flags & SDL_WINDOW_MAXIMIZED) { lua_pushstring(L, "maximized"); } else { lua_pushstring(L, "normal"); } return 1; } static int f_show_fatal_error(lua_State *L) { const char *title = luaL_checkstring(L, 1); const char *msg = luaL_checkstring(L, 2); #ifdef _WIN32 MessageBox(0, msg, title, MB_OK | MB_ICONERROR); #else SDL_MessageBoxButtonData buttons[] = { { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Ok" }, }; SDL_MessageBoxData data = { .title = title, .message = msg, .numbuttons = 1, .buttons = buttons, }; int buttonid; SDL_ShowMessageBox(&data, &buttonid); #endif return 0; } static int f_chdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); int err = chdir(path); if (err) { luaL_error(L, "chdir() failed"); } return 0; } static int f_list_dir(lua_State *L) { const char *path = luaL_checkstring(L, 1); DIR *dir = opendir(path); if (!dir) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } lua_newtable(L); int i = 1; struct dirent *entry; while ( (entry = readdir(dir)) ) { if (strcmp(entry->d_name, "." ) == 0) { continue; } if (strcmp(entry->d_name, "..") == 0) { continue; } lua_pushstring(L, entry->d_name); lua_rawseti(L, -2, i); i++; } closedir(dir); return 1; } #ifdef _WIN32 #include #define realpath(x, y) _fullpath(y, x, MAX_PATH) #endif #ifdef __amigaos4__ #define realpath(x, y) _fullpath(x) #endif static int f_absolute_path(lua_State *L) { const char *path = luaL_checkstring(L, 1); char *res = realpath(path, NULL); if (!res) { return 0; } lua_pushstring(L, res); free(res); return 1; } static int f_get_file_info(lua_State *L) { const char *path = luaL_checkstring(L, 1); struct stat s; int err = stat(path, &s); if (err < 0) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } lua_newtable(L); lua_pushnumber(L, s.st_mtime); lua_setfield(L, -2, "modified"); lua_pushnumber(L, s.st_size); lua_setfield(L, -2, "size"); if (S_ISREG(s.st_mode)) { lua_pushstring(L, "file"); } else if (S_ISDIR(s.st_mode)) { lua_pushstring(L, "dir"); } else { lua_pushnil(L); } lua_setfield(L, -2, "type"); return 1; } static int f_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 int err = _mkdir(path); #else int err = mkdir(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); #endif if (err < 0) { lua_pushboolean(L, 0); lua_pushstring(L, strerror(errno)); return 2; } lua_pushboolean(L, 1); return 1; } static int f_get_clipboard(lua_State *L) { char *text = SDL_GetClipboardText(); if (!text) { return 0; } lua_pushstring(L, text); SDL_free(text); return 1; } static int f_set_clipboard(lua_State *L) { const char *text = luaL_checkstring(L, 1); SDL_SetClipboardText(text); return 0; } static int f_get_time(lua_State *L) { double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency(); lua_pushnumber(L, n); return 1; } static int f_sleep(lua_State *L) { double n = luaL_checknumber(L, 1); SDL_Delay(n * 1000); return 0; } static int f_exec(lua_State *L) { size_t len; const char *cmd = luaL_checklstring(L, 1, &len); char *buf = malloc(len + 32); if (!buf) { luaL_error(L, "buffer allocation failed"); } #if _WIN32 sprintf(buf, "cmd /c \"%s\"", cmd); WinExec(buf, SW_HIDE); #else sprintf(buf, "%s &", cmd); int res = system(buf); (void) res; #endif free(buf); return 0; } static int f_fuzzy_match(lua_State *L) { size_t strLen, ptnLen; const char *str = luaL_checklstring(L, 1, &strLen); const char *ptn = luaL_checklstring(L, 2, &ptnLen); bool files = false; if (lua_gettop(L) > 2 && lua_isboolean(L,3)) files = lua_toboolean(L, 3); int score = 0; int run = 0; // Match things *backwards*. This allows for better matching on filenames than the above // function. For example, in the lite project, opening "renderer" has lib/font_render/build.sh // as the first result, rather than src/renderer.c. Clearly that's wrong. if (files) { const char* strEnd = str + strLen - 1; const char* ptnEnd = ptn + ptnLen - 1; while (strEnd >= str && ptnEnd >= ptn) { while (*strEnd == ' ') { strEnd--; } while (*ptnEnd == ' ') { ptnEnd--; } if (tolower(*strEnd) == tolower(*ptnEnd)) { score += run * 10 - (*strEnd != *ptnEnd); run++; ptnEnd--; } else { score -= 10; run = 0; } strEnd--; } if (ptnEnd >= ptn) { return 0; } } else { while (*str && *ptn) { while (*str == ' ') { str++; } while (*ptn == ' ') { ptn++; } if (tolower(*str) == tolower(*ptn)) { score += run * 10 - (*str != *ptn); run++; ptn++; } else { score -= 10; run = 0; } str++; } if (*ptn) { return 0; } } lua_pushnumber(L, score - (int)strLen); return 1; } static int f_set_window_opacity(lua_State *L) { double n = luaL_checknumber(L, 1); int r = SDL_SetWindowOpacity(window, n); lua_pushboolean(L, r > -1); return 1; } static const luaL_Reg lib[] = { { "poll_event", f_poll_event }, { "wait_event", f_wait_event }, { "set_cursor", f_set_cursor }, { "set_window_title", f_set_window_title }, { "set_window_mode", f_set_window_mode }, { "get_window_mode", f_get_window_mode }, { "set_window_bordered", f_set_window_bordered }, { "set_window_hit_test", f_set_window_hit_test }, { "get_window_size", f_get_window_size }, { "set_window_size", f_set_window_size }, { "window_has_focus", f_window_has_focus }, { "show_fatal_error", f_show_fatal_error }, { "chdir", f_chdir }, { "mkdir", f_mkdir }, { "list_dir", f_list_dir }, { "absolute_path", f_absolute_path }, { "get_file_info", f_get_file_info }, { "get_clipboard", f_get_clipboard }, { "set_clipboard", f_set_clipboard }, { "get_time", f_get_time }, { "sleep", f_sleep }, { "exec", f_exec }, { "fuzzy_match", f_fuzzy_match }, { "set_window_opacity", f_set_window_opacity }, { NULL, NULL } }; int luaopen_system(lua_State *L) { luaL_newlib(L, lib); return 1; }