1 | #include <SDL.h> |
2 | #include <string.h> |
3 | #include <stdbool.h> |
4 | #include <ctype.h> |
5 | #include <errno.h> |
6 | #include <sys/types.h> |
7 | #include <sys/stat.h> |
8 | #include "api.h" |
9 | #include "../rencache.h" |
10 | #include "../renwindow.h" |
11 | #ifdef _WIN32 |
12 | #include <direct.h> |
13 | #include <windows.h> |
14 | #include <fileapi.h> |
15 | #include "../utfconv.h" |
16 | |
17 | // Windows does not define the S_ISREG and S_ISDIR macros in stat.h, so we do. |
18 | // We have to define _CRT_INTERNAL_NONSTDC_NAMES 1 before #including sys/stat.h |
19 | // in order for Microsoft's stat.h to define names like S_IFMT, S_IFREG, and S_IFDIR, |
20 | // rather than just defining _S_IFMT, _S_IFREG, and _S_IFDIR as it normally does. |
21 | #define _CRT_INTERNAL_NONSTDC_NAMES 1 |
22 | #include <sys/types.h> |
23 | #include <sys/stat.h> |
24 | #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) |
25 | #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) |
26 | #endif |
27 | #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) |
28 | #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) |
29 | #endif |
30 | #else |
31 | |
32 | #include <dirent.h> |
33 | #include <unistd.h> |
34 | |
35 | #ifdef __linux__ |
36 | #include <sys/vfs.h> |
37 | #endif |
38 | #endif |
39 | |
40 | static const char* button_name(int button) { |
41 | switch (button) { |
42 | case SDL_BUTTON_LEFT : return "left" ; |
43 | case SDL_BUTTON_MIDDLE : return "middle" ; |
44 | case SDL_BUTTON_RIGHT : return "right" ; |
45 | case SDL_BUTTON_X1 : return "x" ; |
46 | case SDL_BUTTON_X2 : return "y" ; |
47 | default : return "?" ; |
48 | } |
49 | } |
50 | |
51 | |
52 | static void str_tolower(char *p) { |
53 | while (*p) { |
54 | *p = tolower(*p); |
55 | p++; |
56 | } |
57 | } |
58 | |
59 | struct HitTestInfo { |
60 | int title_height; |
61 | int controls_width; |
62 | int resize_border; |
63 | }; |
64 | typedef struct HitTestInfo HitTestInfo; |
65 | |
66 | static HitTestInfo window_hit_info[1] = {{0, 0, 0}}; |
67 | |
68 | #define RESIZE_FROM_TOP 0 |
69 | #define RESIZE_FROM_RIGHT 0 |
70 | |
71 | static SDL_HitTestResult SDLCALL hit_test(SDL_Window *window, const SDL_Point *pt, void *data) { |
72 | const HitTestInfo *hit_info = (HitTestInfo *) data; |
73 | const int resize_border = hit_info->resize_border; |
74 | const int controls_width = hit_info->controls_width; |
75 | int w, h; |
76 | |
77 | SDL_GetWindowSize(window_renderer.window, &w, &h); |
78 | |
79 | if (pt->y < hit_info->title_height && |
80 | #if RESIZE_FROM_TOP |
81 | pt->y > hit_info->resize_border && |
82 | #endif |
83 | pt->x > resize_border && pt->x < w - controls_width) { |
84 | return SDL_HITTEST_DRAGGABLE; |
85 | } |
86 | |
87 | #define REPORT_RESIZE_HIT(name) { \ |
88 | return SDL_HITTEST_RESIZE_##name; \ |
89 | } |
90 | |
91 | if (pt->x < resize_border && pt->y < resize_border) { |
92 | REPORT_RESIZE_HIT(TOPLEFT); |
93 | #if RESIZE_FROM_TOP |
94 | } else if (pt->x > resize_border && pt->x < w - controls_width && pt->y < resize_border) { |
95 | REPORT_RESIZE_HIT(TOP); |
96 | #endif |
97 | } else if (pt->x > w - resize_border && pt->y < resize_border) { |
98 | REPORT_RESIZE_HIT(TOPRIGHT); |
99 | #if RESIZE_FROM_RIGHT |
100 | } else if (pt->x > w - resize_border && pt->y > resize_border && pt->y < h - resize_border) { |
101 | REPORT_RESIZE_HIT(RIGHT); |
102 | #endif |
103 | } else if (pt->x > w - resize_border && pt->y > h - resize_border) { |
104 | REPORT_RESIZE_HIT(BOTTOMRIGHT); |
105 | } else if (pt->x < w - resize_border && pt->x > resize_border && pt->y > h - resize_border) { |
106 | REPORT_RESIZE_HIT(BOTTOM); |
107 | } else if (pt->x < resize_border && pt->y > h - resize_border) { |
108 | REPORT_RESIZE_HIT(BOTTOMLEFT); |
109 | } else if (pt->x < resize_border && pt->y < h - resize_border && pt->y > resize_border) { |
110 | REPORT_RESIZE_HIT(LEFT); |
111 | } |
112 | |
113 | return SDL_HITTEST_NORMAL; |
114 | } |
115 | |
116 | static const char *numpad[] = { "end" , "down" , "pagedown" , "left" , "" , "right" , "home" , "up" , "pageup" , "ins" , "delete" }; |
117 | |
118 | static const char *get_key_name(const SDL_Event *e, char *buf) { |
119 | SDL_Scancode scancode = e->key.keysym.scancode; |
120 | /* Is the scancode from the keypad and the number-lock off? |
121 | ** We assume that SDL_SCANCODE_KP_1 up to SDL_SCANCODE_KP_9 and SDL_SCANCODE_KP_0 |
122 | ** and SDL_SCANCODE_KP_PERIOD are declared in SDL2 in that order. */ |
123 | if (scancode >= SDL_SCANCODE_KP_1 && scancode <= SDL_SCANCODE_KP_1 + 10 && |
124 | !(e->key.keysym.mod & KMOD_NUM)) { |
125 | return numpad[scancode - SDL_SCANCODE_KP_1]; |
126 | } else { |
127 | /* We need to correctly handle non-standard layouts such as dvorak. |
128 | Therefore, if a Latin letter(code<128) is pressed in the current layout, |
129 | then we transmit it as it is. But we also need to support shortcuts in |
130 | other languages, so for non-Latin characters(code>128) we pass the |
131 | scancode based name that matches the letter in the QWERTY layout. |
132 | |
133 | In SDL, the codes of all special buttons such as control, shift, arrows |
134 | and others, are masked with SDLK_SCANCODE_MASK, which moves them outside |
135 | the unicode range (>0x10FFFF). Users can remap these buttons, so we need |
136 | to return the correct name, not scancode based. */ |
137 | if ((e->key.keysym.sym < 128) || (e->key.keysym.sym & SDLK_SCANCODE_MASK)) |
138 | strcpy(buf, SDL_GetKeyName(e->key.keysym.sym)); |
139 | else |
140 | strcpy(buf, SDL_GetScancodeName(scancode)); |
141 | str_tolower(buf); |
142 | return buf; |
143 | } |
144 | } |
145 | |
146 | #ifdef _WIN32 |
147 | static char *win32_error(DWORD rc) { |
148 | LPSTR message; |
149 | FormatMessage( |
150 | FORMAT_MESSAGE_ALLOCATE_BUFFER | |
151 | FORMAT_MESSAGE_FROM_SYSTEM | |
152 | FORMAT_MESSAGE_IGNORE_INSERTS, |
153 | NULL, |
154 | rc, |
155 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
156 | (LPTSTR) &message, |
157 | 0, |
158 | NULL |
159 | ); |
160 | |
161 | return message; |
162 | } |
163 | |
164 | static void push_win32_error(lua_State *L, DWORD rc) { |
165 | LPSTR message = win32_error(rc); |
166 | lua_pushstring(L, message); |
167 | LocalFree(message); |
168 | } |
169 | #endif |
170 | |
171 | static int f_poll_event(lua_State *L) { |
172 | char buf[16]; |
173 | int mx, my, w, h; |
174 | SDL_Event e; |
175 | SDL_Event event_plus; |
176 | |
177 | top: |
178 | if ( !SDL_PollEvent(&e) ) { |
179 | return 0; |
180 | } |
181 | |
182 | switch (e.type) { |
183 | case SDL_QUIT: |
184 | lua_pushstring(L, "quit" ); |
185 | return 1; |
186 | |
187 | case SDL_WINDOWEVENT: |
188 | if (e.window.event == SDL_WINDOWEVENT_RESIZED) { |
189 | ren_resize_window(&window_renderer); |
190 | lua_pushstring(L, "resized" ); |
191 | /* The size below will be in points. */ |
192 | lua_pushinteger(L, e.window.data1); |
193 | lua_pushinteger(L, e.window.data2); |
194 | return 3; |
195 | } else if (e.window.event == SDL_WINDOWEVENT_EXPOSED) { |
196 | rencache_invalidate(); |
197 | lua_pushstring(L, "exposed" ); |
198 | return 1; |
199 | } else if (e.window.event == SDL_WINDOWEVENT_MINIMIZED) { |
200 | lua_pushstring(L, "minimized" ); |
201 | return 1; |
202 | } else if (e.window.event == SDL_WINDOWEVENT_MAXIMIZED) { |
203 | lua_pushstring(L, "maximized" ); |
204 | return 1; |
205 | } else if (e.window.event == SDL_WINDOWEVENT_RESTORED) { |
206 | lua_pushstring(L, "restored" ); |
207 | return 1; |
208 | } else if (e.window.event == SDL_WINDOWEVENT_LEAVE) { |
209 | lua_pushstring(L, "mouseleft" ); |
210 | return 1; |
211 | } |
212 | if (e.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { |
213 | lua_pushstring(L, "focuslost" ); |
214 | return 1; |
215 | } |
216 | /* on some systems, when alt-tabbing to the window SDL will queue up |
217 | ** several KEYDOWN events for the `tab` key; we flush all keydown |
218 | ** events on focus so these are discarded */ |
219 | if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { |
220 | SDL_FlushEvent(SDL_KEYDOWN); |
221 | } |
222 | goto top; |
223 | |
224 | case SDL_DROPFILE: |
225 | SDL_GetMouseState(&mx, &my); |
226 | lua_pushstring(L, "filedropped" ); |
227 | lua_pushstring(L, e.drop.file); |
228 | lua_pushinteger(L, mx); |
229 | lua_pushinteger(L, my); |
230 | SDL_free(e.drop.file); |
231 | return 4; |
232 | |
233 | case SDL_KEYDOWN: |
234 | #ifdef __APPLE__ |
235 | /* on macos 11.2.3 with sdl 2.0.14 the keyup handler for cmd+w below |
236 | ** was not enough. Maybe the quit event started to be triggered from the |
237 | ** keydown handler? In any case, flushing the quit event here too helped. */ |
238 | if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) { |
239 | SDL_FlushEvent(SDL_QUIT); |
240 | } |
241 | #endif |
242 | lua_pushstring(L, "keypressed" ); |
243 | lua_pushstring(L, get_key_name(&e, buf)); |
244 | return 2; |
245 | |
246 | case SDL_KEYUP: |
247 | #ifdef __APPLE__ |
248 | /* on macos command+w will close the current window |
249 | ** we want to flush this event and let the keymapper |
250 | ** handle this key combination. |
251 | ** Thanks to mathewmariani, taken from his lite-macos github repository. */ |
252 | if ((e.key.keysym.sym == SDLK_w) && (e.key.keysym.mod & KMOD_GUI)) { |
253 | SDL_FlushEvent(SDL_QUIT); |
254 | } |
255 | #endif |
256 | lua_pushstring(L, "keyreleased" ); |
257 | lua_pushstring(L, get_key_name(&e, buf)); |
258 | return 2; |
259 | |
260 | case SDL_TEXTINPUT: |
261 | lua_pushstring(L, "textinput" ); |
262 | lua_pushstring(L, e.text.text); |
263 | return 2; |
264 | |
265 | case SDL_TEXTEDITING: |
266 | lua_pushstring(L, "textediting" ); |
267 | lua_pushstring(L, e.edit.text); |
268 | lua_pushinteger(L, e.edit.start); |
269 | lua_pushinteger(L, e.edit.length); |
270 | return 4; |
271 | |
272 | #if SDL_VERSION_ATLEAST(2, 0, 22) |
273 | case SDL_TEXTEDITING_EXT: |
274 | lua_pushstring(L, "textediting" ); |
275 | lua_pushstring(L, e.editExt.text); |
276 | lua_pushinteger(L, e.editExt.start); |
277 | lua_pushinteger(L, e.editExt.length); |
278 | SDL_free(e.editExt.text); |
279 | return 4; |
280 | #endif |
281 | |
282 | case SDL_MOUSEBUTTONDOWN: |
283 | if (e.button.button == 1) { SDL_CaptureMouse(1); } |
284 | lua_pushstring(L, "mousepressed" ); |
285 | lua_pushstring(L, button_name(e.button.button)); |
286 | lua_pushinteger(L, e.button.x); |
287 | lua_pushinteger(L, e.button.y); |
288 | lua_pushinteger(L, e.button.clicks); |
289 | return 5; |
290 | |
291 | case SDL_MOUSEBUTTONUP: |
292 | if (e.button.button == 1) { SDL_CaptureMouse(0); } |
293 | lua_pushstring(L, "mousereleased" ); |
294 | lua_pushstring(L, button_name(e.button.button)); |
295 | lua_pushinteger(L, e.button.x); |
296 | lua_pushinteger(L, e.button.y); |
297 | return 4; |
298 | |
299 | case SDL_MOUSEMOTION: |
300 | SDL_PumpEvents(); |
301 | while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) { |
302 | e.motion.x = event_plus.motion.x; |
303 | e.motion.y = event_plus.motion.y; |
304 | e.motion.xrel += event_plus.motion.xrel; |
305 | e.motion.yrel += event_plus.motion.yrel; |
306 | } |
307 | lua_pushstring(L, "mousemoved" ); |
308 | lua_pushinteger(L, e.motion.x); |
309 | lua_pushinteger(L, e.motion.y); |
310 | lua_pushinteger(L, e.motion.xrel); |
311 | lua_pushinteger(L, e.motion.yrel); |
312 | return 5; |
313 | |
314 | case SDL_MOUSEWHEEL: |
315 | lua_pushstring(L, "mousewheel" ); |
316 | #if SDL_VERSION_ATLEAST(2, 0, 18) |
317 | lua_pushnumber(L, e.wheel.preciseY); |
318 | // Use -x to keep consistency with vertical scrolling values (e.g. shift+scroll) |
319 | lua_pushnumber(L, -e.wheel.preciseX); |
320 | #else |
321 | lua_pushinteger(L, e.wheel.y); |
322 | lua_pushinteger(L, -e.wheel.x); |
323 | #endif |
324 | return 3; |
325 | |
326 | case SDL_FINGERDOWN: |
327 | SDL_GetWindowSize(window_renderer.window, &w, &h); |
328 | |
329 | lua_pushstring(L, "touchpressed" ); |
330 | lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w)); |
331 | lua_pushinteger(L, (lua_Integer)(e.tfinger.y * h)); |
332 | lua_pushinteger(L, e.tfinger.fingerId); |
333 | return 4; |
334 | |
335 | case SDL_FINGERUP: |
336 | SDL_GetWindowSize(window_renderer.window, &w, &h); |
337 | |
338 | lua_pushstring(L, "touchreleased" ); |
339 | lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w)); |
340 | lua_pushinteger(L, (lua_Integer)(e.tfinger.y * h)); |
341 | lua_pushinteger(L, e.tfinger.fingerId); |
342 | return 4; |
343 | |
344 | case SDL_FINGERMOTION: |
345 | SDL_PumpEvents(); |
346 | while (SDL_PeepEvents(&event_plus, 1, SDL_GETEVENT, SDL_FINGERMOTION, SDL_FINGERMOTION) > 0) { |
347 | e.tfinger.x = event_plus.tfinger.x; |
348 | e.tfinger.y = event_plus.tfinger.y; |
349 | e.tfinger.dx += event_plus.tfinger.dx; |
350 | e.tfinger.dy += event_plus.tfinger.dy; |
351 | } |
352 | SDL_GetWindowSize(window_renderer.window, &w, &h); |
353 | |
354 | lua_pushstring(L, "touchmoved" ); |
355 | lua_pushinteger(L, (lua_Integer)(e.tfinger.x * w)); |
356 | lua_pushinteger(L, (lua_Integer)(e.tfinger.y * h)); |
357 | lua_pushinteger(L, (lua_Integer)(e.tfinger.dx * w)); |
358 | lua_pushinteger(L, (lua_Integer)(e.tfinger.dy * h)); |
359 | lua_pushinteger(L, e.tfinger.fingerId); |
360 | return 6; |
361 | case SDL_APP_WILLENTERFOREGROUND: |
362 | case SDL_APP_DIDENTERFOREGROUND: |
363 | #ifdef LITE_USE_SDL_RENDERER |
364 | rencache_invalidate(); |
365 | #else |
366 | SDL_UpdateWindowSurface(window_renderer.window); |
367 | #endif |
368 | lua_pushstring(L, e.type == SDL_APP_WILLENTERFOREGROUND ? "enteringforeground" : "enteredforeground" ); |
369 | return 1; |
370 | case SDL_APP_WILLENTERBACKGROUND: |
371 | lua_pushstring(L, "enteringbackground" ); |
372 | return 1; |
373 | case SDL_APP_DIDENTERBACKGROUND: |
374 | lua_pushstring(L, "enteredbackground" ); |
375 | return 1; |
376 | |
377 | default: |
378 | goto top; |
379 | } |
380 | |
381 | return 0; |
382 | } |
383 | |
384 | |
385 | static int f_wait_event(lua_State *L) { |
386 | int nargs = lua_gettop(L); |
387 | if (nargs >= 1) { |
388 | double n = luaL_checknumber(L, 1); |
389 | lua_pushboolean(L, SDL_WaitEventTimeout(NULL, n * 1000)); |
390 | } else { |
391 | lua_pushboolean(L, SDL_WaitEvent(NULL)); |
392 | } |
393 | return 1; |
394 | } |
395 | |
396 | |
397 | static SDL_Cursor* cursor_cache[SDL_SYSTEM_CURSOR_HAND + 1]; |
398 | |
399 | static const char *cursor_opts[] = { |
400 | "arrow" , |
401 | "ibeam" , |
402 | "sizeh" , |
403 | "sizev" , |
404 | "hand" , |
405 | NULL |
406 | }; |
407 | |
408 | static const int cursor_enums[] = { |
409 | SDL_SYSTEM_CURSOR_ARROW, |
410 | SDL_SYSTEM_CURSOR_IBEAM, |
411 | SDL_SYSTEM_CURSOR_SIZEWE, |
412 | SDL_SYSTEM_CURSOR_SIZENS, |
413 | SDL_SYSTEM_CURSOR_HAND |
414 | }; |
415 | |
416 | static int f_set_cursor(lua_State *L) { |
417 | int opt = luaL_checkoption(L, 1, "arrow" , cursor_opts); |
418 | int n = cursor_enums[opt]; |
419 | SDL_Cursor *cursor = cursor_cache[n]; |
420 | if (!cursor) { |
421 | cursor = SDL_CreateSystemCursor(n); |
422 | cursor_cache[n] = cursor; |
423 | } |
424 | SDL_SetCursor(cursor); |
425 | return 0; |
426 | } |
427 | |
428 | |
429 | static int f_set_window_title(lua_State *L) { |
430 | const char *title = luaL_checkstring(L, 1); |
431 | SDL_SetWindowTitle(window_renderer.window, title); |
432 | return 0; |
433 | } |
434 | |
435 | |
436 | static const char *window_opts[] = { "normal" , "minimized" , "maximized" , "fullscreen" , 0 }; |
437 | enum { WIN_NORMAL, WIN_MINIMIZED, WIN_MAXIMIZED, WIN_FULLSCREEN }; |
438 | |
439 | static int f_set_window_mode(lua_State *L) { |
440 | int n = luaL_checkoption(L, 1, "normal" , window_opts); |
441 | SDL_SetWindowFullscreen(window_renderer.window, |
442 | n == WIN_FULLSCREEN ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); |
443 | if (n == WIN_NORMAL) { SDL_RestoreWindow(window_renderer.window); } |
444 | if (n == WIN_MAXIMIZED) { SDL_MaximizeWindow(window_renderer.window); } |
445 | if (n == WIN_MINIMIZED) { SDL_MinimizeWindow(window_renderer.window); } |
446 | return 0; |
447 | } |
448 | |
449 | |
450 | static int f_set_window_bordered(lua_State *L) { |
451 | int bordered = lua_toboolean(L, 1); |
452 | SDL_SetWindowBordered(window_renderer.window, bordered); |
453 | return 0; |
454 | } |
455 | |
456 | |
457 | static int f_set_window_hit_test(lua_State *L) { |
458 | if (lua_gettop(L) == 0) { |
459 | SDL_SetWindowHitTest(window_renderer.window, NULL, NULL); |
460 | return 0; |
461 | } |
462 | window_hit_info->title_height = luaL_checknumber(L, 1); |
463 | window_hit_info->controls_width = luaL_checknumber(L, 2); |
464 | window_hit_info->resize_border = luaL_checknumber(L, 3); |
465 | SDL_SetWindowHitTest(window_renderer.window, hit_test, window_hit_info); |
466 | return 0; |
467 | } |
468 | |
469 | |
470 | static int f_get_window_size(lua_State *L) { |
471 | int x, y, w, h; |
472 | SDL_GetWindowSize(window_renderer.window, &w, &h); |
473 | SDL_GetWindowPosition(window_renderer.window, &x, &y); |
474 | lua_pushinteger(L, w); |
475 | lua_pushinteger(L, h); |
476 | lua_pushinteger(L, x); |
477 | lua_pushinteger(L, y); |
478 | return 4; |
479 | } |
480 | |
481 | |
482 | static int f_set_window_size(lua_State *L) { |
483 | double w = luaL_checknumber(L, 1); |
484 | double h = luaL_checknumber(L, 2); |
485 | double x = luaL_checknumber(L, 3); |
486 | double y = luaL_checknumber(L, 4); |
487 | SDL_SetWindowSize(window_renderer.window, w, h); |
488 | SDL_SetWindowPosition(window_renderer.window, x, y); |
489 | ren_resize_window(&window_renderer); |
490 | return 0; |
491 | } |
492 | |
493 | |
494 | static int f_window_has_focus(lua_State *L) { |
495 | unsigned flags = SDL_GetWindowFlags(window_renderer.window); |
496 | lua_pushboolean(L, flags & SDL_WINDOW_INPUT_FOCUS); |
497 | return 1; |
498 | } |
499 | |
500 | |
501 | static int f_get_window_mode(lua_State *L) { |
502 | unsigned flags = SDL_GetWindowFlags(window_renderer.window); |
503 | if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { |
504 | lua_pushstring(L, "fullscreen" ); |
505 | } else if (flags & SDL_WINDOW_MINIMIZED) { |
506 | lua_pushstring(L, "minimized" ); |
507 | } else if (flags & SDL_WINDOW_MAXIMIZED) { |
508 | lua_pushstring(L, "maximized" ); |
509 | } else { |
510 | lua_pushstring(L, "normal" ); |
511 | } |
512 | return 1; |
513 | } |
514 | |
515 | static int f_set_text_input_rect(lua_State *L) { |
516 | SDL_Rect rect; |
517 | rect.x = luaL_checknumber(L, 1); |
518 | rect.y = luaL_checknumber(L, 2); |
519 | rect.w = luaL_checknumber(L, 3); |
520 | rect.h = luaL_checknumber(L, 4); |
521 | SDL_SetTextInputRect(&rect); |
522 | return 0; |
523 | } |
524 | |
525 | static int f_clear_ime(lua_State *L) { |
526 | #if SDL_VERSION_ATLEAST(2, 0, 22) |
527 | SDL_ClearComposition(); |
528 | #endif |
529 | return 0; |
530 | } |
531 | |
532 | |
533 | static int f_raise_window(lua_State *L) { |
534 | /* |
535 | SDL_RaiseWindow should be enough but on some window managers like the |
536 | one used on Gnome the window needs to first have input focus in order |
537 | to allow the window to be focused. Also on wayland the raise window event |
538 | may not always be obeyed. |
539 | */ |
540 | SDL_SetWindowInputFocus(window_renderer.window); |
541 | SDL_RaiseWindow(window_renderer.window); |
542 | return 0; |
543 | } |
544 | |
545 | |
546 | static int f_show_fatal_error(lua_State *L) { |
547 | const char *title = luaL_checkstring(L, 1); |
548 | const char *msg = luaL_checkstring(L, 2); |
549 | |
550 | #ifdef _WIN32 |
551 | MessageBox(0, msg, title, MB_OK | MB_ICONERROR); |
552 | #else |
553 | SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, msg, NULL); |
554 | #endif |
555 | return 0; |
556 | } |
557 | |
558 | |
559 | // removes an empty directory |
560 | static int f_rmdir(lua_State *L) { |
561 | const char *path = luaL_checkstring(L, 1); |
562 | |
563 | #ifdef _WIN32 |
564 | LPWSTR wpath = utfconv_utf8towc(path); |
565 | int deleted = RemoveDirectoryW(wpath); |
566 | free(wpath); |
567 | if (deleted > 0) { |
568 | lua_pushboolean(L, 1); |
569 | } else { |
570 | lua_pushboolean(L, 0); |
571 | push_win32_error(L, GetLastError()); |
572 | return 2; |
573 | } |
574 | #else |
575 | int deleted = remove(path); |
576 | if(deleted < 0) { |
577 | lua_pushboolean(L, 0); |
578 | lua_pushstring(L, strerror(errno)); |
579 | |
580 | return 2; |
581 | } else { |
582 | lua_pushboolean(L, 1); |
583 | } |
584 | #endif |
585 | |
586 | return 1; |
587 | } |
588 | |
589 | |
590 | static int f_chdir(lua_State *L) { |
591 | const char *path = luaL_checkstring(L, 1); |
592 | #ifdef _WIN32 |
593 | LPWSTR wpath = utfconv_utf8towc(path); |
594 | if (wpath == NULL) { return luaL_error(L, UTFCONV_ERROR_INVALID_CONVERSION ); } |
595 | int err = _wchdir(wpath); |
596 | free(wpath); |
597 | #else |
598 | int err = chdir(path); |
599 | #endif |
600 | if (err) { luaL_error(L, "chdir() failed: %s" , strerror(errno)); } |
601 | return 0; |
602 | } |
603 | |
604 | |
605 | static int f_list_dir(lua_State *L) { |
606 | const char *path = luaL_checkstring(L, 1); |
607 | |
608 | #ifdef _WIN32 |
609 | lua_settop(L, 1); |
610 | if (path[0] == 0 || strchr("\\/" , path[strlen(path) - 1]) != NULL) |
611 | lua_pushstring(L, "*" ); |
612 | else |
613 | lua_pushstring(L, "/*" ); |
614 | |
615 | lua_concat(L, 2); |
616 | path = lua_tostring(L, -1); |
617 | |
618 | LPWSTR wpath = utfconv_utf8towc(path); |
619 | if (wpath == NULL) { |
620 | lua_pushnil(L); |
621 | lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); |
622 | return 2; |
623 | } |
624 | |
625 | WIN32_FIND_DATAW fd; |
626 | HANDLE find_handle = FindFirstFileExW(wpath, FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0); |
627 | free(wpath); |
628 | if (find_handle == INVALID_HANDLE_VALUE) { |
629 | lua_pushnil(L); |
630 | push_win32_error(L, GetLastError()); |
631 | return 2; |
632 | } |
633 | |
634 | char mbpath[MAX_PATH * 4]; // utf-8 spans 4 bytes at most |
635 | int len, i = 1; |
636 | lua_newtable(L); |
637 | |
638 | do |
639 | { |
640 | if (wcscmp(fd.cFileName, L"." ) == 0) { continue; } |
641 | if (wcscmp(fd.cFileName, L".." ) == 0) { continue; } |
642 | |
643 | len = WideCharToMultiByte(CP_UTF8, 0, fd.cFileName, -1, mbpath, MAX_PATH * 4, NULL, NULL); |
644 | if (len == 0) { break; } |
645 | lua_pushlstring(L, mbpath, len - 1); // len includes \0 |
646 | lua_rawseti(L, -2, i++); |
647 | } while (FindNextFileW(find_handle, &fd)); |
648 | |
649 | if (GetLastError() != ERROR_NO_MORE_FILES) { |
650 | lua_pushnil(L); |
651 | push_win32_error(L, GetLastError()); |
652 | FindClose(find_handle); |
653 | return 2; |
654 | } |
655 | |
656 | FindClose(find_handle); |
657 | return 1; |
658 | #else |
659 | DIR *dir = opendir(path); |
660 | if (!dir) { |
661 | lua_pushnil(L); |
662 | lua_pushstring(L, strerror(errno)); |
663 | return 2; |
664 | } |
665 | |
666 | lua_newtable(L); |
667 | int i = 1; |
668 | struct dirent *entry; |
669 | while ( (entry = readdir(dir)) ) { |
670 | if (strcmp(entry->d_name, "." ) == 0) { continue; } |
671 | if (strcmp(entry->d_name, ".." ) == 0) { continue; } |
672 | lua_pushstring(L, entry->d_name); |
673 | lua_rawseti(L, -2, i); |
674 | i++; |
675 | } |
676 | |
677 | closedir(dir); |
678 | return 1; |
679 | #endif |
680 | } |
681 | |
682 | |
683 | #ifdef _WIN32 |
684 | #define realpath(x, y) _wfullpath(y, x, MAX_PATH) |
685 | #endif |
686 | |
687 | static int f_absolute_path(lua_State *L) { |
688 | const char *path = luaL_checkstring(L, 1); |
689 | #ifdef _WIN32 |
690 | LPWSTR wpath = utfconv_utf8towc(path); |
691 | if (!wpath) { return 0; } |
692 | |
693 | LPWSTR wfullpath = realpath(wpath, NULL); |
694 | free(wpath); |
695 | if (!wfullpath) { return 0; } |
696 | |
697 | char *res = utfconv_wctoutf8(wfullpath); |
698 | free(wfullpath); |
699 | #else |
700 | char *res = realpath(path, NULL); |
701 | #endif |
702 | if (!res) { return 0; } |
703 | lua_pushstring(L, res); |
704 | free(res); |
705 | return 1; |
706 | } |
707 | |
708 | |
709 | static int f_get_file_info(lua_State *L) { |
710 | const char *path = luaL_checkstring(L, 1); |
711 | |
712 | #ifdef _WIN32 |
713 | struct _stat s; |
714 | LPWSTR wpath = utfconv_utf8towc(path); |
715 | if (wpath == NULL) { |
716 | lua_pushnil(L); |
717 | lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); |
718 | return 2; |
719 | } |
720 | int err = _wstat(wpath, &s); |
721 | free(wpath); |
722 | #else |
723 | struct stat s; |
724 | int err = stat(path, &s); |
725 | #endif |
726 | if (err < 0) { |
727 | lua_pushnil(L); |
728 | lua_pushstring(L, strerror(errno)); |
729 | return 2; |
730 | } |
731 | |
732 | lua_newtable(L); |
733 | lua_pushinteger(L, s.st_mtime); |
734 | lua_setfield(L, -2, "modified" ); |
735 | |
736 | lua_pushinteger(L, s.st_size); |
737 | lua_setfield(L, -2, "size" ); |
738 | |
739 | if (S_ISREG(s.st_mode)) { |
740 | lua_pushstring(L, "file" ); |
741 | } else if (S_ISDIR(s.st_mode)) { |
742 | lua_pushstring(L, "dir" ); |
743 | } else { |
744 | lua_pushnil(L); |
745 | } |
746 | lua_setfield(L, -2, "type" ); |
747 | |
748 | #if __linux__ |
749 | if (S_ISDIR(s.st_mode)) { |
750 | if (lstat(path, &s) == 0) { |
751 | lua_pushboolean(L, S_ISLNK(s.st_mode)); |
752 | lua_setfield(L, -2, "symlink" ); |
753 | } |
754 | } |
755 | #endif |
756 | return 1; |
757 | } |
758 | |
759 | #if __linux__ |
760 | // https://man7.org/linux/man-pages/man2/statfs.2.html |
761 | |
762 | struct f_type_names { |
763 | uint32_t magic; |
764 | const char *name; |
765 | }; |
766 | |
767 | static struct f_type_names fs_names[] = { |
768 | { 0xef53, "ext2/ext3" }, |
769 | { 0x6969, "nfs" }, |
770 | { 0x65735546, "fuse" }, |
771 | { 0x517b, "smb" }, |
772 | { 0xfe534d42, "smb2" }, |
773 | { 0x52654973, "reiserfs" }, |
774 | { 0x01021994, "tmpfs" }, |
775 | { 0x858458f6, "ramfs" }, |
776 | { 0x5346544e, "ntfs" }, |
777 | { 0x0, NULL }, |
778 | }; |
779 | |
780 | #endif |
781 | |
782 | static int f_get_fs_type(lua_State *L) { |
783 | #if __linux__ |
784 | const char *path = luaL_checkstring(L, 1); |
785 | struct statfs buf; |
786 | int status = statfs(path, &buf); |
787 | if (status != 0) { |
788 | return luaL_error(L, "error calling statfs on %s" , path); |
789 | } |
790 | for (int i = 0; fs_names[i].magic; i++) { |
791 | if (fs_names[i].magic == buf.f_type) { |
792 | lua_pushstring(L, fs_names[i].name); |
793 | return 1; |
794 | } |
795 | } |
796 | #endif |
797 | lua_pushstring(L, "unknown" ); |
798 | return 1; |
799 | } |
800 | |
801 | |
802 | static int f_mkdir(lua_State *L) { |
803 | const char *path = luaL_checkstring(L, 1); |
804 | |
805 | #ifdef _WIN32 |
806 | LPWSTR wpath = utfconv_utf8towc(path); |
807 | if (wpath == NULL) { |
808 | lua_pushboolean(L, 0); |
809 | lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); |
810 | return 2; |
811 | } |
812 | |
813 | int err = _wmkdir(wpath); |
814 | free(wpath); |
815 | #else |
816 | int err = mkdir(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); |
817 | #endif |
818 | if (err < 0) { |
819 | lua_pushboolean(L, 0); |
820 | lua_pushstring(L, strerror(errno)); |
821 | return 2; |
822 | } |
823 | |
824 | lua_pushboolean(L, 1); |
825 | return 1; |
826 | } |
827 | |
828 | |
829 | static int f_get_clipboard(lua_State *L) { |
830 | char *text = SDL_GetClipboardText(); |
831 | if (!text) { return 0; } |
832 | lua_pushstring(L, text); |
833 | SDL_free(text); |
834 | return 1; |
835 | } |
836 | |
837 | |
838 | static int f_set_clipboard(lua_State *L) { |
839 | const char *text = luaL_checkstring(L, 1); |
840 | SDL_SetClipboardText(text); |
841 | return 0; |
842 | } |
843 | |
844 | |
845 | static int f_get_process_id(lua_State *L) { |
846 | #ifdef _WIN32 |
847 | lua_pushinteger(L, GetCurrentProcessId()); |
848 | #else |
849 | lua_pushinteger(L, getpid()); |
850 | #endif |
851 | return 1; |
852 | } |
853 | |
854 | |
855 | static int f_get_time(lua_State *L) { |
856 | double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency(); |
857 | lua_pushnumber(L, n); |
858 | return 1; |
859 | } |
860 | |
861 | |
862 | static int f_sleep(lua_State *L) { |
863 | double n = luaL_checknumber(L, 1); |
864 | SDL_Delay(n * 1000); |
865 | return 0; |
866 | } |
867 | |
868 | |
869 | static int f_exec(lua_State *L) { |
870 | size_t len; |
871 | const char *cmd = luaL_checklstring(L, 1, &len); |
872 | char *buf = malloc(len + 32); |
873 | if (!buf) { luaL_error(L, "buffer allocation failed" ); } |
874 | #if _WIN32 |
875 | sprintf(buf, "cmd /c \"%s\"" , cmd); |
876 | WinExec(buf, SW_HIDE); |
877 | #else |
878 | sprintf(buf, "%s &" , cmd); |
879 | int res = system(buf); |
880 | (void) res; |
881 | #endif |
882 | free(buf); |
883 | return 0; |
884 | } |
885 | |
886 | static int f_fuzzy_match(lua_State *L) { |
887 | size_t strLen, ptnLen; |
888 | const char *str = luaL_checklstring(L, 1, &strLen); |
889 | const char *ptn = luaL_checklstring(L, 2, &ptnLen); |
890 | // If true match things *backwards*. This allows for better matching on filenames than the above |
891 | // function. For example, in the lite project, opening "renderer" has lib/font_render/build.sh |
892 | // as the first result, rather than src/renderer.c. Clearly that's wrong. |
893 | bool files = lua_gettop(L) > 2 && lua_isboolean(L,3) && lua_toboolean(L, 3); |
894 | int score = 0, run = 0, increment = files ? -1 : 1; |
895 | const char* strTarget = files ? str + strLen - 1 : str; |
896 | const char* ptnTarget = files ? ptn + ptnLen - 1 : ptn; |
897 | while (strTarget >= str && ptnTarget >= ptn && *strTarget && *ptnTarget) { |
898 | while (strTarget >= str && *strTarget == ' ') { strTarget += increment; } |
899 | while (ptnTarget >= ptn && *ptnTarget == ' ') { ptnTarget += increment; } |
900 | if (tolower(*strTarget) == tolower(*ptnTarget)) { |
901 | score += run * 10 - (*strTarget != *ptnTarget); |
902 | run++; |
903 | ptnTarget += increment; |
904 | } else { |
905 | score -= 10; |
906 | run = 0; |
907 | } |
908 | strTarget += increment; |
909 | } |
910 | if (ptnTarget >= ptn && *ptnTarget) { return 0; } |
911 | lua_pushinteger(L, score - (int)strLen * 10); |
912 | return 1; |
913 | } |
914 | |
915 | static int f_set_window_opacity(lua_State *L) { |
916 | double n = luaL_checknumber(L, 1); |
917 | int r = SDL_SetWindowOpacity(window_renderer.window, n); |
918 | lua_pushboolean(L, r > -1); |
919 | return 1; |
920 | } |
921 | |
922 | typedef void (*fptr)(void); |
923 | |
924 | typedef struct lua_function_node { |
925 | const char *symbol; |
926 | fptr address; |
927 | } lua_function_node; |
928 | |
929 | #define P(FUNC) { "lua_" #FUNC, (fptr)(lua_##FUNC) } |
930 | #define U(FUNC) { "luaL_" #FUNC, (fptr)(luaL_##FUNC) } |
931 | #define S(FUNC) { #FUNC, (fptr)(FUNC) } |
932 | static void* api_require(const char* symbol) { |
933 | static const lua_function_node nodes[] = { |
934 | #if LUA_VERSION_NUM == 501 || LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 || LUA_VERSION_NUM == 504 |
935 | U(addlstring), U(addstring), U(addvalue), U(argerror), U(buffinit), |
936 | U(callmeta), U(checkany), U(checkinteger), U(checklstring), |
937 | U(checknumber), U(checkoption), U(checkstack), U(checktype), |
938 | U(checkudata), U(error), U(getmetafield), U(gsub), U(loadstring), |
939 | U(newmetatable), U(newstate), U(openlibs), U(optinteger), U(optlstring), |
940 | U(optnumber), U(pushresult), U(ref), U(unref), U(where), P(atpanic), |
941 | P(checkstack), P(close), P(concat), P(createtable), P(dump), P(error), |
942 | P(gc), P(getallocf), P(getfield), P(gethook), P(gethookcount), |
943 | P(gethookmask), P(getinfo), P(getlocal), P(getmetatable), P(getstack), |
944 | P(gettable), P(gettop), P(getupvalue), P(iscfunction), P(isnumber), |
945 | P(isstring), P(isuserdata), P(load), P(newstate), P(newthread), P(next), |
946 | P(pushboolean), P(pushcclosure), P(pushfstring), P(pushinteger), |
947 | P(pushlightuserdata), P(pushlstring), P(pushnil), P(pushnumber), |
948 | P(pushstring), P(pushthread), P(pushvalue), P(pushvfstring), P(rawequal), |
949 | P(rawget), P(rawgeti), P(rawset), P(rawseti), P(resume), P(setallocf), |
950 | P(setfield), P(sethook), P(setlocal), P(setmetatable), P(settable), |
951 | P(settop), P(setupvalue), P(status), P(toboolean), P(tocfunction), |
952 | P(tolstring), P(topointer), P(tothread), P(touserdata), P(type), |
953 | P(typename), P(xmove), S(luaopen_base), S(luaopen_debug), S(luaopen_io), |
954 | S(luaopen_math), S(luaopen_os), S(luaopen_package), S(luaopen_string), |
955 | S(luaopen_table), S(api_load_libs), |
956 | #endif |
957 | #if LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 || LUA_VERSION_NUM == 504 |
958 | U(buffinitsize), U(checkversion_), U(execresult), U(fileresult), |
959 | U(getsubtable), U(len), U(loadbufferx), U(loadfilex), U(prepbuffsize), |
960 | U(pushresultsize), U(requiref), U(setfuncs), U(setmetatable), |
961 | U(testudata), U(tolstring), U(traceback), P(absindex), P(arith), |
962 | P(callk), P(compare), P(copy), P(getglobal), P(len), P(pcallk), |
963 | P(rawgetp), P(rawlen), P(rawsetp), P(setglobal), P(tointegerx), |
964 | P(tonumberx), P(upvalueid), P(upvaluejoin), P(version), P(yieldk), |
965 | S(luaopen_coroutine), |
966 | #endif |
967 | #if LUA_VERSION_NUM == 501 || LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 |
968 | P(newuserdata), |
969 | #endif |
970 | #if LUA_VERSION_NUM == 503 || LUA_VERSION_NUM == 504 |
971 | P(geti), P(isinteger), P(isyieldable), P(rotate), P(seti), |
972 | P(stringtonumber), S(luaopen_utf8), |
973 | #endif |
974 | #if LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 |
975 | P(getuservalue), P(setuservalue), S(luaopen_bit32), |
976 | #endif |
977 | #if LUA_VERSION_NUM == 501 || LUA_VERSION_NUM == 502 |
978 | P(insert), P(remove), P(replace), |
979 | #endif |
980 | #if LUA_VERSION_NUM == 504 |
981 | U(addgsub), U(typeerror), P(closeslot), P(getiuservalue), |
982 | P(newuserdatauv), P(resetthread), P(setcstacklimit), P(setiuservalue), |
983 | P(setwarnf), P(toclose), P(warning), |
984 | #endif |
985 | #if LUA_VERSION_NUM == 502 |
986 | U(checkunsigned), U(optunsigned), P(getctx), P(pushunsigned), |
987 | P(tounsignedx), |
988 | #endif |
989 | #if LUA_VERSION_NUM == 501 |
990 | U(findtable), U(loadbuffer), U(loadfile), U(openlib), U(prepbuffer), |
991 | U(register), U(typerror), P(call), P(cpcall), P(equal), P(getfenv), |
992 | P(lessthan), P(objlen), P(pcall), P(setfenv), P(setlevel), P(tointeger), |
993 | P(tonumber), P(yield), |
994 | #endif |
995 | }; |
996 | for (size_t i = 0; i < sizeof(nodes) / sizeof(lua_function_node); ++i) { |
997 | if (strcmp(nodes[i].symbol, symbol) == 0) |
998 | return *(void**)(&nodes[i].address); |
999 | } |
1000 | return NULL; |
1001 | } |
1002 | |
1003 | static int f_library_gc(lua_State *L) { |
1004 | lua_getfield(L, 1, "handle" ); |
1005 | void* handle = lua_touserdata(L, -1); |
1006 | SDL_UnloadObject(handle); |
1007 | |
1008 | return 0; |
1009 | } |
1010 | |
1011 | static int f_load_native_plugin(lua_State *L) { |
1012 | char entrypoint_name[512]; entrypoint_name[sizeof(entrypoint_name) - 1] = '\0'; |
1013 | int result; |
1014 | |
1015 | const char *name = luaL_checkstring(L, 1); |
1016 | const char *path = luaL_checkstring(L, 2); |
1017 | void *library = SDL_LoadObject(path); |
1018 | if (!library) |
1019 | return (lua_pushstring(L, SDL_GetError()), lua_error(L)); |
1020 | |
1021 | lua_getglobal(L, "package" ); |
1022 | lua_getfield(L, -1, "native_plugins" ); |
1023 | lua_newtable(L); |
1024 | lua_pushlightuserdata(L, library); |
1025 | lua_setfield(L, -2, "handle" ); |
1026 | luaL_setmetatable(L, API_TYPE_NATIVE_PLUGIN); |
1027 | lua_setfield(L, -2, name); |
1028 | lua_pop(L, 2); |
1029 | |
1030 | const char *basename = strrchr(name, '.'); |
1031 | basename = !basename ? name : basename + 1; |
1032 | snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_lite_xl_%s" , basename); |
1033 | int (*ext_entrypoint) (lua_State *L, void* (*)(const char*)); |
1034 | *(void**)(&ext_entrypoint) = SDL_LoadFunction(library, entrypoint_name); |
1035 | if (!ext_entrypoint) { |
1036 | snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_%s" , basename); |
1037 | int (*entrypoint)(lua_State *L); |
1038 | *(void**)(&entrypoint) = SDL_LoadFunction(library, entrypoint_name); |
1039 | if (!entrypoint) |
1040 | return luaL_error(L, "Unable to load %s: Can't find %s(lua_State *L, void *XL)" , name, entrypoint_name); |
1041 | result = entrypoint(L); |
1042 | } else { |
1043 | result = ext_entrypoint(L, api_require); |
1044 | } |
1045 | |
1046 | if (!result) |
1047 | return luaL_error(L, "Unable to load %s: entrypoint must return a value" , name); |
1048 | |
1049 | return result; |
1050 | } |
1051 | |
1052 | #ifdef _WIN32 |
1053 | #define PATHSEP '\\' |
1054 | #else |
1055 | #define PATHSEP '/' |
1056 | #endif |
1057 | |
1058 | /* Special purpose filepath compare function. Corresponds to the |
1059 | order used in the TreeView view of the project's files. Returns true iff |
1060 | path1 < path2 in the TreeView order. */ |
1061 | static int f_path_compare(lua_State *L) { |
1062 | size_t len1, len2; |
1063 | const char *path1 = luaL_checklstring(L, 1, &len1); |
1064 | const char *type1_s = luaL_checkstring(L, 2); |
1065 | const char *path2 = luaL_checklstring(L, 3, &len2); |
1066 | const char *type2_s = luaL_checkstring(L, 4); |
1067 | int type1 = strcmp(type1_s, "dir" ) != 0; |
1068 | int type2 = strcmp(type2_s, "dir" ) != 0; |
1069 | /* Find the index of the common part of the path. */ |
1070 | size_t offset = 0, i, j; |
1071 | for (i = 0; i < len1 && i < len2; i++) { |
1072 | if (path1[i] != path2[i]) break; |
1073 | if (isdigit(path1[i])) break; |
1074 | if (path1[i] == PATHSEP) { |
1075 | offset = i + 1; |
1076 | } |
1077 | } |
1078 | /* If a path separator is present in the name after the common part we consider |
1079 | the entry like a directory. */ |
1080 | if (strchr(path1 + offset, PATHSEP)) { |
1081 | type1 = 0; |
1082 | } |
1083 | if (strchr(path2 + offset, PATHSEP)) { |
1084 | type2 = 0; |
1085 | } |
1086 | /* If types are different "dir" types comes before "file" types. */ |
1087 | if (type1 != type2) { |
1088 | lua_pushboolean(L, type1 < type2); |
1089 | return 1; |
1090 | } |
1091 | /* If types are the same compare the files' path alphabetically. */ |
1092 | int cfr = -1; |
1093 | bool same_len = len1 == len2; |
1094 | for (i = offset, j = offset; i <= len1 && j <= len2; i++, j++) { |
1095 | if (path1[i] == 0 || path2[j] == 0) { |
1096 | if (cfr < 0) cfr = 0; // The strings are equal |
1097 | if (!same_len) { |
1098 | cfr = (path1[i] == 0); |
1099 | } |
1100 | } else if (isdigit(path1[i]) && isdigit(path2[j])) { |
1101 | size_t ii = 0, ij = 0; |
1102 | while (isdigit(path1[i+ii])) { ii++; } |
1103 | while (isdigit(path2[j+ij])) { ij++; } |
1104 | |
1105 | size_t di = 0, dj = 0; |
1106 | for (size_t ai = 0; ai < ii; ++ai) { |
1107 | di = di * 10 + (path1[i+ai] - '0'); |
1108 | } |
1109 | for (size_t aj = 0; aj < ij; ++aj) { |
1110 | dj = dj * 10 + (path2[j+aj] - '0'); |
1111 | } |
1112 | |
1113 | if (di == dj) { |
1114 | continue; |
1115 | } |
1116 | cfr = (di < dj); |
1117 | } else if (path1[i] == path2[j]) { |
1118 | continue; |
1119 | } else if (path1[i] == PATHSEP || path2[j] == PATHSEP) { |
1120 | /* For comparison we treat PATHSEP as if it was the string terminator. */ |
1121 | cfr = (path1[i] == PATHSEP); |
1122 | } else { |
1123 | char a = path1[i], b = path2[j]; |
1124 | if (a >= 'A' && a <= 'Z') a += 32; |
1125 | if (b >= 'A' && b <= 'Z') b += 32; |
1126 | if (a == b) { |
1127 | /* If the strings have the same length, we need |
1128 | to keep the first case sensitive difference. */ |
1129 | if (same_len && cfr < 0) { |
1130 | /* Give priority to lower-case characters */ |
1131 | cfr = (path1[i] > path2[j]); |
1132 | } |
1133 | continue; |
1134 | } |
1135 | cfr = (a < b); |
1136 | } |
1137 | break; |
1138 | } |
1139 | lua_pushboolean(L, cfr); |
1140 | return 1; |
1141 | } |
1142 | |
1143 | |
1144 | static int f_text_input(lua_State* L) { |
1145 | if (lua_toboolean(L, 1)) |
1146 | SDL_StartTextInput(); |
1147 | else |
1148 | SDL_StopTextInput(); |
1149 | return 0; |
1150 | } |
1151 | |
1152 | |
1153 | static const luaL_Reg lib[] = { |
1154 | { "poll_event" , f_poll_event }, |
1155 | { "wait_event" , f_wait_event }, |
1156 | { "set_cursor" , f_set_cursor }, |
1157 | { "set_window_title" , f_set_window_title }, |
1158 | { "set_window_mode" , f_set_window_mode }, |
1159 | { "get_window_mode" , f_get_window_mode }, |
1160 | { "set_window_bordered" , f_set_window_bordered }, |
1161 | { "set_window_hit_test" , f_set_window_hit_test }, |
1162 | { "get_window_size" , f_get_window_size }, |
1163 | { "set_window_size" , f_set_window_size }, |
1164 | { "set_text_input_rect" , f_set_text_input_rect }, |
1165 | { "clear_ime" , f_clear_ime }, |
1166 | { "window_has_focus" , f_window_has_focus }, |
1167 | { "raise_window" , f_raise_window }, |
1168 | { "show_fatal_error" , f_show_fatal_error }, |
1169 | { "rmdir" , f_rmdir }, |
1170 | { "chdir" , f_chdir }, |
1171 | { "mkdir" , f_mkdir }, |
1172 | { "list_dir" , f_list_dir }, |
1173 | { "absolute_path" , f_absolute_path }, |
1174 | { "get_file_info" , f_get_file_info }, |
1175 | { "get_clipboard" , f_get_clipboard }, |
1176 | { "set_clipboard" , f_set_clipboard }, |
1177 | { "get_process_id" , f_get_process_id }, |
1178 | { "get_time" , f_get_time }, |
1179 | { "sleep" , f_sleep }, |
1180 | { "exec" , f_exec }, |
1181 | { "fuzzy_match" , f_fuzzy_match }, |
1182 | { "set_window_opacity" , f_set_window_opacity }, |
1183 | { "load_native_plugin" , f_load_native_plugin }, |
1184 | { "path_compare" , f_path_compare }, |
1185 | { "get_fs_type" , f_get_fs_type }, |
1186 | { "text_input" , f_text_input }, |
1187 | { NULL, NULL } |
1188 | }; |
1189 | |
1190 | |
1191 | int luaopen_system(lua_State *L) { |
1192 | luaL_newmetatable(L, API_TYPE_NATIVE_PLUGIN); |
1193 | lua_pushcfunction(L, f_library_gc); |
1194 | lua_setfield(L, -2, "__gc" ); |
1195 | luaL_newlib(L, lib); |
1196 | return 1; |
1197 | } |
1198 | |