openmodal.h 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. /*
  2. * openmodal.h
  3. * https://gitlab.com/bztsrc/openmodal
  4. *
  5. * Copyright (C) 2023 bzt MIT license
  6. *
  7. * Permission is hereby granted, free of charge, to any person
  8. * obtaining a copy of this software and associated documentation
  9. * files (the "Software"), to deal in the Software without
  10. * restriction, including without limitation the rights to use, copy,
  11. * modify, merge, publish, distribute, sublicense, and/or sell copies
  12. * of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be
  16. * included in all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  19. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  20. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  21. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  22. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  23. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  25. * DEALINGS IN THE SOFTWARE.
  26. *
  27. * @brief Platform native open file modal windows
  28. *
  29. */
  30. #ifndef _OPENMODAL_H_
  31. #define _OPENMODAL_H_
  32. #ifdef __cplusplus
  33. extern "C" {
  34. #endif
  35. #include <stdint.h>
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <string.h>
  39. #include <limits.h>
  40. #include <time.h>
  41. #ifdef __EMSCRIPTEN__
  42. # include <emscripten.h>
  43. static uint8_t *openmodal_buf = NULL;
  44. static int openmodal_len = 0;
  45. void openmodal_helper(uint8_t *buf, int len) { openmodal_buf = buf; openmodal_len = len; }
  46. #else
  47. # ifdef __WIN32__
  48. # include <windows.h>
  49. # include <winnls.h>
  50. # include <fileapi.h>
  51. # include <shlobj.h>
  52. # include <shellapi.h>
  53. typedef int (__cdecl *tGetOpenFileNameW)(LPOPENFILENAMEW Param);
  54. typedef int (__cdecl *tGetSaveFileNameW)(LPOPENFILENAMEW Param);
  55. # else
  56. # ifdef __ANDROID__
  57. /* TODO */
  58. # else
  59. # ifdef __APPLE__
  60. # include <AppKit/AppKit.h>
  61. # else
  62. /* Linux, BSD, other POSIX */
  63. # include <signal.h> /* including the glib version of this with __USE_MISC doesn't compile... */
  64. # include <dlfcn.h>
  65. # include <unistd.h>
  66. # include <sys/types.h>
  67. # include <sys/mman.h>
  68. # ifndef MAP_ANONYMOUS
  69. # define MAP_ANONYMOUS 0x20
  70. # endif
  71. int kill(pid_t pid, int sig);
  72. FILE *popen(const char *command, const char *type);
  73. int pclose(FILE *stream);
  74. /* gtk is sooo fucked up, DO NOT include its headers... */
  75. void (*gtk_init)(int *argc, char ***argv);
  76. void* (*gtk_file_chooser_dialog_new)(const char *title, void *parent, int action, const char *first_button_text, ...);
  77. int (*gtk_dialog_run)(void *dialog);
  78. void (*gtk_widget_destroy)(void *widget);
  79. char* (*gtk_file_chooser_get_filename)(void *chooser);
  80. void (*gtk_file_chooser_set_do_overwrite_confirmation)(void *chooser, int do_overwrite_confirmation);
  81. void (*gtk_file_chooser_set_create_folders)(void *chooser, int create_folders);
  82. int (*gtk_file_chooser_set_current_name)(void *chooser, const char *filename);
  83. # endif
  84. # endif
  85. # endif
  86. #endif
  87. #ifndef PATH_MAX
  88. #define PATH_MAX 4096
  89. #endif
  90. #ifndef FILENAME_MAX
  91. #define FILENAME_MAX 255
  92. #endif
  93. /**
  94. * Open open file modal and return selected file's data
  95. */
  96. uint8_t *openmodal_load(char *name, int *size)
  97. {
  98. uint8_t *data = NULL;
  99. #ifdef __EMSCRIPTEN__
  100. struct timespec tv;
  101. openmodal_buf = NULL; openmodal_len = 0;
  102. EM_ASM({
  103. var inp = document.getElementById("openmodal_upload");
  104. if(inp == undefined) {
  105. inp = document.createElement("INPUT");
  106. inp.setAttribute("id", "openmodal_upload");
  107. inp.setAttribute("type", "file");
  108. inp.setAttribute("style", "display:none");
  109. inp.addEventListener("change", function() {
  110. inp = document.getElementById("openmodal_upload");
  111. if(inp.files.length>0) {
  112. inp.setAttribute("data-hasonchange",1);
  113. var reader = new FileReader();
  114. reader.onloadend = function() {
  115. var file = inp.files[0].toString();
  116. var data = new Uint8Array(reader.result);
  117. var openmodal = Module.cwrap("openmodal_helper", "number", [ "number", "number" ]);
  118. if(openmodal != undefined) {
  119. const buf = Module._malloc(data.length);
  120. if($0) Module.HEAPU8.set(file, $0);
  121. Module.HEAPU8.set(data, buf);
  122. openmodal(buf, data.length);
  123. }
  124. };
  125. reader.readAsArrayBuffer(inp.files[0]);
  126. }
  127. });
  128. /* we can't check for "cancel" button... so we cancel the wait when document regains focus */
  129. document.body.addEventListener("focus", function() {
  130. setTimeout(function() {
  131. if(!inp.hasAttribute("data-hasonchange")) {
  132. var openmodal = Module.cwrap("openmodal_helper", "number", [ "number", "number" ]);
  133. if(openmodal != undefined) openmodal(0, -1);
  134. }
  135. }, 333);
  136. }, true);
  137. document.body.appendChild(inp);
  138. }
  139. inp.removeAttribute("data-hasonchange");
  140. inp.click();
  141. }, name);
  142. /* poor man's synchronization */
  143. while(openmodal_buf == NULL && openmodal_len == 0) {
  144. tv.tv_sec = 0; tv.tv_nsec = 100000000;
  145. nanosleep(&tv, NULL);
  146. }
  147. data = openmodal_buf; *size = openmodal_len > 0 ? openmodal_len : 0;
  148. openmodal_buf = NULL; openmodal_len = 0;
  149. #else
  150. # ifdef __WIN32__
  151. tGetOpenFileNameW modal;
  152. HINSTANCE lib;
  153. OPENFILENAMEW ofn;
  154. HANDLE f;
  155. DWORD r, t;
  156. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1];
  157. char fn[PATH_MAX + FILENAME_MAX + 1];
  158. if((lib = LoadLibraryW(L"comdlg32.dll"))) {
  159. if((modal = (tGetOpenFileNameW)GetProcAddress(lib, "GetOpenFileNameW"))) {
  160. memset(&szFile,0,sizeof(szFile));
  161. memset(&ofn,0,sizeof(ofn));
  162. ofn.lStructSize = sizeof(ofn);
  163. ofn.lpstrFile = szFile;
  164. ofn.nMaxFile = sizeof(szFile)-1;
  165. ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
  166. if((modal)(&ofn)) {
  167. WideCharToMultiByte(CP_UTF8, 0, szFile, -1, fn, PATH_MAX + FILENAME_MAX, NULL, NULL);
  168. f = CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  169. if(f) {
  170. r = GetFileSize(f, NULL);
  171. *size = r;
  172. data = (uint8_t*)malloc(*size);
  173. if(data) {
  174. memset(data, 0, *size);
  175. if(!ReadFile(f, data, r, &t, NULL) || r != t) { free(data); data = NULL; *size = 0; }
  176. }
  177. CloseHandle(f);
  178. }
  179. if(data && name) strcpy(name, fn);
  180. }
  181. }
  182. FreeLibrary(lib);
  183. }
  184. # else
  185. FILE *f;
  186. char *fn = NULL;
  187. # ifdef __ANDROID__
  188. /* TODO */
  189. # else
  190. # ifdef __APPLE__
  191. const char *utf8Path;
  192. NSURL *url;
  193. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  194. NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
  195. NSOpenPanel *dialog = [NSOpenPanel openPanel];
  196. [dialog setAllowsMultipleSelection:NO];
  197. if([dialog runModal] == NSModalResponseOK) {
  198. url = [dialog URL];
  199. utf8Path = [[url path] UTF8String];
  200. len = strlen(utf8Path);
  201. fn = (char*)malloc(len + 1);
  202. if(fn) strcpy(ret, utf8Path);
  203. }
  204. [pool release];
  205. [keyWindow makeKeyAndOrderFront:nil];
  206. # else
  207. struct timespec tv;
  208. pid_t pid;
  209. void *chooser;
  210. void *handle;
  211. char *tmp1, *tmp2;
  212. tmp1 = mmap(NULL, PATH_MAX, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  213. if(!tmp1) return NULL;
  214. memset(tmp1, 0, PATH_MAX);
  215. if(!(pid = fork())) {
  216. handle = dlopen("libgtk-3.so", RTLD_LAZY | RTLD_LOCAL);
  217. if(handle) {
  218. # ifdef __GNUC__
  219. # pragma GCC diagnostic push
  220. # pragma GCC diagnostic ignored "-Wpedantic"
  221. # endif
  222. if( (gtk_init = dlsym(handle, "gtk_init")) &&
  223. (gtk_file_chooser_dialog_new = dlsym(handle, "gtk_file_chooser_dialog_new")) &&
  224. (gtk_dialog_run = dlsym(handle, "gtk_dialog_run")) &&
  225. (gtk_file_chooser_set_create_folders = dlsym(handle, "gtk_file_chooser_set_create_folders")) &&
  226. (gtk_file_chooser_set_do_overwrite_confirmation = dlsym(handle, "gtk_file_chooser_set_do_overwrite_confirmation")) &&
  227. (gtk_file_chooser_set_current_name = dlsym(handle, "gtk_file_chooser_set_current_name")) &&
  228. (gtk_file_chooser_get_filename = dlsym(handle, "gtk_file_chooser_get_filename")) &&
  229. (gtk_widget_destroy = dlsym(handle, "gtk_widget_destroy")) ) {
  230. # ifdef __GNUC__
  231. # pragma GCC diagnostic pop
  232. # endif
  233. (*gtk_init)(NULL, NULL);
  234. chooser = (*gtk_file_chooser_dialog_new)(NULL, NULL, 0, "gtk-cancel", -6, "gtk-open", -3, NULL);
  235. if((*gtk_dialog_run)(chooser) == -3) {
  236. tmp2 = (*gtk_file_chooser_get_filename)(chooser);
  237. if(tmp2) strncpy(tmp1, tmp2, PATH_MAX - 1);
  238. }
  239. (*gtk_widget_destroy)(chooser);
  240. } else fprintf(stderr, "openmodal: unable to load GTK symbols\n");
  241. dlclose(handle);
  242. } else fprintf(stderr, "openmodal: unable to run-time link the GTK3 library\n");
  243. tmp1[0] = 1;
  244. /* this will actually hang if gtk_init was called... */
  245. exit(0);
  246. }
  247. if(pid < 0) fprintf(stderr, "openmodal: unable to fork\n");
  248. else {
  249. /* waitpid(pid, &ret, 0); */
  250. while(!tmp1[0] && !kill(pid, SIGCONT)) {
  251. tv.tv_sec = 0; tv.tv_nsec = 100000000;
  252. nanosleep(&tv, NULL);
  253. }
  254. kill(pid, SIGKILL);
  255. }
  256. if(tmp1[1]) { fn = (char*)malloc(strlen(tmp1 + 1) + 1); if(fn) strcpy(fn, tmp1 + 1); }
  257. munmap(tmp1, PATH_MAX);
  258. # endif
  259. # endif
  260. if(fn) {
  261. if((f = fopen(fn, "rb"))) {
  262. fseek(f, 0L, SEEK_END);
  263. *size = (int)ftell(f);
  264. fseek(f, 0L, SEEK_SET);
  265. data = (uint8_t*)malloc(*size);
  266. if(data) {
  267. memset(data, 0, *size);
  268. if((int)fread(data, 1, *size, f) != *size) { free(data); data = NULL; *size = 0; }
  269. }
  270. fclose(f);
  271. if(data && name) strcpy(name, fn);
  272. }
  273. free(fn);
  274. }
  275. # endif
  276. #endif
  277. return data;
  278. }
  279. /**
  280. * Open save file modal and write buffer to selected file
  281. */
  282. int openmodal_save(char *name, uint8_t *buf, int size)
  283. {
  284. #ifdef __EMSCRIPTEN__
  285. if(name && buf && size > 0) {
  286. EM_ASM({
  287. const fview = new Uint8Array(Module.HEAPU8.buffer,$0,$1);
  288. const bview = new Uint8Array(Module.HEAPU8.buffer,$2,$3);
  289. var name = new TextDecoder("utf-8").decode(fview);
  290. var blob = new Blob([bview], { type: "application/octet-stream" });
  291. var url = window.URL.createObjectURL(blob);
  292. var a = document.getElementById('openmodal_download');
  293. if(a == undefined) {
  294. a = document.createElement("A");
  295. a.setAttribute("id", "openmodal_download");
  296. a.setAttribute("style", "display:none");
  297. a.setAttribute("download", "");
  298. document.body.appendChild(a);
  299. }
  300. name = prompt("Save As", name);
  301. if(name) {
  302. Module.HEAPU8.set(new TextEncoder("utf-8").encode(name), $0);
  303. a.setAttribute('href',url);
  304. a.setAttribute('download',name);
  305. a.click();
  306. }
  307. window.URL.revokeObjectURL(url);
  308. }, name, strlen(name), buf, size);
  309. return 1;
  310. }
  311. return 0;
  312. #else
  313. int ret = 0, l = 0;
  314. # ifdef __WIN32__
  315. tGetSaveFileNameW modal;
  316. HINSTANCE lib;
  317. OPENFILENAMEW ofn;
  318. HANDLE f;
  319. DWORD w = size, t;
  320. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1], szExt[FILENAME_MAX];
  321. char fn[PATH_MAX + FILENAME_MAX + 1];
  322. if(!buf || size < 1) return 0;
  323. if((lib = LoadLibraryW(L"comdlg32.dll"))) {
  324. if((modal = (tGetOpenFileNameW)GetProcAddress(lib, "GetOpenFileNameW"))) {
  325. memset(&szFile,0,sizeof(szFile));
  326. memset(&szExt,0,sizeof(szExt));
  327. memcpy(szExt, L"All\0*.*\0", 18);
  328. if(name && *name) {
  329. l = MultiByteToWideChar(CP_UTF8, 0, name, -1, szFile, sizeof(szFile)-1);
  330. while(l > 0 && szFile[l - 1] != L'.') l--;
  331. if(l > 0) wsprintfW(szExt, L"%s\0*.%s\0", szFile + l, szFile + l);
  332. }
  333. memset(&ofn,0,sizeof(ofn));
  334. ofn.lStructSize = sizeof(ofn);
  335. ofn.lpstrFilter = szExt;
  336. ofn.lpstrFile = szFile;
  337. ofn.nMaxFile = sizeof(szFile)-1;
  338. ofn.lpstrDefExt = l > 0 ? &szFile[l] : NULL;
  339. ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
  340. if((modal)(&ofn)) {
  341. WideCharToMultiByte(CP_UTF8, 0, szFile, -1, fn, PATH_MAX + FILENAME_MAX, NULL, NULL);
  342. f = CreateFileW(szFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  343. if(f) { ret = (WriteFile(f, buf, w, &t, NULL) && w == t); CloseHandle(f); }
  344. if(ret && name) strcpy(name, fn);
  345. }
  346. }
  347. FreeLibrary(lib);
  348. }
  349. # else
  350. char *fn = NULL;
  351. FILE *f;
  352. # ifdef __ANDROID__
  353. /* TODO */
  354. # else
  355. # ifdef _MACOS_
  356. const char *utf8Path;
  357. NSURL *url;
  358. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  359. NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
  360. NSSavePanel *dialog = [NSSavePanel savePanel];
  361. [dialog setExtensionHidden:NO];
  362. if(buf && size > 0 && [dialog runModal] == NSModalResponseOK) {
  363. url = [dialog URL];
  364. utf8Path = [[url path] UTF8String];
  365. l = strlen(utf8Path);
  366. fn = (char*)malloc(l + 1);
  367. if(fn) strcpy(ret, utf8Path);
  368. }
  369. [pool release];
  370. [keyWindow makeKeyAndOrderFront:nil];
  371. # else
  372. struct timespec tv;
  373. pid_t pid;
  374. void *chooser;
  375. void *handle;
  376. char *tmp1, *tmp2;
  377. if(!buf || size < 1) return 0;
  378. tmp1 = mmap(NULL, PATH_MAX, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  379. if(!tmp1) return 0;
  380. memset(tmp1, 0, PATH_MAX);
  381. if(!(pid = fork())) {
  382. handle = dlopen("libgtk-3.so", RTLD_LAZY | RTLD_LOCAL);
  383. if(handle) {
  384. # ifdef __GNUC__
  385. # pragma GCC diagnostic push
  386. # pragma GCC diagnostic ignored "-Wpedantic"
  387. # endif
  388. if( (gtk_init = dlsym(handle, "gtk_init")) &&
  389. (gtk_file_chooser_dialog_new = dlsym(handle, "gtk_file_chooser_dialog_new")) &&
  390. (gtk_dialog_run = dlsym(handle, "gtk_dialog_run")) &&
  391. (gtk_file_chooser_set_create_folders = dlsym(handle, "gtk_file_chooser_set_create_folders")) &&
  392. (gtk_file_chooser_set_do_overwrite_confirmation = dlsym(handle, "gtk_file_chooser_set_do_overwrite_confirmation")) &&
  393. (gtk_file_chooser_set_current_name = dlsym(handle, "gtk_file_chooser_set_current_name")) &&
  394. (gtk_file_chooser_get_filename = dlsym(handle, "gtk_file_chooser_get_filename")) &&
  395. (gtk_widget_destroy = dlsym(handle, "gtk_widget_destroy")) ) {
  396. # ifdef __GNUC__
  397. # pragma GCC diagnostic pop
  398. # endif
  399. (*gtk_init)(NULL, NULL);
  400. chooser = (*gtk_file_chooser_dialog_new)(NULL, NULL, 1, "gtk-cancel", -6, "gtk-save", -3, NULL);
  401. (*gtk_file_chooser_set_create_folders)(chooser, 1);
  402. (*gtk_file_chooser_set_do_overwrite_confirmation)(chooser, 1);
  403. if(name && *name) (*gtk_file_chooser_set_current_name)(chooser, name);
  404. if((*gtk_dialog_run)(chooser) == -3) {
  405. tmp2 = (*gtk_file_chooser_get_filename)(chooser);
  406. if(tmp2) strncpy(tmp1, tmp2, PATH_MAX - 1);
  407. }
  408. (*gtk_widget_destroy)(chooser);
  409. } else fprintf(stderr, "openmodal: unable to load GTK symbols\n");
  410. dlclose(handle);
  411. } else fprintf(stderr, "openmodal: unable to run-time link the GTK3 library\n");
  412. tmp1[0] = 1;
  413. /* this will actually hang if gtk_init was called... */
  414. exit(0);
  415. }
  416. if(pid < 0) fprintf(stderr, "openmodal: unable to fork\n");
  417. else {
  418. /* waitpid(pid, &ret, 0); */
  419. while(!tmp1[0] && !kill(pid, SIGCONT)) {
  420. tv.tv_sec = 0; tv.tv_nsec = 100000000;
  421. nanosleep(&tv, NULL);
  422. }
  423. kill(pid, SIGKILL);
  424. }
  425. if(tmp1[1]) { fn = (char*)malloc(strlen(tmp1 + 1) + 1); if(fn) strcpy(fn, tmp1 + 1); }
  426. munmap(tmp1, PATH_MAX);
  427. # endif
  428. # endif
  429. if(fn) {
  430. if(f = fopen(fn, "wb")) { ret = ((int)fwrite(buf, 1, size, f) == size); fclose(f); }
  431. if(ret && name) strcpy(name, fn);
  432. free(fn);
  433. }
  434. # endif
  435. return ret;
  436. #endif
  437. }
  438. #ifdef __cplusplus
  439. }
  440. #endif
  441. #endif