123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- /*
- * openmodal.h
- * https://gitlab.com/bztsrc/openmodal
- *
- * Copyright (C) 2023 bzt MIT license
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- *
- * @brief Platform native open file modal windows
- *
- */
- #ifndef _OPENMODAL_H_
- #define _OPENMODAL_H_
- #ifdef __cplusplus
- extern "C" {
- #endif
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <limits.h>
- #include <time.h>
- #ifdef __EMSCRIPTEN__
- # include <emscripten.h>
- static uint8_t *openmodal_buf = NULL;
- static int openmodal_len = 0;
- void openmodal_helper(uint8_t *buf, int len) { openmodal_buf = buf; openmodal_len = len; }
- #else
- # ifdef __WIN32__
- # include <windows.h>
- # include <winnls.h>
- # include <fileapi.h>
- # include <shlobj.h>
- # include <shellapi.h>
- typedef int (__cdecl *tGetOpenFileNameW)(LPOPENFILENAMEW Param);
- typedef int (__cdecl *tGetSaveFileNameW)(LPOPENFILENAMEW Param);
- # else
- # ifdef __ANDROID__
- /* TODO */
- # else
- # ifdef __APPLE__
- # include <AppKit/AppKit.h>
- # else
- /* Linux, BSD, other POSIX */
- # include <signal.h> /* including the glib version of this with __USE_MISC doesn't compile... */
- # include <dlfcn.h>
- # include <unistd.h>
- # include <sys/types.h>
- # include <sys/mman.h>
- # ifndef MAP_ANONYMOUS
- # define MAP_ANONYMOUS 0x20
- # endif
- int kill(pid_t pid, int sig);
- FILE *popen(const char *command, const char *type);
- int pclose(FILE *stream);
- /* gtk is sooo fucked up, DO NOT include its headers... */
- void (*gtk_init)(int *argc, char ***argv);
- void* (*gtk_file_chooser_dialog_new)(const char *title, void *parent, int action, const char *first_button_text, ...);
- int (*gtk_dialog_run)(void *dialog);
- void (*gtk_widget_destroy)(void *widget);
- char* (*gtk_file_chooser_get_filename)(void *chooser);
- void (*gtk_file_chooser_set_do_overwrite_confirmation)(void *chooser, int do_overwrite_confirmation);
- void (*gtk_file_chooser_set_create_folders)(void *chooser, int create_folders);
- int (*gtk_file_chooser_set_current_name)(void *chooser, const char *filename);
- # endif
- # endif
- # endif
- #endif
- #ifndef PATH_MAX
- #define PATH_MAX 4096
- #endif
- #ifndef FILENAME_MAX
- #define FILENAME_MAX 255
- #endif
- /**
- * Open open file modal and return selected file's data
- */
- uint8_t *openmodal_load(char *name, int *size)
- {
- uint8_t *data = NULL;
- #ifdef __EMSCRIPTEN__
- struct timespec tv;
- openmodal_buf = NULL; openmodal_len = 0;
- EM_ASM({
- var inp = document.getElementById("openmodal_upload");
- if(inp == undefined) {
- inp = document.createElement("INPUT");
- inp.setAttribute("id", "openmodal_upload");
- inp.setAttribute("type", "file");
- inp.setAttribute("style", "display:none");
- inp.addEventListener("change", function() {
- inp = document.getElementById("openmodal_upload");
- if(inp.files.length>0) {
- inp.setAttribute("data-hasonchange",1);
- var reader = new FileReader();
- reader.onloadend = function() {
- var file = inp.files[0].toString();
- var data = new Uint8Array(reader.result);
- var openmodal = Module.cwrap("openmodal_helper", "number", [ "number", "number" ]);
- if(openmodal != undefined) {
- const buf = Module._malloc(data.length);
- if($0) Module.HEAPU8.set(file, $0);
- Module.HEAPU8.set(data, buf);
- openmodal(buf, data.length);
- }
- };
- reader.readAsArrayBuffer(inp.files[0]);
- }
- });
- /* we can't check for "cancel" button... so we cancel the wait when document regains focus */
- document.body.addEventListener("focus", function() {
- setTimeout(function() {
- if(!inp.hasAttribute("data-hasonchange")) {
- var openmodal = Module.cwrap("openmodal_helper", "number", [ "number", "number" ]);
- if(openmodal != undefined) openmodal(0, -1);
- }
- }, 333);
- }, true);
- document.body.appendChild(inp);
- }
- inp.removeAttribute("data-hasonchange");
- inp.click();
- }, name);
- /* poor man's synchronization */
- while(openmodal_buf == NULL && openmodal_len == 0) {
- tv.tv_sec = 0; tv.tv_nsec = 100000000;
- nanosleep(&tv, NULL);
- }
- data = openmodal_buf; *size = openmodal_len > 0 ? openmodal_len : 0;
- openmodal_buf = NULL; openmodal_len = 0;
- #else
- # ifdef __WIN32__
- tGetOpenFileNameW modal;
- HINSTANCE lib;
- OPENFILENAMEW ofn;
- HANDLE f;
- DWORD r, t;
- wchar_t szFile[PATH_MAX + FILENAME_MAX + 1];
- char fn[PATH_MAX + FILENAME_MAX + 1];
- if((lib = LoadLibraryW(L"comdlg32.dll"))) {
- if((modal = (tGetOpenFileNameW)GetProcAddress(lib, "GetOpenFileNameW"))) {
- memset(&szFile,0,sizeof(szFile));
- memset(&ofn,0,sizeof(ofn));
- ofn.lStructSize = sizeof(ofn);
- ofn.lpstrFile = szFile;
- ofn.nMaxFile = sizeof(szFile)-1;
- ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
- if((modal)(&ofn)) {
- WideCharToMultiByte(CP_UTF8, 0, szFile, -1, fn, PATH_MAX + FILENAME_MAX, NULL, NULL);
- f = CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
- if(f) {
- r = GetFileSize(f, NULL);
- *size = r;
- data = (uint8_t*)malloc(*size);
- if(data) {
- memset(data, 0, *size);
- if(!ReadFile(f, data, r, &t, NULL) || r != t) { free(data); data = NULL; *size = 0; }
- }
- CloseHandle(f);
- }
- if(data && name) strcpy(name, fn);
- }
- }
- FreeLibrary(lib);
- }
- # else
- FILE *f;
- char *fn = NULL;
- # ifdef __ANDROID__
- /* TODO */
- # else
- # ifdef __APPLE__
- const char *utf8Path;
- NSURL *url;
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
- NSOpenPanel *dialog = [NSOpenPanel openPanel];
- [dialog setAllowsMultipleSelection:NO];
- if([dialog runModal] == NSModalResponseOK) {
- url = [dialog URL];
- utf8Path = [[url path] UTF8String];
- len = strlen(utf8Path);
- fn = (char*)malloc(len + 1);
- if(fn) strcpy(ret, utf8Path);
- }
- [pool release];
- [keyWindow makeKeyAndOrderFront:nil];
- # else
- struct timespec tv;
- pid_t pid;
- void *chooser;
- void *handle;
- char *tmp1, *tmp2;
- tmp1 = mmap(NULL, PATH_MAX, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- if(!tmp1) return NULL;
- memset(tmp1, 0, PATH_MAX);
- if(!(pid = fork())) {
- handle = dlopen("libgtk-3.so", RTLD_LAZY | RTLD_LOCAL);
- if(handle) {
- # ifdef __GNUC__
- # pragma GCC diagnostic push
- # pragma GCC diagnostic ignored "-Wpedantic"
- # endif
- if( (gtk_init = dlsym(handle, "gtk_init")) &&
- (gtk_file_chooser_dialog_new = dlsym(handle, "gtk_file_chooser_dialog_new")) &&
- (gtk_dialog_run = dlsym(handle, "gtk_dialog_run")) &&
- (gtk_file_chooser_set_create_folders = dlsym(handle, "gtk_file_chooser_set_create_folders")) &&
- (gtk_file_chooser_set_do_overwrite_confirmation = dlsym(handle, "gtk_file_chooser_set_do_overwrite_confirmation")) &&
- (gtk_file_chooser_set_current_name = dlsym(handle, "gtk_file_chooser_set_current_name")) &&
- (gtk_file_chooser_get_filename = dlsym(handle, "gtk_file_chooser_get_filename")) &&
- (gtk_widget_destroy = dlsym(handle, "gtk_widget_destroy")) ) {
- # ifdef __GNUC__
- # pragma GCC diagnostic pop
- # endif
- (*gtk_init)(NULL, NULL);
- chooser = (*gtk_file_chooser_dialog_new)(NULL, NULL, 0, "gtk-cancel", -6, "gtk-open", -3, NULL);
- if((*gtk_dialog_run)(chooser) == -3) {
- tmp2 = (*gtk_file_chooser_get_filename)(chooser);
- if(tmp2) strncpy(tmp1, tmp2, PATH_MAX - 1);
- }
- (*gtk_widget_destroy)(chooser);
- } else fprintf(stderr, "openmodal: unable to load GTK symbols\n");
- dlclose(handle);
- } else fprintf(stderr, "openmodal: unable to run-time link the GTK3 library\n");
- tmp1[0] = 1;
- /* this will actually hang if gtk_init was called... */
- exit(0);
- }
- if(pid < 0) fprintf(stderr, "openmodal: unable to fork\n");
- else {
- /* waitpid(pid, &ret, 0); */
- while(!tmp1[0] && !kill(pid, SIGCONT)) {
- tv.tv_sec = 0; tv.tv_nsec = 100000000;
- nanosleep(&tv, NULL);
- }
- kill(pid, SIGKILL);
- }
- if(tmp1[1]) { fn = (char*)malloc(strlen(tmp1 + 1) + 1); if(fn) strcpy(fn, tmp1 + 1); }
- munmap(tmp1, PATH_MAX);
- # endif
- # endif
- if(fn) {
- if((f = fopen(fn, "rb"))) {
- fseek(f, 0L, SEEK_END);
- *size = (int)ftell(f);
- fseek(f, 0L, SEEK_SET);
- data = (uint8_t*)malloc(*size);
- if(data) {
- memset(data, 0, *size);
- if((int)fread(data, 1, *size, f) != *size) { free(data); data = NULL; *size = 0; }
- }
- fclose(f);
- if(data && name) strcpy(name, fn);
- }
- free(fn);
- }
- # endif
- #endif
- return data;
- }
- /**
- * Open save file modal and write buffer to selected file
- */
- int openmodal_save(char *name, uint8_t *buf, int size)
- {
- #ifdef __EMSCRIPTEN__
- if(name && buf && size > 0) {
- EM_ASM({
- const fview = new Uint8Array(Module.HEAPU8.buffer,$0,$1);
- const bview = new Uint8Array(Module.HEAPU8.buffer,$2,$3);
- var name = new TextDecoder("utf-8").decode(fview);
- var blob = new Blob([bview], { type: "application/octet-stream" });
- var url = window.URL.createObjectURL(blob);
- var a = document.getElementById('openmodal_download');
- if(a == undefined) {
- a = document.createElement("A");
- a.setAttribute("id", "openmodal_download");
- a.setAttribute("style", "display:none");
- a.setAttribute("download", "");
- document.body.appendChild(a);
- }
- name = prompt("Save As", name);
- if(name) {
- Module.HEAPU8.set(new TextEncoder("utf-8").encode(name), $0);
- a.setAttribute('href',url);
- a.setAttribute('download',name);
- a.click();
- }
- window.URL.revokeObjectURL(url);
- }, name, strlen(name), buf, size);
- return 1;
- }
- return 0;
- #else
- int ret = 0, l = 0;
- # ifdef __WIN32__
- tGetSaveFileNameW modal;
- HINSTANCE lib;
- OPENFILENAMEW ofn;
- HANDLE f;
- DWORD w = size, t;
- wchar_t szFile[PATH_MAX + FILENAME_MAX + 1], szExt[FILENAME_MAX];
- char fn[PATH_MAX + FILENAME_MAX + 1];
- if(!buf || size < 1) return 0;
- if((lib = LoadLibraryW(L"comdlg32.dll"))) {
- if((modal = (tGetOpenFileNameW)GetProcAddress(lib, "GetOpenFileNameW"))) {
- memset(&szFile,0,sizeof(szFile));
- memset(&szExt,0,sizeof(szExt));
- memcpy(szExt, L"All\0*.*\0", 18);
- if(name && *name) {
- l = MultiByteToWideChar(CP_UTF8, 0, name, -1, szFile, sizeof(szFile)-1);
- while(l > 0 && szFile[l - 1] != L'.') l--;
- if(l > 0) wsprintfW(szExt, L"%s\0*.%s\0", szFile + l, szFile + l);
- }
- memset(&ofn,0,sizeof(ofn));
- ofn.lStructSize = sizeof(ofn);
- ofn.lpstrFilter = szExt;
- ofn.lpstrFile = szFile;
- ofn.nMaxFile = sizeof(szFile)-1;
- ofn.lpstrDefExt = l > 0 ? &szFile[l] : NULL;
- ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
- if((modal)(&ofn)) {
- WideCharToMultiByte(CP_UTF8, 0, szFile, -1, fn, PATH_MAX + FILENAME_MAX, NULL, NULL);
- f = CreateFileW(szFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- if(f) { ret = (WriteFile(f, buf, w, &t, NULL) && w == t); CloseHandle(f); }
- if(ret && name) strcpy(name, fn);
- }
- }
- FreeLibrary(lib);
- }
- # else
- char *fn = NULL;
- FILE *f;
- # ifdef __ANDROID__
- /* TODO */
- # else
- # ifdef _MACOS_
- const char *utf8Path;
- NSURL *url;
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
- NSSavePanel *dialog = [NSSavePanel savePanel];
- [dialog setExtensionHidden:NO];
- if(buf && size > 0 && [dialog runModal] == NSModalResponseOK) {
- url = [dialog URL];
- utf8Path = [[url path] UTF8String];
- l = strlen(utf8Path);
- fn = (char*)malloc(l + 1);
- if(fn) strcpy(ret, utf8Path);
- }
- [pool release];
- [keyWindow makeKeyAndOrderFront:nil];
- # else
- struct timespec tv;
- pid_t pid;
- void *chooser;
- void *handle;
- char *tmp1, *tmp2;
- if(!buf || size < 1) return 0;
- tmp1 = mmap(NULL, PATH_MAX, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- if(!tmp1) return 0;
- memset(tmp1, 0, PATH_MAX);
- if(!(pid = fork())) {
- handle = dlopen("libgtk-3.so", RTLD_LAZY | RTLD_LOCAL);
- if(handle) {
- # ifdef __GNUC__
- # pragma GCC diagnostic push
- # pragma GCC diagnostic ignored "-Wpedantic"
- # endif
- if( (gtk_init = dlsym(handle, "gtk_init")) &&
- (gtk_file_chooser_dialog_new = dlsym(handle, "gtk_file_chooser_dialog_new")) &&
- (gtk_dialog_run = dlsym(handle, "gtk_dialog_run")) &&
- (gtk_file_chooser_set_create_folders = dlsym(handle, "gtk_file_chooser_set_create_folders")) &&
- (gtk_file_chooser_set_do_overwrite_confirmation = dlsym(handle, "gtk_file_chooser_set_do_overwrite_confirmation")) &&
- (gtk_file_chooser_set_current_name = dlsym(handle, "gtk_file_chooser_set_current_name")) &&
- (gtk_file_chooser_get_filename = dlsym(handle, "gtk_file_chooser_get_filename")) &&
- (gtk_widget_destroy = dlsym(handle, "gtk_widget_destroy")) ) {
- # ifdef __GNUC__
- # pragma GCC diagnostic pop
- # endif
- (*gtk_init)(NULL, NULL);
- chooser = (*gtk_file_chooser_dialog_new)(NULL, NULL, 1, "gtk-cancel", -6, "gtk-save", -3, NULL);
- (*gtk_file_chooser_set_create_folders)(chooser, 1);
- (*gtk_file_chooser_set_do_overwrite_confirmation)(chooser, 1);
- if(name && *name) (*gtk_file_chooser_set_current_name)(chooser, name);
- if((*gtk_dialog_run)(chooser) == -3) {
- tmp2 = (*gtk_file_chooser_get_filename)(chooser);
- if(tmp2) strncpy(tmp1, tmp2, PATH_MAX - 1);
- }
- (*gtk_widget_destroy)(chooser);
- } else fprintf(stderr, "openmodal: unable to load GTK symbols\n");
- dlclose(handle);
- } else fprintf(stderr, "openmodal: unable to run-time link the GTK3 library\n");
- tmp1[0] = 1;
- /* this will actually hang if gtk_init was called... */
- exit(0);
- }
- if(pid < 0) fprintf(stderr, "openmodal: unable to fork\n");
- else {
- /* waitpid(pid, &ret, 0); */
- while(!tmp1[0] && !kill(pid, SIGCONT)) {
- tv.tv_sec = 0; tv.tv_nsec = 100000000;
- nanosleep(&tv, NULL);
- }
- kill(pid, SIGKILL);
- }
- if(tmp1[1]) { fn = (char*)malloc(strlen(tmp1 + 1) + 1); if(fn) strcpy(fn, tmp1 + 1); }
- munmap(tmp1, PATH_MAX);
- # endif
- # endif
- if(fn) {
- if(f = fopen(fn, "wb")) { ret = ((int)fwrite(buf, 1, size, f) == size); fclose(f); }
- if(ret && name) strcpy(name, fn);
- free(fn);
- }
- # endif
- return ret;
- #endif
- }
- #ifdef __cplusplus
- }
- #endif
- #endif
|