diff --git a/.ci_scripts/setup_environment.sh b/.ci_scripts/setup_environment.sh index ecc2299c2e..c086f08e52 100755 --- a/.ci_scripts/setup_environment.sh +++ b/.ci_scripts/setup_environment.sh @@ -10,7 +10,7 @@ case "$BUILD_TARGET" in "switch") # You can obtain a recent devkitA64 image from https://hub.docker.com/repository/docker/devkitpro/devkita64/general # As for Vita above, make sure that it compiles correctly and runs on a Switch prior to pushing the change - docker run -d --name switchdev --workdir /build/git -v "${PWD}:/build/git" devkitpro/devkita64:20231108 tail -f /dev/null + docker run -d --name switchdev --workdir /build/git -v "${PWD}:/build/git" devkitpro/devkita64:20240324 tail -f /dev/null ;; "android") # Decrypt the key files diff --git a/CMakeLists.txt b/CMakeLists.txt index 86d1ff9aff..e9a336e02c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,6 +202,7 @@ set(PLATFORM_FILES ${PROJECT_SOURCE_DIR}/src/platform/screen.c ${PROJECT_SOURCE_DIR}/src/platform/sound_device.c ${PROJECT_SOURCE_DIR}/src/platform/touch.c + ${PROJECT_SOURCE_DIR}/src/platform/user_path.c ${PROJECT_SOURCE_DIR}/src/platform/version.c ${PROJECT_SOURCE_DIR}/src/platform/virtual_keyboard.c ) @@ -654,6 +655,7 @@ set(WINDOW_FILES ${PROJECT_SOURCE_DIR}/src/window/text_input.c ${PROJECT_SOURCE_DIR}/src/window/trade_opened.c ${PROJECT_SOURCE_DIR}/src/window/trade_prices.c + ${PROJECT_SOURCE_DIR}/src/window/user_path_setup.c ${PROJECT_SOURCE_DIR}/src/window/victory_dialog.c ${PROJECT_SOURCE_DIR}/src/window/video.c ) diff --git a/ext/tinyfiledialogs/tinyfiledialogs.c b/ext/tinyfiledialogs/tinyfiledialogs.c index 48d49fd1bf..d7a79ea370 100644 --- a/ext/tinyfiledialogs/tinyfiledialogs.c +++ b/ext/tinyfiledialogs/tinyfiledialogs.c @@ -1,26 +1,42 @@ -/* -Note: This file is a heavily stripped-down version of Tinyfiledialogs, customized for Julius. - -If you are interested in Tinyfiledialogs, please download its original version from the links below. - -Tinyfiledialogs: -Copyright (c) 2014 - 2018 Guillaume Vareille http://ysengrin.com -http://tinyfiledialogs.sourceforge.net - -Thanks for contributions, bug corrections & thorough testing to: -- Don Heyse http://ldglite.sf.net for bug corrections & thorough testing! -- Paul Rouget +/* SPDX-License-Identifier: Zlib +Copyright (c) 2014 - 2024 Guillaume Vareille http://ysengrin.com + ________________________________________________________________ + | | + | 100% compatible C C++ -> You can rename this .c file as .cpp | + |________________________________________________________________| + +********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE ********* + _________ + / \ tinyfiledialogs.c v3.18.1 [May 2, 2024] zlib licence + |tiny file| Unique code file created [November 9, 2014] + | dialogs | + \____ ___/ http://tinyfiledialogs.sourceforge.net + \| git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd + ____________________________________________ + | | + | email: tinyfiledialogs at ysengrin.com | + |____________________________________________| + _________________________________________________________________________________ + | | + | the windows only wchar_t UTF-16 prototypes are at the bottom of the header file | + |_________________________________________________________________________________| + _________________________________________________________ + | | + | on windows: - since v3.6 char is UTF-8 by default | + | - if you want MBCS set tinyfd_winUtf8 to 0 | + | - functions like fopen expect MBCS | + |_________________________________________________________| + +If you like tinyfiledialogs, please upvote my stackoverflow answer +https://stackoverflow.com/a/47651444 - License - - This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. - Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be @@ -28,10 +44,32 @@ appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. + + __________________________________________ + | ______________________________________ | + | | | | + | | DO NOT USE USER INPUT IN THE DIALOGS | | + | |______________________________________| | + |__________________________________________| */ -#ifndef __sun -#define _POSIX_C_SOURCE 2 /* to accept POSIX 2 in old ANSI C standards */ +#include "tinyfiledialogs.h" + +#ifdef USE_TINYFILEDIALOGS + +#if defined(__GNUC__) || defined(__clang__) +#ifndef _GNU_SOURCE + #define _GNU_SOURCE /* used only to resolve symbolic links. Can be commented out */ + #ifndef _POSIX_C_SOURCE + #ifdef __FreeBSD__ + #define _POSIX_C_SOURCE 199506L /* 199506L is enough for freebsd for realpath() */ + #elif defined(__illumos__) || defined(__solaris__) + #define _POSIX_C_SOURCE 200112L /* illumos/solaris needs 200112L for realpath() */ + #else + #define _POSIX_C_SOURCE 2 /* to accept POSIX 2 in old ANSI C standards */ + #endif + #endif +#endif #endif #include @@ -40,38 +78,87 @@ misrepresented as being the original software. #include #include -#include "tinyfiledialogs.h" - -#ifdef USE_TINYFILEDIALOGS - #ifdef _WIN32 -#ifdef __BORLANDC__ -#define _getch getch -#endif -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0500 -#endif -#include -#include -#include -#include + #ifdef __BORLANDC__ + #define _getch getch + #endif + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0500 + #endif + #include + #include + #include + #include + #include + #define TINYFD_NOCCSUNICODE + #define TINYFD_SLASH "\\" #else -#include -#include -#include /* on old systems try instead */ -#include -#include -#include /* on old systems try instead */ + #include + #include + #include /* on old systems try instead */ + #include + #include + #include /* on old systems try instead */ + #define TINYFD_SLASH "/" #endif /* _WIN32 */ #define MAX_PATH_OR_CMD 1024 /* _MAX_PATH or MAX_PATH */ -int tinyfd_verbose = 0; /* on unix: prints the command line calls */ -int tinyfd_silent = 1; /* 1 (default) or 0 : on unix, - hide errors and warnings from called dialog*/ +#ifndef MAX_MULTIPLE_FILES +#define MAX_MULTIPLE_FILES 1024 +#endif +#define LOW_MULTIPLE_FILES 32 + +char tinyfd_version[8] = "3.18.1"; + +/******************************************************************************************************/ +/**************************************** UTF-8 on Windows ********************************************/ +/******************************************************************************************************/ +#ifdef _WIN32 +/* if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of tinyfiledialogs.h ) +Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */ +int tinyfd_winUtf8 = 1; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */ +/* for MBCS change this to 0, here or in your code */ +#endif +/******************************************************************************************************/ +/******************************************************************************************************/ +/******************************************************************************************************/ + +int tinyfd_verbose = 0 ; /* on unix: prints the command line calls */ +int tinyfd_silent = 1 ; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */ + +/* Curses dialogs are difficult to use, on windows they are only ascii and uses the unix backslah */ +int tinyfd_allowCursesDialogs = 0 ; /* 0 (default) or 1 */ +int tinyfd_forceConsole = 0 ; /* 0 (default) or 1 */ +/* for unix & windows: 0 (graphic mode) or 1 (console mode). +0: try to use a graphic solution, if it fails then it uses console mode. +1: forces all dialogs into console mode even when the X server is present. + it can use the package dialog or dialog.exe. + on windows it only make sense for console applications */ + +int tinyfd_assumeGraphicDisplay = 0; /* 0 (default) or 1 */ +/* some systems don't set the environment variable DISPLAY even when a graphic display is present. +set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */ + + +char tinyfd_response[1024]; +/* if you pass "tinyfd_query" as aTitle, +the functions will not display the dialogs +but return 0 for console mode, 1 for graphic mode. +tinyfd_response is then filled with the retain solution. +possible values for tinyfd_response are (all lowercase) +for graphic mode: + windows_wchar windows applescript kdialog zenity zenity3 yad matedialog + shellementary qarma python2-tkinter python3-tkinter python-dbus + perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst +for console mode: + dialog whiptail basicinput no_solution */ + +static int gWarningDisplayed = 0 ; +static char gTitle[]="missing software! (we will try basic console input)"; #ifdef _WIN32 -char const tinyfd_needs[] = "\ +char tinyfd_needs[] = "\ ___________\n\ / \\ \n\ | tiny file |\n\ @@ -80,10 +167,10 @@ char const tinyfd_needs[] = "\ \\|\ \ntiny file dialogs on Windows needs:\ \n a graphic display\ -\nor dialog.exe (enhanced console mode)\ +\nor dialog.exe (curses console mode ** Disabled by default **)\ \nor a console for basic input"; #else -char const tinyfd_needs[] = "\ +char tinyfd_needs[] = "\ ___________\n\ / \\ \n\ | tiny file |\n\ @@ -91,15 +178,13 @@ char const tinyfd_needs[] = "\ \\_____ ____/\n\ \\|\ \ntiny file dialogs on UNIX needs:\ -\n applescript\ -\nor kdialog\ -\nor zenity (or matedialog or qarma)\ -\nor python (2 or 3)\ -\n + tkinter + python-dbus (optional)\ -\nor dialog (opens console if needed)\ -\nor xterm + bash\ -\n (opens console for basic input)\ -\nor existing console for basic input"; +\n applescript or kdialog or yad or Xdialog\ +\nor zenity (or matedialog or shellementary or qarma)\ +\nor python (2 or 3) + tkinter + python-dbus (optional)\ +\nor dialog (opens console if needed) ** Disabled by default **\ +\nor xterm + bash (opens console for basic input)\ +\nor existing console for basic input."; + #endif #ifdef _MSC_VER @@ -108,1169 +193,7980 @@ char const tinyfd_needs[] = "\ #pragma warning(disable:4706) /* allows usage of strncpy, strcpy, strcat, sprintf, fopen */ #endif -#ifdef _WIN32 +static int getenvDISPLAY(void) +{ + return tinyfd_assumeGraphicDisplay || getenv("DISPLAY"); +} -#if !defined(WC_ERR_INVALID_CHARS) -/* undefined prior to Vista, so not yet in MINGW header file */ -#define WC_ERR_INVALID_CHARS 0x00000080 -#endif -static wchar_t *utf8to16(char const *const utf8) +static char * getCurDir(void) { - int charactersNeeded = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, -1, NULL, 0); - wchar_t *utf16 = (wchar_t *) malloc(charactersNeeded * sizeof(wchar_t)); - int charactersWritten = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, -1, utf16, charactersNeeded); - if (charactersWritten == 0) { - free(utf16); - return NULL; - } - return utf16; + static char lCurDir[MAX_PATH_OR_CMD]; + return getcwd(lCurDir, sizeof(lCurDir)); } -static char *utf16to8(wchar_t const *const utf16) + +static char * getPathWithoutFinalSlash( + char * aoDestination, /* make sure it is allocated, use _MAX_PATH */ + char const * aSource) /* aoDestination and aSource can be the same */ { - int bytesNeeded = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, NULL, 0, NULL, NULL); - char *utf8 = (char *) malloc(bytesNeeded); - int bytesWritten = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, utf8, bytesNeeded, NULL, NULL); - if (bytesWritten == 0) { - free(utf8); - return NULL; - } - return utf8; + char const * lTmp ; + if ( aSource ) + { + lTmp = strrchr(aSource, '/'); + if (!lTmp) + { + lTmp = strrchr(aSource, '\\'); + } + if (lTmp) + { + strncpy(aoDestination, aSource, lTmp - aSource ); + aoDestination[lTmp - aSource] = '\0'; + } + else + { + * aoDestination = '\0'; + } + } + else + { + * aoDestination = '\0'; + } + return aoDestination; } -static int dirExists(wchar_t const *const dirPath) -{ - if (!dirPath) { - return 0; - } - size_t dirLen = wcslen(dirPath); - if (dirLen == 0) { - return 0; - } else if (dirLen == 2 && dirPath[1] == L':') { - return 1; - } - struct _stat info; - int statResult = _wstat(dirPath, &info); - return statResult == 0 && info.st_mode & S_IFDIR; +static char * getLastName( + char * aoDestination, /* make sure it is allocated */ + char const * aSource) +{ + /* copy the last name after '/' or '\' */ + char const * lTmp ; + if ( aSource ) + { + lTmp = strrchr(aSource, '/'); + if (!lTmp) + { + lTmp = strrchr(aSource, '\\'); + } + if (lTmp) + { + strcpy(aoDestination, lTmp + 1); + } + else + { + strcpy(aoDestination, aSource); + } + } + else + { + * aoDestination = '\0'; + } + return aoDestination; } -int tinyfd_messageBox( - char const *const aTitle, /* NULL or "" */ - char const *const aMessage, /* NULL or "" may contain \n and \t */ - char const *const aDialogType, /* "ok" "okcancel" */ - char const *const aIconType, /* "info" "warning" "error" "question" */ - int const aDefaultButton) /* 0 for cancel, 1 for ok */ -{ - wchar_t *wTitle = utf8to16(aTitle); - wchar_t *wMessage = utf8to16(aMessage); - - UINT mbCode; - if (aIconType && strcmp("warning", aIconType) == 0) { - mbCode = MB_ICONWARNING; - } else if (aIconType && strcmp("error", aIconType) == 0) { - mbCode = MB_ICONERROR; - } else if (aIconType && strcmp("question", aIconType) == 0) { - mbCode = MB_ICONQUESTION; - } else { - mbCode = MB_ICONINFORMATION; - } - - if (aDialogType && strcmp("okcancel", aDialogType) == 0) { - mbCode |= MB_OKCANCEL; - if (aDefaultButton == 0) { - mbCode |= MB_DEFBUTTON2; - } - } else { - mbCode |= MB_OK; - } - mbCode |= MB_TOPMOST; - int mbResult = MessageBoxW(GetForegroundWindow(), wMessage, wTitle, mbCode); +static void ensureFinalSlash( char * aioString ) +{ + if ( aioString && strlen( aioString ) ) + { + char * lastcar = aioString + strlen( aioString ) - 1 ; + if ( strncmp( lastcar , TINYFD_SLASH , 1 ) ) + { + strcat( lastcar , TINYFD_SLASH ) ; + } + } +} - free(wTitle); - free(wMessage); - if ((aDialogType && strcmp("okcancel", aDialogType)) || (mbResult == IDOK)) { - return 1; - } else { - return 0; - } +static void Hex2RGB( char const aHexRGB[8] , unsigned char aoResultRGB[3] ) +{ + char lColorChannel[8] ; + if ( aoResultRGB ) + { + if ( aHexRGB ) + { + strcpy(lColorChannel, aHexRGB ) ; + aoResultRGB[2] = (unsigned char)strtoul(lColorChannel+5,NULL,16); + lColorChannel[5] = '\0'; + aoResultRGB[1] = (unsigned char)strtoul(lColorChannel+3,NULL,16); + lColorChannel[3] = '\0'; + aoResultRGB[0] = (unsigned char)strtoul(lColorChannel+1,NULL,16); +/* printf("%d %d %d\n", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]); */ + } + else + { + aoResultRGB[0]=0; + aoResultRGB[1]=0; + aoResultRGB[2]=0; + } + } } -static int __stdcall BrowseCallbackProcW(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) +static void RGB2Hex( unsigned char const aRGB[3], char aoResultHexRGB[8] ) { - if (uMsg == BFFM_INITIALIZED) { - SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) pData); - } - return 0; + if ( aoResultHexRGB ) + { + if ( aRGB ) + { +#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) + sprintf(aoResultHexRGB, "#%02hhx%02hhx%02hhx", aRGB[0], aRGB[1], aRGB[2]); +#else + sprintf(aoResultHexRGB, "#%02hx%02hx%02hx", aRGB[0], aRGB[1], aRGB[2]); +#endif + /*printf("aoResultHexRGB %s\n", aoResultHexRGB);*/ + } + else + { + aoResultHexRGB[0]=0; + aoResultHexRGB[1]=0; + aoResultHexRGB[2]=0; + } + } } -char const *tinyfd_selectFolderDialog(char const *const aTitle) /* NULL or "" */ + +void tfd_replaceSubStr( char const * aSource, char const * aOldSubStr, + char const * aNewSubStr, char * aoDestination ) { - static char resultBuff[MAX_PATH_OR_CMD]; - static wchar_t wBuff[MAX_PATH_OR_CMD]; + char const * pOccurence ; + char const * p ; + char const * lNewSubStr = "" ; + size_t lOldSubLen = strlen( aOldSubStr ) ; + + if ( ! aSource ) + { + * aoDestination = '\0' ; + return ; + } + if ( ! aOldSubStr ) + { + strcpy( aoDestination , aSource ) ; + return ; + } + if ( aNewSubStr ) + { + lNewSubStr = aNewSubStr ; + } + p = aSource ; + * aoDestination = '\0' ; + while ( ( pOccurence = strstr( p , aOldSubStr ) ) != NULL ) + { + strncat( aoDestination , p , pOccurence - p ) ; + strcat( aoDestination , lNewSubStr ) ; + p = pOccurence + lOldSubLen ; + } + strcat( aoDestination , p ) ; +} - wchar_t *wTitle = utf8to16(aTitle); - BROWSEINFOW bInfo; - HRESULT hResult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +static int filenameValid( char const * aFileNameWithoutPath ) +{ + if ( ! aFileNameWithoutPath + || ! strlen(aFileNameWithoutPath) + || strpbrk(aFileNameWithoutPath , "\\/:*?\"<>|") ) + { + return 0 ; + } + return 1 ; +} - bInfo.hwndOwner = GetForegroundWindow(); - bInfo.pidlRoot = NULL; - bInfo.pszDisplayName = wBuff; - bInfo.lpszTitle = wTitle && wcslen(wTitle) ? wTitle : NULL; - if (hResult == S_OK || hResult == S_FALSE) { - bInfo.ulFlags = BIF_USENEWUI; - } - bInfo.lpfn = BrowseCallbackProcW; - bInfo.lParam = 0; - bInfo.iImage = -1; - bInfo.ulFlags |= BIF_RETURNONLYFSDIRS; - - LPITEMIDLIST lpItem = SHBrowseForFolderW(&bInfo); - if (lpItem) { - SHGetPathFromIDListW(lpItem, wBuff); - } +#ifndef _WIN32 - if (hResult == S_OK || hResult == S_FALSE) { - CoUninitialize(); - } +static int fileExists( char const * aFilePathAndName ) +{ + FILE * lIn ; + if ( ! aFilePathAndName || ! strlen(aFilePathAndName) ) + { + return 0 ; + } + lIn = fopen( aFilePathAndName , "r" ) ; + if ( ! lIn ) + { + return 0 ; + } + fclose( lIn ) ; + return 1 ; +} - free(wTitle); +#endif - if (!dirExists(wBuff)) { - return NULL; - } - char *dirPath = utf16to8(wBuff); - strcpy(resultBuff, dirPath); - free(dirPath); +static void wipefile(char const * aFilename) +{ + int i; + struct stat st; + FILE * lIn; + + if (stat(aFilename, &st) == 0) + { + if ((lIn = fopen(aFilename, "w"))) + { + for (i = 0; i < st.st_size; i++) + { + fputc('A', lIn); + } + fclose(lIn); + } + } +} + - return resultBuff; +int tfd_quoteDetected(char const * aString) +{ + char const * p; + + if (!aString) return 0; + + p = aString; + if ( strchr(p, '\'')) + { + return 1; + } + + if ( strchr(p, '\"')) + { + return 1; + } + + if ( strchr(p, '`')) + { + return 1; + } + + p = aString; + while ((p = strchr(p, '$'))) + { + p ++ ; + if ( ( * p == '(' ) || ( * p == '_' ) || isalpha( * p) ) return 1 ; + } + + return 0; } -#else /* unix */ -#define RETURN_CACHED_INT(setter) static int result = -1; if (result == -1) { result = setter; } return result; +char const * tinyfd_getGlobalChar(char const * aCharVariableName) /* to be called from C# (you don't need this in C or C++) */ +{ + if (!aCharVariableName || !strlen(aCharVariableName)) return NULL; + else if (!strcmp(aCharVariableName, "tinyfd_version")) return tinyfd_version; + else if (!strcmp(aCharVariableName, "tinyfd_needs")) return tinyfd_needs; + else if (!strcmp(aCharVariableName, "tinyfd_response")) return tinyfd_response; + else return NULL ; +} -static int gWarningDisplayed = 0; -static char const gTitle[] = "missing software! (we will try basic console input)"; +int tinyfd_getGlobalInt(char const * aIntVariableName) /* to be called from C# (you don't need this in C or C++) */ +{ + if ( !aIntVariableName || !strlen(aIntVariableName) ) return -1 ; + else if ( !strcmp(aIntVariableName, "tinyfd_verbose") ) return tinyfd_verbose ; + else if ( !strcmp(aIntVariableName, "tinyfd_silent") ) return tinyfd_silent ; + else if ( !strcmp(aIntVariableName, "tinyfd_allowCursesDialogs") ) return tinyfd_allowCursesDialogs ; + else if ( !strcmp(aIntVariableName, "tinyfd_forceConsole") ) return tinyfd_forceConsole ; + else if ( !strcmp(aIntVariableName, "tinyfd_assumeGraphicDisplay") ) return tinyfd_assumeGraphicDisplay ; +#ifdef _WIN32 + else if ( !strcmp(aIntVariableName, "tinyfd_winUtf8") ) return tinyfd_winUtf8 ; +#endif + else return -1; +} -static char gPython2Name[16]; -static char gPython3Name[16]; -static void replaceSubStr( - char const *const aSource, - char const *const aOldSubStr, - char const *const aNewSubStr, - char *const aoDestination) +int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue) /* to be called from C# (you don't need this in C or C++) */ { - char const *pOccurence; - char const *p; - char const *lNewSubStr = ""; - size_t lOldSubLen = strlen(aOldSubStr); + if (!aIntVariableName || !strlen(aIntVariableName)) return -1 ; + else if (!strcmp(aIntVariableName, "tinyfd_verbose")) { tinyfd_verbose = aValue; return tinyfd_verbose; } + else if (!strcmp(aIntVariableName, "tinyfd_silent")) { tinyfd_silent = aValue; return tinyfd_silent; } + else if (!strcmp(aIntVariableName, "tinyfd_allowCursesDialogs")) { tinyfd_allowCursesDialogs = aValue; return tinyfd_allowCursesDialogs; } + else if (!strcmp(aIntVariableName, "tinyfd_forceConsole")) { tinyfd_forceConsole = aValue; return tinyfd_forceConsole; } + else if (!strcmp(aIntVariableName, "tinyfd_assumeGraphicDisplay")) { tinyfd_assumeGraphicDisplay = aValue; return tinyfd_assumeGraphicDisplay; } +#ifdef _WIN32 + else if (!strcmp(aIntVariableName, "tinyfd_winUtf8")) { tinyfd_winUtf8 = aValue; return tinyfd_winUtf8; } +#endif + else return -1; +} - if (!aSource) { - *aoDestination = '\0'; - return; - } - if (!aOldSubStr) { - strcpy(aoDestination, aSource); - return; - } - if (aNewSubStr) { - lNewSubStr = aNewSubStr; - } - p = aSource; - *aoDestination = '\0'; - while ((pOccurence = strstr(p, aOldSubStr)) != NULL) { - strncat(aoDestination, p, pOccurence - p); - strcat(aoDestination, lNewSubStr); - p = pOccurence + lOldSubLen; - } - strcat(aoDestination, p); + +#ifdef _WIN32 +static int powershellPresent(void) +{ /*only on vista and above (or installed on xp)*/ + static int lPowershellPresent = -1; + char lBuff[MAX_PATH_OR_CMD]; + FILE* lIn; + char const* lString = "powershell.exe"; + + if (lPowershellPresent < 0) + { + if (!(lIn = _popen("where powershell.exe", "r"))) + { + lPowershellPresent = 0; + return 0; + } + while (fgets(lBuff, sizeof(lBuff), lIn) != NULL) + { + } + _pclose(lIn); + if (lBuff[strlen(lBuff) - 1] == '\n') + { + lBuff[strlen(lBuff) - 1] = '\0'; + } + if (strcmp(lBuff + strlen(lBuff) - strlen(lString), lString)) + { + lPowershellPresent = 0; + } + else + { + lPowershellPresent = 1; + } + } + return lPowershellPresent; } -static int dirExists(char const *const dirPath) +static int windowsVersion(void) { - if (!dirPath || !strlen(dirPath)) { - return 0; - } - DIR *dir = opendir(dirPath); - if (!dir) { - return 0; - } - closedir(dir); - return 1; -} - -static int detectPresence(char const *const aExecutable) -{ - char lBuff[MAX_PATH_OR_CMD]; - char lTestedString[MAX_PATH_OR_CMD] = "which "; - FILE *lIn; - - strcat(lTestedString, aExecutable); - strcat(lTestedString, " 2>/dev/null "); - lIn = popen(lTestedString, "r"); - if ((fgets(lBuff, sizeof(lBuff), lIn) != NULL) - && (!strchr(lBuff, ':')) - && (strncmp(lBuff, "no ", 3))) { /* present */ - pclose(lIn); - if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 1); - return 1; - } else { - pclose(lIn); - if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 0); - return 0; - } +#if !defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR) + HMODULE hMod; + FARPROC lFxPtr; + RTL_OSVERSIONINFOW lRovi = { 0 }; + + hMod = GetModuleHandleW(L"ntdll.dll"); + if (hMod) { + lFxPtr = GetProcAddress(hMod, "RtlGetVersion"); + if (lFxPtr) + { + lRovi.dwOSVersionInfoSize = sizeof(lRovi); + if (!lFxPtr(&lRovi)) + { + return lRovi.dwMajorVersion; + } + } + } +#endif + if (powershellPresent()) return 6; /*minimum is vista or installed on xp*/ + return 0; } -static int tryCommand(char const *const aCommand) +static void replaceChr(char * aString, char aOldChr, char aNewChr) { - char lBuff[MAX_PATH_OR_CMD]; - FILE *lIn; - - lIn = popen(aCommand, "r"); - if (fgets(lBuff, sizeof(lBuff), lIn) == NULL) { /* present */ - pclose(lIn); - return 1; - } else { - pclose(lIn); - return 0; - } + char * p; + + if (!aString) return; + if (aOldChr == aNewChr) return; + + p = aString; + while ((p = strchr(p, aOldChr))) + { + *p = aNewChr; + p++; + } + return; } -static int copyAndDetectPresence(char *aExecutable, char const *const path) + +#if !defined(WC_ERR_INVALID_CHARS) +/* undefined prior to Vista, so not yet in MINGW header file */ +#define WC_ERR_INVALID_CHARS 0x00000000 /* 0x00000080 for MINGW maybe ? */ +#endif + +static int sizeUtf16From8(char const * aUtf8string) { - strcpy(aExecutable, path); - return detectPresence(aExecutable); + return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + aUtf8string, -1, NULL, 0); } -static int isTerminalRunning(void) + +static int sizeUtf16FromMbcs(char const * aMbcsString) { - static int lIsTerminalRunning = -1; - if (lIsTerminalRunning < 0) { - lIsTerminalRunning = isatty(1); - if (tinyfd_verbose) printf("isTerminalRunning %d\n", lIsTerminalRunning); - } - return lIsTerminalRunning; + return MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, + aMbcsString, -1, NULL, 0); } -static char const *terminalName(void) +static int sizeUtf8(wchar_t const * aUtf16string) { - static char lTerminalName[128] = "*"; - char lShellName[64] = "*"; + return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + aUtf16string, -1, NULL, 0, NULL, NULL); +} - if (lTerminalName[0] == '*') { - if (detectPresence("bash")) { - strcpy(lShellName, "bash -c "); /*good for basic input*/ - } else { - strcpy(lTerminalName, ""); - return NULL; - } - if (copyAndDetectPresence(lTerminalName, "xterm")) { /*good (small without parameters)*/ - strcat(lTerminalName, " -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "terminator")) { /*good*/ - strcat(lTerminalName, " -x "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "lxterminal")) { /*good*/ - strcat(lTerminalName, " -e "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "konsole")) { /*good*/ - strcat(lTerminalName, " -e "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "kterm")) { /*good*/ - strcat(lTerminalName, " -e "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "tilix")) { /*good*/ - strcat(lTerminalName, " -e "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "xfce4-terminal")) { /*good*/ - strcat(lTerminalName, " -x "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "mate-terminal")) { /*good*/ - strcat(lTerminalName, " -x "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "Eterm")) { /*good*/ - strcat(lTerminalName, " -e "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "evilvte")) { /*good*/ - strcat(lTerminalName, " -e "); - strcat(lTerminalName, lShellName); - } else if (copyAndDetectPresence(lTerminalName, "pterm")) { /*good (only letters)*/ - strcat(lTerminalName, " -e "); - strcat(lTerminalName, lShellName); - } else { - strcpy(lTerminalName, ""); - } - /* bad: koi rxterm guake tilda vala-terminal qterminal - aterm Terminal terminology sakura lilyterm weston-terminal - roxterm termit xvt rxvt mrxvt urxvt */ - } - if (strlen(lTerminalName)) { - return lTerminalName; - } else { - return NULL; - } +static int sizeMbcs(wchar_t const * aMbcsString) +{ + int lRes = WideCharToMultiByte(CP_ACP, 0, + aMbcsString, -1, NULL, 0, NULL, NULL); + /* DWORD licic = GetLastError(); */ + return lRes; } -static int xmessagePresent(void) + +wchar_t* tinyfd_mbcsTo16(char const* aMbcsString) { - RETURN_CACHED_INT(detectPresence("xmessage")); + static wchar_t* lMbcsString = NULL; + int lSize; + + free(lMbcsString); + if (!aMbcsString) { lMbcsString = NULL; return NULL; } + lSize = sizeUtf16FromMbcs(aMbcsString); + if (lSize) + { + lMbcsString = (wchar_t*) malloc(lSize * sizeof(wchar_t)); + lSize = MultiByteToWideChar(CP_ACP, 0, aMbcsString, -1, lMbcsString, lSize); + } + else wcscpy(lMbcsString, L""); + return lMbcsString; } -static int gxmessagePresent(void) + +wchar_t * tinyfd_utf8to16(char const * aUtf8string) { - RETURN_CACHED_INT(detectPresence("gxmessage")); + static wchar_t * lUtf16string = NULL; + int lSize; + + free(lUtf16string); + if (!aUtf8string) {lUtf16string = NULL; return NULL;} + lSize = sizeUtf16From8(aUtf8string); + if (lSize) + { + lUtf16string = (wchar_t*) malloc(lSize * sizeof(wchar_t)); + lSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + aUtf8string, -1, lUtf16string, lSize); + return lUtf16string; + } + else + { + /* let's try mbcs anyway */ + lUtf16string = NULL; + return tinyfd_mbcsTo16(aUtf8string); + } } -static int gmessagePresent(void) + +char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string) { - RETURN_CACHED_INT(detectPresence("gmessage")); + static char * lMbcsString = NULL; + int lSize; + + free(lMbcsString); + if (!aUtf16string) { lMbcsString = NULL; return NULL; } + lSize = sizeMbcs(aUtf16string); + if (lSize) + { + lMbcsString = (char*) malloc(lSize); + lSize = WideCharToMultiByte(CP_ACP, 0, aUtf16string, -1, lMbcsString, lSize, NULL, NULL); + } + else strcpy(lMbcsString, ""); + return lMbcsString; } -static int xdialogPresent(void) + +char * tinyfd_utf8toMbcs(char const * aUtf8string) { - RETURN_CACHED_INT(detectPresence("Xdialog")); + wchar_t const * lUtf16string; + lUtf16string = tinyfd_utf8to16(aUtf8string); + return tinyfd_utf16toMbcs(lUtf16string); } -static int gdialogPresent(void) + +char * tinyfd_utf16to8(wchar_t const * aUtf16string) { - RETURN_CACHED_INT(detectPresence("gdialog")); + static char * lUtf8string = NULL; + int lSize; + + free(lUtf8string); + if (!aUtf16string) { lUtf8string = NULL; return NULL; } + lSize = sizeUtf8(aUtf16string); + if (lSize) + { + lUtf8string = (char*) malloc(lSize); + lSize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, aUtf16string, -1, lUtf8string, lSize, NULL, NULL); + } + else strcpy(lUtf8string, ""); + return lUtf8string; } -static int osascriptPresent(void) + +char * tinyfd_mbcsTo8(char const * aMbcsString) { - RETURN_CACHED_INT(detectPresence("osascript")); + wchar_t const * lUtf16string; + lUtf16string = tinyfd_mbcsTo16(aMbcsString); + return tinyfd_utf16to8(lUtf16string); } -static int qarmaPresent(void) + +void tinyfd_beep(void) { - RETURN_CACHED_INT(detectPresence("qarma")); + if (windowsVersion() > 5) Beep(440, 300); + else MessageBeep(MB_OK); } -static int matedialogPresent(void) + +static void wipefileW(wchar_t const * aFilename) { - RETURN_CACHED_INT(detectPresence("matedialog")); + int i; + FILE * lIn; +#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) + struct _stat st; + if (_wstat(aFilename, &st) == 0) +#else + struct __stat64 st; + if (_wstat64(aFilename, &st) == 0) +#endif + { + if ((lIn = _wfopen(aFilename, L"w"))) + { + for (i = 0; i < st.st_size; i++) + { + fputc('A', lIn); + } + fclose(lIn); + } + } } -static int zenityPresent(void) + +static wchar_t * getPathWithoutFinalSlashW( + wchar_t * aoDestination, /* make sure it is allocated, use _MAX_PATH */ + wchar_t const * aSource) /* aoDestination and aSource can be the same */ { - RETURN_CACHED_INT(detectPresence("zenity")); + wchar_t const * lTmp; + if (aSource) + { + lTmp = wcsrchr(aSource, L'/'); + if (!lTmp) + { + lTmp = wcsrchr(aSource, L'\\'); + } + if (lTmp) + { + wcsncpy(aoDestination, aSource, lTmp - aSource); + aoDestination[lTmp - aSource] = L'\0'; + } + else + { + *aoDestination = L'\0'; + } + } + else + { + *aoDestination = L'\0'; + } + return aoDestination; } -static int zenity3Present(void) + +static wchar_t * getLastNameW( + wchar_t * aoDestination, /* make sure it is allocated */ + wchar_t const * aSource) { - static int lZenity3Present = -1; - char lBuff[MAX_PATH_OR_CMD]; - FILE *lIn; - int lIntTmp; + /* copy the last name after '/' or '\' */ + wchar_t const * lTmp; + if (aSource) + { + lTmp = wcsrchr(aSource, L'/'); + if (!lTmp) + { + lTmp = wcsrchr(aSource, L'\\'); + } + if (lTmp) + { + wcscpy(aoDestination, lTmp + 1); + } + else + { + wcscpy(aoDestination, aSource); + } + } + else + { + *aoDestination = L'\0'; + } + return aoDestination; +} - if (lZenity3Present < 0) { - lZenity3Present = 0; - if (zenityPresent()) { - lIn = popen("zenity --version", "r"); - if (fgets(lBuff, sizeof(lBuff), lIn) != NULL) { - if (atoi(lBuff) >= 3) { - lZenity3Present = 3; - lIntTmp = atoi(strtok(lBuff, ".") + 2); - if (lIntTmp >= 18) { - lZenity3Present = 5; - } else if (lIntTmp >= 10) { - lZenity3Present = 4; - } - } else if ((atoi(lBuff) == 2) && (atoi(strtok(lBuff, ".") + 2) >= 32)) { - lZenity3Present = 2; - } - if (tinyfd_verbose) printf("zenity %d\n", lZenity3Present); - } - pclose(lIn); - } - } - return lZenity3Present; + +static void Hex2RGBW(wchar_t const aHexRGB[8], unsigned char aoResultRGB[3]) +{ + wchar_t lColorChannel[8]; + if (aoResultRGB) + { + if (aHexRGB) + { + wcscpy(lColorChannel, aHexRGB); + aoResultRGB[2] = (unsigned char)wcstoul(lColorChannel + 5, NULL, 16); + lColorChannel[5] = '\0'; + aoResultRGB[1] = (unsigned char)wcstoul(lColorChannel + 3, NULL, 16); + lColorChannel[3] = '\0'; + aoResultRGB[0] = (unsigned char)wcstoul(lColorChannel + 1, NULL, 16); + /* printf("%d %d %d\n", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]); */ + } + else + { + aoResultRGB[0] = 0; + aoResultRGB[1] = 0; + aoResultRGB[2] = 0; + } + } } -static int kdialogPresent(void) + +static void RGB2HexW( unsigned char const aRGB[3], wchar_t aoResultHexRGB[8]) { - static int lKdialogPresent = -1; - char lBuff[MAX_PATH_OR_CMD]; - FILE *lIn; - char *lDesktop; +#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) + wchar_t const * const lPrintFormat = L"#%02hhx%02hhx%02hhx"; +#else + wchar_t const * const lPrintFormat = L"#%02hx%02hx%02hx"; +#endif - if (lKdialogPresent < 0) { - if (zenityPresent()) { - lDesktop = getenv("XDG_SESSION_DESKTOP"); - if (!lDesktop || (strcmp(lDesktop, "KDE") && strcmp(lDesktop, "lxqt"))) { - lKdialogPresent = 0; - return lKdialogPresent; - } - } + if (aoResultHexRGB) + { + if (aRGB) + { + /* wprintf(L"aoResultHexRGB %s\n", aoResultHexRGB); */ +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + swprintf(aoResultHexRGB, 8, lPrintFormat, aRGB[0], aRGB[1], aRGB[2]); +#else + swprintf(aoResultHexRGB, lPrintFormat, aRGB[0], aRGB[1], aRGB[2]); +#endif - lKdialogPresent = detectPresence("kdialog"); - if (lKdialogPresent && !getenv("SSH_TTY")) { - lIn = popen("kdialog --attach 2>&1", "r"); - if (fgets(lBuff, sizeof(lBuff), lIn) != NULL) { - if (!strstr("Unknown", lBuff)) { - lKdialogPresent = 2; - if (tinyfd_verbose) printf("kdialog-attach %d\n", lKdialogPresent); - } - } - pclose(lIn); - - if (lKdialogPresent == 2) { - lKdialogPresent = 1; - lIn = popen("kdialog --passivepopup 2>&1", "r"); - if (fgets(lBuff, sizeof(lBuff), lIn) != NULL) { - if (!strstr("Unknown", lBuff)) { - lKdialogPresent = 2; - if (tinyfd_verbose) printf("kdialog-popup %d\n", lKdialogPresent); - } - } - pclose(lIn); - } - } - } - return lKdialogPresent; + } + else + { + aoResultHexRGB[0] = 0; + aoResultHexRGB[1] = 0; + aoResultHexRGB[2] = 0; + } + } } -static int python2Present(void) -{ - static int lPython2Present = -1; - int i; - - if (lPython2Present < 0) { - lPython2Present = 0; - strcpy(gPython2Name, "python2"); - if (detectPresence(gPython2Name)) { - lPython2Present = 1; - } else { - for (i = 9; i >= 0; i--) { - sprintf(gPython2Name, "python2.%d", i); - if (detectPresence(gPython2Name)) { - lPython2Present = 1; - break; - } - } - } - if (tinyfd_verbose) printf("lPython2Present %d\n", lPython2Present); - if (tinyfd_verbose) printf("gPython2Name %s\n", gPython2Name); - } - return lPython2Present; -} -static int python3Present(void) +static int dirExists(char const * aDirPath) { - static int lPython3Present = -1; - int i; - - if (lPython3Present < 0) { - lPython3Present = 0; - strcpy(gPython3Name, "python3"); - if (detectPresence(gPython3Name)) { - lPython3Present = 1; - } else { - for (i = 9; i >= 0; i--) { - sprintf(gPython3Name, "python3.%d", i); - if (detectPresence(gPython3Name)) { - lPython3Present = 1; - break; - } - } - } - if (tinyfd_verbose) printf("lPython3Present %d\n", lPython3Present); - if (tinyfd_verbose) printf("gPython3Name %s\n", gPython3Name); - } - return lPython3Present; +#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) + struct _stat lInfo; +#else + struct __stat64 lInfo; +#endif + wchar_t * lTmpWChar; + int lStatRet; + size_t lDirLen; + + if (!aDirPath) + return 0; + lDirLen = strlen(aDirPath); + if (!lDirLen) + return 1; + if ( (lDirLen == 2) && (aDirPath[1] == ':') ) + return 1; + + if (tinyfd_winUtf8) + { + lTmpWChar = tinyfd_utf8to16(aDirPath); +#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) + lStatRet = _wstat(lTmpWChar, &lInfo); +#else + lStatRet = _wstat64(lTmpWChar, &lInfo); +#endif + if (lStatRet != 0) + return 0; + else if (lInfo.st_mode & S_IFDIR) + return 1; + else + return 0; + } +#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) + else if (_stat(aDirPath, &lInfo) != 0) +#else + else if (_stat64(aDirPath, &lInfo) != 0) +#endif + return 0; + else if (lInfo.st_mode & S_IFDIR) + return 1; + else + return 0; } -static int tkinter2Present(void) + +static int fileExists(char const * aFilePathAndName) { - static int lTkinter2Present = -1; - char lPythonCommand[300]; - char lPythonParams[256] = "-S -c \"try:\n\timport Tkinter;\nexcept:\n\tprint 0;\""; +#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) + struct _stat lInfo; +#else + struct __stat64 lInfo; +#endif + wchar_t * lTmpWChar; + int lStatRet; + FILE * lIn; + + if (!aFilePathAndName || !strlen(aFilePathAndName)) + { + return 0; + } + + if (tinyfd_winUtf8) + { + lTmpWChar = tinyfd_utf8to16(aFilePathAndName); +#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) + lStatRet = _wstat(lTmpWChar, &lInfo); +#else + lStatRet = _wstat64(lTmpWChar, &lInfo); +#endif - if (lTkinter2Present < 0) { - lTkinter2Present = 0; - if (python2Present()) { - sprintf(lPythonCommand, "%s %s", gPython2Name, lPythonParams); - lTkinter2Present = tryCommand(lPythonCommand); - } - if (tinyfd_verbose) printf("lTkinter2Present %d\n", lTkinter2Present); - } - return lTkinter2Present; + if (lStatRet != 0) + return 0; + else if (lInfo.st_mode & _S_IFREG) + return 1; + else + return 0; + } + else + { + lIn = fopen(aFilePathAndName, "r"); + if (!lIn) + { + return 0; + } + fclose(lIn); + return 1; + } } -static int tkinter3Present(void) +static void replaceWchar(wchar_t * aString, + wchar_t aOldChr, + wchar_t aNewChr) { - static int lTkinter3Present = -1; - char lPythonCommand[300]; - char lPythonParams[256] = - "-S -c \"try:\n\timport tkinter;\nexcept:\n\tprint(0);\""; - - if (lTkinter3Present < 0) { - lTkinter3Present = 0; - if (python3Present()) { - sprintf(lPythonCommand, "%s %s", gPython3Name, lPythonParams); - lTkinter3Present = tryCommand(lPythonCommand); - } - if (tinyfd_verbose) printf("lTkinter3Present %d\n", lTkinter3Present); - } - return lTkinter3Present; + wchar_t * p; + + if (!aString) + { + return ; + } + + if (aOldChr == aNewChr) + { + return ; + } + + p = aString; + while ((p = wcsrchr(p, aOldChr))) + { + *p = aNewChr; +#ifdef TINYFD_NOCCSUNICODE + p++; +#endif + p++; + } + return ; } -int tinyfd_messageBox( - char const *const aTitle, /* NULL or "" */ - char const *const aMessage, /* NULL or "" may contain \n and \t */ - char const *const aDialogType, /* "ok" "okcancel" */ - char const *const aIconType, /* "info" "warning" "error" "question" */ - int const aDefaultButton) /* 0 for cancel, 1 for ok */ -{ - char lBuff[MAX_PATH_OR_CMD]; - char *lDialogString = NULL; - char *lpDialogString; - FILE *lIn; - int lResult; - char lChar; - struct termios infoOri; - struct termios info; - size_t lTitleLen; - size_t lMessageLen; - - lBuff[0] = '\0'; - - lTitleLen = aTitle ? strlen(aTitle) : 0; - lMessageLen = aMessage ? strlen(aMessage) : 0; - lDialogString = (char *) malloc(MAX_PATH_OR_CMD + lTitleLen + lMessageLen); - - if (osascriptPresent()) { - strcpy(lDialogString, "osascript "); - strcat(lDialogString, " -e 'try' -e 'set {vButton} to {button returned} of ( display dialog \""); - if (aMessage && strlen(aMessage)) { - strcat(lDialogString, aMessage); - } - strcat(lDialogString, "\" "); - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, "with title \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\" "); - } - strcat(lDialogString, "with icon "); - if (aIconType && !strcmp("error", aIconType)) { - strcat(lDialogString, "stop "); - } else if (aIconType && !strcmp("warning", aIconType)) { - strcat(lDialogString, "caution "); - } else { /* question or info */ - strcat(lDialogString, "note "); - } - if (aDialogType && !strcmp("okcancel", aDialogType)) { - if (!aDefaultButton) { - strcat(lDialogString, "default button \"Cancel\" "); - } - } else { - strcat(lDialogString, "buttons {\"OK\"} "); - strcat(lDialogString, "default button \"OK\" "); - } - strcat(lDialogString, ")' "); +static int quoteDetectedW(wchar_t const * aString) +{ + wchar_t const * p; - strcat(lDialogString, -"-e 'if vButton is \"Yes\" then' -e 'return 1'\ - -e 'else if vButton is \"OK\" then' -e 'return 1'\ - -e 'else if vButton is \"No\" then' -e 'return 2'\ - -e 'else' -e 'return 0' -e 'end if' "); - - strcat(lDialogString, "-e 'on error number -128' "); - strcat(lDialogString, "-e '0' "); - - strcat(lDialogString, "-e 'end try'"); - } else if (kdialogPresent()) { - strcpy(lDialogString, "kdialog"); - strcat(lDialogString, " --"); - if (aDialogType && !strcmp("okcancel", aDialogType)) { - if (aIconType && (!strcmp("warning", aIconType) || !strcmp("error", aIconType))) { - strcat(lDialogString, "warning"); - } - strcat(lDialogString, "yesno"); - } else if (aIconType && !strcmp("error", aIconType)) { - strcat(lDialogString, "error"); - } else if (aIconType && !strcmp("warning", aIconType)) { - strcat(lDialogString, "sorry"); - } else { - strcat(lDialogString, "msgbox"); - } - strcat(lDialogString, " \""); - if (aMessage) { - strcat(lDialogString, aMessage); - } - strcat(lDialogString, "\""); - if (aDialogType && !strcmp("okcancel", aDialogType)) { - strcat(lDialogString, " --yes-label Ok --no-label Cancel"); - } - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, " --title \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\""); - } + if (!aString) return 0; - strcat(lDialogString, ";if [ $? = 0 ];then echo 1;else echo 0;fi"); - } else if (zenityPresent() || matedialogPresent() || qarmaPresent()) { - if (zenityPresent()) { - strcpy(lDialogString, "szAnswer=$(zenity"); - } else if (matedialogPresent()) { - strcpy(lDialogString, "szAnswer=$(matedialog"); - } else { - strcpy(lDialogString, "szAnswer=$(qarma"); - } - strcat(lDialogString, " --"); - - if (aDialogType && !strcmp("okcancel", aDialogType)) { - strcat(lDialogString, "question --ok-label=Ok --cancel-label=Cancel"); - } else if (aIconType && !strcmp("error", aIconType)) { - strcat(lDialogString, "error"); - } else if (aIconType && !strcmp("warning", aIconType)) { - strcat(lDialogString, "warning"); - } else { - strcat(lDialogString, "info"); - } - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, " --title=\""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\""); - } - if (aMessage && strlen(aMessage)) { - strcat(lDialogString, " --no-wrap --text=\""); - strcat(lDialogString, aMessage); - strcat(lDialogString, "\""); - } - if ((zenity3Present() >= 3) || (!zenityPresent() && qarmaPresent())) { - strcat(lDialogString, " --icon-name=dialog-"); - if (aIconType && (!strcmp("question", aIconType) - || !strcmp("error", aIconType) - || !strcmp("warning", aIconType))) { - strcat(lDialogString, aIconType); - } else { - strcat(lDialogString, "information"); - } - } + p = aString; + while ((p = wcsrchr(p, L'\''))) + { + return 1; + } - if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); + p = aString; + while ((p = wcsrchr(p, L'\"'))) + { + return 1; + } - strcat(lDialogString, ");if [ $? = 0 ];then echo 1;else echo 0;fi"); - } else if (!gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter2Present()) { - strcpy(lDialogString, gPython2Name); - strcat(lDialogString, " -S -c \"import Tkinter,tkMessageBox;root=Tkinter.Tk();root.withdraw();"); + return 0; +} - strcat(lDialogString, "res=tkMessageBox."); - if (aDialogType && !strcmp("okcancel", aDialogType)) { - strcat(lDialogString, "askokcancel("); - if (aDefaultButton) { - strcat(lDialogString, "default=tkMessageBox.OK,"); - } else { - strcat(lDialogString, "default=tkMessageBox.CANCEL,"); - } - } else { - strcat(lDialogString, "showinfo("); - } +#endif /* _WIN32 */ - strcat(lDialogString, "icon='"); - if (aIconType && (!strcmp("question", aIconType) - || !strcmp("error", aIconType) - || !strcmp("warning", aIconType))) { - strcat(lDialogString, aIconType); - } else { - strcat(lDialogString, "info"); - } +/* source and destination can be the same or ovelap*/ +static char * ensureFilesExist(char * aDestination, + char const * aSourcePathsAndNames) +{ + char * lDestination = aDestination; + char const * p; + char const * p2; + size_t lLen; + + if (!aSourcePathsAndNames) + { + return NULL; + } + lLen = strlen(aSourcePathsAndNames); + if (!lLen) + { + return NULL; + } + + p = aSourcePathsAndNames; + while ((p2 = strchr(p, '|')) != NULL) + { + lLen = p2 - p; + memmove(lDestination, p, lLen); + lDestination[lLen] = '\0'; + if (fileExists(lDestination)) + { + lDestination += lLen; + *lDestination = '|'; + lDestination++; + } + p = p2 + 1; + } + if (fileExists(p)) + { + lLen = strlen(p); + memmove(lDestination, p, lLen); + lDestination[lLen] = '\0'; + } + else + { + *(lDestination - 1) = '\0'; + } + return aDestination; +} - strcat(lDialogString, "',"); - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, "title='"); - strcat(lDialogString, aTitle); - strcat(lDialogString, "',"); - } - if (aMessage && strlen(aMessage)) { - strcat(lDialogString, "message='"); - lpDialogString = lDialogString + strlen(lDialogString); - replaceSubStr(aMessage, "\n", "\\n", lpDialogString); - strcat(lDialogString, "'"); - } +#ifdef _WIN32 - strcat(lDialogString, ");\n\ -if res is False :\n\tprint 0\n\ -else :\n\tprint 1\n\""); - } else if (!gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter3Present()) { - strcpy(lDialogString, gPython3Name); - strcat(lDialogString, - " -S -c \"import tkinter;from tkinter import messagebox;root=tkinter.Tk();root.withdraw();"); - - strcat(lDialogString, "res=messagebox."); - if (aDialogType && !strcmp("okcancel", aDialogType)) { - strcat(lDialogString, "askokcancel("); - if (aDefaultButton) { - strcat(lDialogString, "default=messagebox.OK,"); - } else { - strcat(lDialogString, "default=messagebox.CANCEL,"); - } - } else { - strcat(lDialogString, "showinfo("); - } +static int __stdcall EnumThreadWndProc(HWND hwnd, LPARAM lParam) +{ + wchar_t lTitleName[MAX_PATH]; + wchar_t const* lDialogTitle = (wchar_t const *) lParam; + + GetWindowTextW(hwnd, lTitleName, MAX_PATH); + /* wprintf(L"lTitleName %ls \n", lTitleName); */ + + if (wcscmp(lDialogTitle, lTitleName) == 0) + { + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + return 0; + } + return 1; +} - strcat(lDialogString, "icon='"); - if (aIconType && (!strcmp("question", aIconType) - || !strcmp("error", aIconType) - || !strcmp("warning", aIconType))) { - strcat(lDialogString, aIconType); - } else { - strcat(lDialogString, "info"); - } - strcat(lDialogString, "',"); - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, "title='"); - strcat(lDialogString, aTitle); - strcat(lDialogString, "',"); - } - if (aMessage && strlen(aMessage)) { - strcat(lDialogString, "message='"); - lpDialogString = lDialogString + strlen(lDialogString); - replaceSubStr(aMessage, "\n", "\\n", lpDialogString); - strcat(lDialogString, "'"); - } +static void hiddenConsoleW(wchar_t const * aString, wchar_t const * aDialogTitle, int aInFront) +{ + STARTUPINFOW StartupInfo; + PROCESS_INFORMATION ProcessInfo; + + if (!aString || !wcslen(aString) ) return; + + memset(&StartupInfo, 0, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(STARTUPINFOW); + StartupInfo.dwFlags = STARTF_USESHOWWINDOW; + StartupInfo.wShowWindow = SW_HIDE; + + if (!CreateProcessW(NULL, (LPWSTR)aString, NULL, NULL, FALSE, + CREATE_NEW_CONSOLE, NULL, NULL, + &StartupInfo, &ProcessInfo)) + { + return; /* GetLastError(); */ + } + + WaitForInputIdle(ProcessInfo.hProcess, INFINITE); + if (aInFront) + { + while (EnumWindows(EnumThreadWndProc, (LPARAM)aDialogTitle)) {} + } + WaitForSingleObject(ProcessInfo.hProcess, INFINITE); + CloseHandle(ProcessInfo.hThread); + CloseHandle(ProcessInfo.hProcess); +} - strcat(lDialogString, ");\n\ -if res is False :\n\tprint(0)\n\ -else :\n\tprint(1)\n\""); - } else if (gxmessagePresent() || gmessagePresent() || (!gdialogPresent() && !xdialogPresent() && xmessagePresent())) { - if (gxmessagePresent()) { - strcpy(lDialogString, "gxmessage"); - } else if (gmessagePresent()) { - strcpy(lDialogString, "gmessage"); - } else { - strcpy(lDialogString, "xmessage"); - } - if (aDialogType && !strcmp("okcancel", aDialogType)) { - strcat(lDialogString, " -buttons Ok:1,Cancel:0"); - switch (aDefaultButton) { - case 1: strcat(lDialogString, " -default Ok"); break; - case 0: strcat(lDialogString, " -default Cancel"); break; - } - } else { - strcat(lDialogString, " -buttons Ok:1"); - strcat(lDialogString, " -default Ok"); - } +int tinyfd_messageBoxW( + wchar_t const * aTitle, /* NULL or "" */ + wchar_t const * aMessage, /* NULL or "" may contain \n and \t */ + wchar_t const * aDialogType, /* "ok" "okcancel" "yesno" "yesnocancel" */ + wchar_t const * aIconType, /* "info" "warning" "error" "question" */ + int aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ +{ + int lBoxReturnValue; + UINT aCode; + + if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return 1; } + + /*if (quoteDetectedW(aTitle)) return tinyfd_messageBoxW(L"INVALID TITLE WITH QUOTES", aMessage, aDialogType, aIconType, aDefaultButton); + if (quoteDetectedW(aMessage)) return tinyfd_messageBoxW(aTitle, L"INVALID MESSAGE WITH QUOTES", aDialogType, aIconType, aDefaultButton);*/ + + if (aIconType && !wcscmp(L"warning", aIconType)) + { + aCode = MB_ICONWARNING; + } + else if (aIconType && !wcscmp(L"error", aIconType)) + { + aCode = MB_ICONERROR; + } + else if (aIconType && !wcscmp(L"question", aIconType)) + { + aCode = MB_ICONQUESTION; + } + else + { + aCode = MB_ICONINFORMATION; + } + + if (aDialogType && !wcscmp(L"okcancel", aDialogType)) + { + aCode += MB_OKCANCEL; + if (!aDefaultButton) + { + aCode += MB_DEFBUTTON2; + } + } + else if (aDialogType && !wcscmp(L"yesno", aDialogType)) + { + aCode += MB_YESNO; + if (!aDefaultButton) + { + aCode += MB_DEFBUTTON2; + } + } + else if (aDialogType && !wcscmp(L"yesnocancel", aDialogType)) + { + aCode += MB_YESNOCANCEL; + if (aDefaultButton == 1) + { + aCode += MB_DEFBUTTON1; + } + else if (aDefaultButton == 2) + { + aCode += MB_DEFBUTTON2; + } + else + { + aCode += MB_DEFBUTTON3; + } + } + else + { + aCode += MB_OK; + } + + aCode += MB_TOPMOST; + + lBoxReturnValue = MessageBoxW(GetForegroundWindow(), aMessage, aTitle, aCode); + + if ( (lBoxReturnValue == IDNO) && (aDialogType && !wcscmp(L"yesnocancel", aDialogType)) ) + { + return 2; + } + else if ( (lBoxReturnValue == IDOK) || (lBoxReturnValue == IDYES) ) + { + return 1; + } + else + { + return 0; + } +} - strcat(lDialogString, " -center \""); - if (aMessage && strlen(aMessage)) { - strcat(lDialogString, aMessage); - } - strcat(lDialogString, "\""); - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, " -title \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\""); - } - strcat(lDialogString, " ; echo $? "); - } else if (xdialogPresent() || gdialogPresent()) { - if (gdialogPresent()) { - strcpy(lDialogString, "(gdialog "); - } else if (xdialogPresent()) { - strcpy(lDialogString, "(Xdialog "); - } - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, "--title \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\" "); - } +/* int tinyfd_notifyPopupW_ORIGINAL( + wchar_t const * aTitle, + wchar_t const * aMessage, + wchar_t const * aIconType) +{ + wchar_t * lDialogString; + size_t lTitleLen; + size_t lMessageLen; + size_t lDialogStringLen; + + if (aTitle && !wcscmp(aTitle, L"tinyfd_query")) { strcpy(tinyfd_response, "windows_wchar"); return 1; } + + if (quoteDetectedW(aTitle)) return tinyfd_notifyPopupW(L"INVALID TITLE WITH QUOTES", aMessage, aIconType); + if (quoteDetectedW(aMessage)) return tinyfd_notifyPopupW(aTitle, L"INVALID MESSAGE WITH QUOTES", aIconType); + + lTitleLen = aTitle ? wcslen(aTitle) : 0; + lMessageLen = aMessage ? wcslen(aMessage) : 0; + lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen; + lDialogString = (wchar_t *) malloc(2 * lDialogStringLen); + if (!lDialogString) return 0; + + wcscpy(lDialogString, L"powershell.exe -executionpolicy bypass -command \"\ +function Show-BalloonTip {\ +[cmdletbinding()] \ +param( \ +[string]$Title = ' ', \ +[string]$Message = ' ', \ +[ValidateSet('info', 'warning', 'error')] \ +[string]$IconType = 'info');\ +[system.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null ; \ +$balloon = New-Object System.Windows.Forms.NotifyIcon ; \ +$path = Get-Process -id $pid | Select-Object -ExpandProperty Path ; \ +$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) ;"); + + wcscat(lDialogString, L"\ +$balloon.Icon = $icon ; \ +$balloon.BalloonTipIcon = $IconType ; \ +$balloon.BalloonTipText = $Message ; \ +$balloon.BalloonTipTitle = $Title ; \ +$balloon.Text = 'tinyfiledialogs' ; \ +$balloon.Visible = $true ; \ +$balloon.ShowBalloonTip(5000)};\ +Show-BalloonTip"); + + if (aTitle && wcslen(aTitle)) + { + wcscat(lDialogString, L" -Title '"); + wcscat(lDialogString, aTitle); + wcscat(lDialogString, L"'"); + } + if (aMessage && wcslen(aMessage)) + { + wcscat(lDialogString, L" -Message '"); + wcscat(lDialogString, aMessage); + wcscat(lDialogString, L"'"); + } + if (aMessage && wcslen(aIconType)) + { + wcscat(lDialogString, L" -IconType '"); + wcscat(lDialogString, aIconType); + wcscat(lDialogString, L"'"); + } + wcscat(lDialogString, L"\""); + + hiddenConsoleW(lDialogString, aTitle, 0); + free(lDialogString); + return 1; +}*/ + + +/* return has only meaning for tinyfd_query */ +int tinyfd_notifyPopupW( + wchar_t const* aTitle, /* NULL or L"" */ + wchar_t const* aMessage, /* NULL or L"" may contain \n \t */ + wchar_t const* aIconType) /* L"info" L"warning" L"error" */ +{ + wchar_t* lDialogString; + size_t lTitleLen; + size_t lMessageLen; + size_t lDialogStringLen; - if (aDialogType && !strcmp("okcancel", aDialogType)) { - if (!aDefaultButton) { - strcat(lDialogString, "--defaultno "); - } - strcat(lDialogString, "--yes-label \"Ok\" --no-label \"Cancel\" --yesno "); - } else { - strcat(lDialogString, "--msgbox "); + FILE* lIn; - } - strcat(lDialogString, "\""); - if (aMessage && strlen(aMessage)) { - strcat(lDialogString, aMessage); - } - strcat(lDialogString, "\" "); - - strcat(lDialogString, "10 60 ) 2>&1;if [ $? = 0 ];then echo 1;else echo 0;fi"); - } else if (!isTerminalRunning() && terminalName()) { - strcpy(lDialogString, terminalName()); - strcat(lDialogString, "'"); - if (!gWarningDisplayed) { - gWarningDisplayed = 1; - strcat(lDialogString, "echo \""); - strcat(lDialogString, gTitle); - strcat(lDialogString, "\";"); - strcat(lDialogString, "echo \""); - strcat(lDialogString, tinyfd_needs); - strcat(lDialogString, "\";echo;echo;"); - } - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, "echo \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\";echo;"); - } - if (aMessage && strlen(aMessage)) { - strcat(lDialogString, "echo \""); - strcat(lDialogString, aMessage); - strcat(lDialogString, "\"; "); - } - if (aDialogType && !strcmp("okcancel", aDialogType)) { - strcat(lDialogString, "echo -n \"[O]kay/[C]ancel: \"; "); - strcat(lDialogString, "stty sane -echo;"); - strcat(lDialogString, "answer=$( while ! head -c 1 | grep -i [oc];do true ;done);"); - strcat(lDialogString, "if echo \"$answer\" | grep -iq \"^o\";then\n"); - strcat(lDialogString, "\techo 1\nelse\n\techo 0\nfi"); - } else { - strcat(lDialogString, "echo -n \"press enter to continue \"; "); - strcat(lDialogString, "stty sane -echo;"); - strcat(lDialogString, "answer=$( while ! head -c 1;do true ;done);echo 1"); - } - strcat(lDialogString, " >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); - } else { - if (!gWarningDisplayed) { - gWarningDisplayed = 1; - printf("\n\n%s\n", gTitle); - printf("%s\n\n", tinyfd_needs); - } - if (aTitle && strlen(aTitle)) { - printf("\n%s\n", aTitle); - } + if (aTitle && !wcscmp(aTitle, L"tinyfd_query")) { strcpy(tinyfd_response, "windows_wchar"); return 1; } - tcgetattr(0, &infoOri); - tcgetattr(0, &info); - info.c_lflag &= ~ICANON; - info.c_cc[VMIN] = 1; - info.c_cc[VTIME] = 0; - tcsetattr(0, TCSANOW, &info); - if (aDialogType && !strcmp("okcancel", aDialogType)) { - do { - if (aMessage && strlen(aMessage)) { - printf("\n%s\n", aMessage); - } - printf("[O]kay/[C]ancel: "); fflush(stdout); - lChar = tolower(getchar()); - printf("\n\n"); - } while (lChar != 'o' && lChar != 'c'); - lResult = lChar == 'o' ? 1 : 0; - } else { - if (aMessage && strlen(aMessage)) { - printf("\n%s\n\n", aMessage); - } - printf("press enter to continue "); fflush(stdout); - getchar(); - printf("\n\n"); - lResult = 1; - } - tcsetattr(0, TCSANOW, &infoOri); - free(lDialogString); - return lResult; - } + if (quoteDetectedW(aTitle)) return tinyfd_notifyPopupW(L"INVALID TITLE WITH QUOTES", aMessage, aIconType); + if (quoteDetectedW(aMessage)) return tinyfd_notifyPopupW(aTitle, L"INVALID MESSAGE WITH QUOTES", aIconType); - if (tinyfd_verbose) printf("lDialogString: %s\n", lDialogString); + lTitleLen = aTitle ? wcslen(aTitle) : 0; + lMessageLen = aMessage ? wcslen(aMessage) : 0; + lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen; + lDialogString = (wchar_t*)malloc(2 * lDialogStringLen); + if (!lDialogString) return 0; - if (!(lIn = popen(lDialogString, "r"))) { - free(lDialogString); - return 0; - } - while (fgets(lBuff, sizeof(lBuff), lIn) != NULL) { - } + swprintf(lDialogString, +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + lDialogStringLen, +#endif + L"%ls\\tinyfd.hta", _wgetenv(L"TEMP")); + + lIn = _wfopen(lDialogString, L"w"); + if (!lIn) + { + free(lDialogString); + return 0; + } + + wcscpy(lDialogString, L"\n\ +\n\ +\n\ +"); + if ( aTitle && wcslen(aTitle) ) wcscat(lDialogString, aTitle); + wcscat(lDialogString, L"\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +
\n"); + + wcscat(lDialogString, aMessage ? aMessage : L""); + + wcscat(lDialogString, L"\n\ +\n\ +\n\ +"); + + fputws(lDialogString, lIn); + fclose(lIn); + + if (aTitle && wcslen(aTitle)) + { + wcscat(lDialogString, L" -Title '"); + wcscat(lDialogString, aTitle); + wcscat(lDialogString, L"'"); + } + if (aMessage && wcslen(aMessage)) + { + wcscat(lDialogString, L" -Message '"); + wcscat(lDialogString, aMessage); + wcscat(lDialogString, L"'"); + } + if (aMessage && wcslen(aIconType)) + { + wcscat(lDialogString, L" -IconType '"); + wcscat(lDialogString, aIconType); + wcscat(lDialogString, L"'"); + } + wcscat(lDialogString, L"\""); + + /* wprintf ( L"lDialogString: %ls\n" , lDialogString ) ; */ + wcscpy(lDialogString, + L"cmd.exe /c mshta.exe \"%TEMP%\\tinyfd.hta\""); + + hiddenConsoleW(lDialogString, aTitle, 0); + free(lDialogString); + return 1; +} - pclose(lIn); - /* printf( "lBuff: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ - if (lBuff[strlen(lBuff) - 1] == '\n') { - lBuff[strlen(lBuff) - 1] = '\0'; - } - /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ +wchar_t * tinyfd_inputBoxW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aMessage, /* NULL or L"" (\n and \t have no effect) */ + wchar_t const * aDefaultInput) /* L"" , if NULL it's a passwordBox */ +{ + static wchar_t lBuff[MAX_PATH_OR_CMD]; + wchar_t * lDialogString; + FILE * lIn; + FILE * lFile; + int lResult; + size_t lTitleLen; + size_t lMessageLen; + size_t lDialogStringLen; + + if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } + + if (quoteDetectedW(aTitle)) return tinyfd_inputBoxW(L"INVALID TITLE WITH QUOTES", aMessage, aDefaultInput); + if (quoteDetectedW(aMessage)) return tinyfd_inputBoxW(aTitle, L"INVALID MESSAGE WITH QUOTES", aDefaultInput); + if (quoteDetectedW(aDefaultInput)) return tinyfd_inputBoxW(aTitle, aMessage, L"INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\x60 instead."); + + lTitleLen = aTitle ? wcslen(aTitle) : 0 ; + lMessageLen = aMessage ? wcslen(aMessage) : 0 ; + lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen; + lDialogString = (wchar_t *) malloc(2 * lDialogStringLen); + + if (aDefaultInput) + { + swprintf(lDialogString, +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + lDialogStringLen, +#endif + L"%ls\\tinyfd.vbs", _wgetenv(L"TEMP")); + } + else + { + swprintf(lDialogString, +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + lDialogStringLen, +#endif + L"%ls\\tinyfd.hta", _wgetenv(L"TEMP")); + } + lIn = _wfopen(lDialogString, L"w"); + if (!lIn) + { + free(lDialogString); + return NULL; + } + + if ( aDefaultInput ) + { + wcscpy(lDialogString, L"Dim result:result=InputBox(\""); + if (aMessage && wcslen(aMessage)) + { + wcscpy(lBuff, aMessage); + replaceWchar(lBuff, L'\n', L' '); + wcscat(lDialogString, lBuff); + } + wcscat(lDialogString, L"\",\""); + if (aTitle) wcscat(lDialogString, aTitle); + wcscat(lDialogString, L"\",\""); + + if (aDefaultInput && wcslen(aDefaultInput)) + { + wcscpy(lBuff, aDefaultInput); + replaceWchar(lBuff, L'\n', L' '); + wcscat(lDialogString, lBuff); + } + wcscat(lDialogString, L"\"):If IsEmpty(result) then:WScript.Echo 0"); + wcscat(lDialogString, L":Else: WScript.Echo \"1\" & result : End If"); + } + else + { + wcscpy(lDialogString, L"\n\ +\n\ +\n\ +"); + if (aTitle) wcscat(lDialogString, aTitle); + wcscat(lDialogString, L"\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +
\n"); + + wcscat(lDialogString, aMessage ? aMessage : L""); + + wcscat(lDialogString, L"\n\ +\n\ +\n\ +\n\ +
\n\ +

\n\ +\n\ +
\n\ +
\n"); + + wcscat(lDialogString, L"\n\ +\n\ +\n\ +\n\ +
\n\ +
\n\ +
\n\ +\n\ +\n\ +" ) ; + } + fputws(lDialogString, lIn); + fclose(lIn); + + if (aDefaultInput) + { + swprintf(lDialogString, +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + lDialogStringLen, +#endif + L"%ls\\tinyfd.txt",_wgetenv(L"TEMP")); - lResult = !strcmp(lBuff, "2") ? 2 : !strcmp(lBuff, "1") ? 1 : 0; +#ifdef TINYFD_NOCCSUNICODE + lFile = _wfopen(lDialogString, L"w"); + fputc(0xFF, lFile); + fputc(0xFE, lFile); +#else + lFile = _wfopen(lDialogString, L"wt, ccs=UNICODE"); /*or ccs=UTF-16LE*/ +#endif + fclose(lFile); + + wcscpy(lDialogString, L"cmd.exe /c cscript.exe //U //Nologo "); + wcscat(lDialogString, L"\"%TEMP%\\tinyfd.vbs\" "); + wcscat(lDialogString, L">> \"%TEMP%\\tinyfd.txt\""); + } + else + { + wcscpy(lDialogString, + L"cmd.exe /c mshta.exe \"%TEMP%\\tinyfd.hta\""); + } + + /* wprintf ( "lDialogString: %ls\n" , lDialogString ) ; */ + + hiddenConsoleW(lDialogString, aTitle, 1); + + swprintf(lDialogString, +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + lDialogStringLen, +#endif + L"%ls\\tinyfd.txt", _wgetenv(L"TEMP")); + /* wprintf(L"lDialogString: %ls\n", lDialogString); */ +#ifdef TINYFD_NOCCSUNICODE + if (!(lIn = _wfopen(lDialogString, L"r"))) +#else + if (!(lIn = _wfopen(lDialogString, L"rt, ccs=UNICODE"))) /*or ccs=UTF-16LE*/ +#endif + { + _wremove(lDialogString); + free(lDialogString); + return NULL; + } + + memset(lBuff, 0, MAX_PATH_OR_CMD * sizeof(wchar_t) ); + +#ifdef TINYFD_NOCCSUNICODE + fgets((char *)lBuff, 2*MAX_PATH_OR_CMD, lIn); +#else + fgetws(lBuff, MAX_PATH_OR_CMD, lIn); +#endif + fclose(lIn); + wipefileW(lDialogString); + _wremove(lDialogString); + + if (aDefaultInput) + { + swprintf(lDialogString, +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + lDialogStringLen, +#endif + L"%ls\\tinyfd.vbs", _wgetenv(L"TEMP")); + } + else + { + swprintf(lDialogString, +#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + lDialogStringLen, +#endif + L"%ls\\tinyfd.hta", _wgetenv(L"TEMP")); + } + _wremove(lDialogString); + free(lDialogString); + /* wprintf( L"lBuff: %ls\n" , lBuff ) ; */ +#ifdef TINYFD_NOCCSUNICODE + lResult = !wcsncmp(lBuff+1, L"1", 1); +#else + lResult = !wcsncmp(lBuff, L"1", 1); +#endif + + /* printf( "lResult: %d \n" , lResult ) ; */ + if (!lResult) + { + return NULL ; + } + + /* wprintf( "lBuff+1: %ls\n" , lBuff+1 ) ; */ + +#ifdef TINYFD_NOCCSUNICODE + if (aDefaultInput) + { + lDialogStringLen = wcslen(lBuff) ; + lBuff[lDialogStringLen - 1] = L'\0'; + lBuff[lDialogStringLen - 2] = L'\0'; + } + return lBuff + 2; +#else + if (aDefaultInput) lBuff[wcslen(lBuff) - 1] = L'\0'; + return lBuff + 1; +#endif +} + + +wchar_t * tinyfd_saveFileDialogW( + wchar_t const * aTitle, /* NULL or "" */ + wchar_t const * aDefaultPathAndOrFile, /* NULL or "" */ + int aNumOfFilterPatterns, /* 0 */ + wchar_t const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ + wchar_t const * aSingleFilterDescription) /* NULL or "image files" */ +{ + static wchar_t lBuff[MAX_PATH_OR_CMD]; + wchar_t lDirname[MAX_PATH_OR_CMD]; + wchar_t lDialogString[MAX_PATH_OR_CMD]; + wchar_t lFilterPatterns[MAX_PATH_OR_CMD] = L""; + wchar_t * p; + wchar_t * lRetval; + wchar_t const * ldefExt = NULL; + int i; + HRESULT lHResult; + OPENFILENAMEW ofn = {0}; + + if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } + + /*if (quoteDetectedW(aTitle)) return tinyfd_saveFileDialogW(L"INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); + if (quoteDetectedW(aDefaultPathAndOrFile)) return tinyfd_saveFileDialogW(aTitle, L"INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); + if (quoteDetectedW(aSingleFilterDescription)) return tinyfd_saveFileDialogW(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, L"INVALID FILTER_DESCRIPTION WITH QUOTES"); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (quoteDetectedW(aFilterPatterns[i])) return tinyfd_saveFileDialogW(L"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL); + }*/ + + lHResult = CoInitializeEx(NULL, 0); + + getPathWithoutFinalSlashW(lDirname, aDefaultPathAndOrFile); + getLastNameW(lBuff, aDefaultPathAndOrFile); + + if (aNumOfFilterPatterns > 0) + { + ldefExt = aFilterPatterns[0]; + + if (aSingleFilterDescription && wcslen(aSingleFilterDescription)) + { + wcscpy(lFilterPatterns, aSingleFilterDescription); + wcscat(lFilterPatterns, L"\n"); + } + wcscat(lFilterPatterns, aFilterPatterns[0]); + for (i = 1; i < aNumOfFilterPatterns; i++) + { + wcscat(lFilterPatterns, L";"); + wcscat(lFilterPatterns, aFilterPatterns[i]); + } + wcscat(lFilterPatterns, L"\n"); + if (!(aSingleFilterDescription && wcslen(aSingleFilterDescription))) + { + wcscpy(lDialogString, lFilterPatterns); + wcscat(lFilterPatterns, lDialogString); + } + wcscat(lFilterPatterns, L"All Files\n*.*\n"); + p = lFilterPatterns; + while ((p = wcschr(p, L'\n')) != NULL) + { + *p = L'\0'; + p++; + } + } + + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetForegroundWindow(); + ofn.hInstance = 0; + ofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : NULL; + ofn.lpstrCustomFilter = NULL; + ofn.nMaxCustFilter = 0; + ofn.nFilterIndex = 1; + ofn.lpstrFile = lBuff; + + ofn.nMaxFile = MAX_PATH_OR_CMD; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = MAX_PATH_OR_CMD/2; + ofn.lpstrInitialDir = wcslen(lDirname) ? lDirname : NULL; + ofn.lpstrTitle = aTitle && wcslen(aTitle) ? aTitle : NULL; + ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST ; + ofn.nFileOffset = 0; + ofn.nFileExtension = 0; + ofn.lpstrDefExt = ldefExt; + ofn.lCustData = 0L; + ofn.lpfnHook = NULL; + ofn.lpTemplateName = NULL; + + if (GetSaveFileNameW(&ofn) == 0) + { + lRetval = NULL; + } + else + { + lRetval = lBuff; + } + + if (lHResult == S_OK || lHResult == S_FALSE) + { + CoUninitialize(); + } + return lRetval; +} + + +wchar_t * tinyfd_openFileDialogW( + wchar_t const * aTitle, /* NULL or "" */ + wchar_t const * aDefaultPathAndOrFile, /* NULL or "" */ + int aNumOfFilterPatterns, /* 0 */ + wchar_t const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ + wchar_t const * aSingleFilterDescription, /* NULL or "image files" */ + int aAllowMultipleSelects) /* 0 or 1 ; -1 to free allocated memory and return */ +{ + size_t lLengths[MAX_MULTIPLE_FILES]; + wchar_t lDirname[MAX_PATH_OR_CMD]; + wchar_t lFilterPatterns[MAX_PATH_OR_CMD] = L""; + wchar_t lDialogString[MAX_PATH_OR_CMD]; + wchar_t * lPointers[MAX_MULTIPLE_FILES+1]; + wchar_t * p; + int i, j; + size_t lBuffLen; + DWORD lFullBuffLen; + HRESULT lHResult; + OPENFILENAMEW ofn = { 0 }; + static wchar_t * lBuff = NULL; + + free(lBuff); + lBuff = NULL; + if (aAllowMultipleSelects < 0) return (wchar_t *)0; + + if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } + + /*if (quoteDetectedW(aTitle)) return tinyfd_openFileDialogW(L"INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); + if (quoteDetectedW(aDefaultPathAndOrFile)) return tinyfd_openFileDialogW(aTitle, L"INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); + if (quoteDetectedW(aSingleFilterDescription)) return tinyfd_openFileDialogW(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, L"INVALID FILTER_DESCRIPTION WITH QUOTES", aAllowMultipleSelects); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (quoteDetectedW(aFilterPatterns[i])) return tinyfd_openFileDialogW(L"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects); + }*/ + + if (aAllowMultipleSelects) + { + lFullBuffLen = MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; + lBuff = (wchar_t*) malloc(lFullBuffLen * sizeof(wchar_t)); + if (!lBuff) + { + lFullBuffLen = LOW_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; + lBuff = (wchar_t*) malloc( lFullBuffLen * sizeof(wchar_t)); + } + } + else + { + lFullBuffLen = MAX_PATH_OR_CMD + 1; + lBuff = (wchar_t*) malloc(lFullBuffLen * sizeof(wchar_t)); + } + if (!lBuff) return NULL; + + lHResult = CoInitializeEx(NULL, 0); + + getPathWithoutFinalSlashW(lDirname, aDefaultPathAndOrFile); + getLastNameW(lBuff, aDefaultPathAndOrFile); + + if (aNumOfFilterPatterns > 0) + { + if (aSingleFilterDescription && wcslen(aSingleFilterDescription)) + { + wcscpy(lFilterPatterns, aSingleFilterDescription); + wcscat(lFilterPatterns, L"\n"); + } + wcscat(lFilterPatterns, aFilterPatterns[0]); + for (i = 1; i < aNumOfFilterPatterns; i++) + { + wcscat(lFilterPatterns, L";"); + wcscat(lFilterPatterns, aFilterPatterns[i]); + } + wcscat(lFilterPatterns, L"\n"); + if (!(aSingleFilterDescription && wcslen(aSingleFilterDescription))) + { + wcscpy(lDialogString, lFilterPatterns); + wcscat(lFilterPatterns, lDialogString); + } + wcscat(lFilterPatterns, L"All Files\n*.*\n"); + p = lFilterPatterns; + while ((p = wcschr(p, L'\n')) != NULL) + { + *p = L'\0'; + p++; + } + } + + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = GetForegroundWindow(); + ofn.hInstance = 0; + ofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : NULL; + ofn.lpstrCustomFilter = NULL; + ofn.nMaxCustFilter = 0; + ofn.nFilterIndex = 1; + ofn.lpstrFile = lBuff; + ofn.nMaxFile = lFullBuffLen; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = MAX_PATH_OR_CMD / 2; + ofn.lpstrInitialDir = wcslen(lDirname) ? lDirname : NULL; + ofn.lpstrTitle = aTitle && wcslen(aTitle) ? aTitle : NULL; + ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + ofn.nFileOffset = 0; + ofn.nFileExtension = 0; + ofn.lpstrDefExt = NULL; + ofn.lCustData = 0L; + ofn.lpfnHook = NULL; + ofn.lpTemplateName = NULL; + + if (aAllowMultipleSelects) + { + ofn.Flags |= OFN_ALLOWMULTISELECT; + } + + if (GetOpenFileNameW(&ofn) == 0) + { + free(lBuff); + lBuff = NULL; + } + else + { + lBuffLen = wcslen(lBuff); + lPointers[0] = lBuff + lBuffLen + 1; + if (aAllowMultipleSelects && (lPointers[0][0] != L'\0')) + { + i = 0; + do + { + lLengths[i] = wcslen(lPointers[i]); + lPointers[i + 1] = lPointers[i] + lLengths[i] + 1; + i++; + } while (lPointers[i][0] != L'\0' && i < MAX_MULTIPLE_FILES ); + + if (i > MAX_MULTIPLE_FILES) + { + free(lBuff); + lBuff = NULL; + } + else + { + i--; + p = lBuff + lFullBuffLen - 1; + *p = L'\0'; + for (j = i; j >= 0; j--) + { + p -= lLengths[j]; + memmove(p, lPointers[j], lLengths[j] * sizeof(wchar_t)); + p--; + *p = L'\\'; + p -= lBuffLen; + memmove(p, lBuff, lBuffLen*sizeof(wchar_t)); + p--; + *p = L'|'; + } + p++; + wcscpy(lBuff, p); + lBuffLen = wcslen(lBuff); + } + } + if (lBuff) lBuff = (wchar_t*)(realloc(lBuff, (lBuffLen + 1) * sizeof(wchar_t))); + } + + if (lHResult == S_OK || lHResult == S_FALSE) + { + CoUninitialize(); + } + + return lBuff; +} + + +BOOL CALLBACK BrowseCallbackProcW_enum(HWND hWndChild, LPARAM lParam) +{ + wchar_t buf[255]; + (void)lParam; + GetClassNameW(hWndChild, buf, sizeof(buf)); + if (wcscmp(buf, L"SysTreeView32") == 0) + { + HTREEITEM hNode = TreeView_GetSelection(hWndChild); + TreeView_EnsureVisible(hWndChild, hNode); + return FALSE; + } + return TRUE; +} + + +static int __stdcall BrowseCallbackProcW(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) +{ + (void)lp; + switch (uMsg) + { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)pData); + break; + case BFFM_SELCHANGED: + EnumChildWindows(hwnd, BrowseCallbackProcW_enum, 0); + } + return 0; +} + +wchar_t * tinyfd_selectFolderDialogW( + wchar_t const * aTitle, /* NULL or "" */ + wchar_t const * aDefaultPath) /* NULL or "" */ +{ + static wchar_t lBuff[MAX_PATH_OR_CMD]; + wchar_t * lRetval; + + BROWSEINFOW bInfo; + LPITEMIDLIST lpItem; + HRESULT lHResult; + + if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } + + /*if (quoteDetectedW(aTitle)) return tinyfd_selectFolderDialogW(L"INVALID TITLE WITH QUOTES", aDefaultPath); + if (quoteDetectedW(aDefaultPath)) return tinyfd_selectFolderDialogW(aTitle, L"INVALID DEFAULT_PATH WITH QUOTES");*/ + + lHResult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + bInfo.hwndOwner = GetForegroundWindow(); + bInfo.pidlRoot = NULL; + bInfo.pszDisplayName = lBuff; + bInfo.lpszTitle = aTitle && wcslen(aTitle) ? aTitle : NULL; + if (lHResult == S_OK || lHResult == S_FALSE) + { + bInfo.ulFlags = BIF_USENEWUI; + } + bInfo.lpfn = BrowseCallbackProcW; + bInfo.lParam = (LPARAM)aDefaultPath; + bInfo.iImage = -1; + + lpItem = SHBrowseForFolderW(&bInfo); + if (!lpItem) + { + lRetval = NULL; + } + else + { + SHGetPathFromIDListW(lpItem, lBuff); + lRetval = lBuff ; + } + + if (lHResult == S_OK || lHResult == S_FALSE) + { + CoUninitialize(); + } + return lRetval; +} + + +wchar_t * tinyfd_colorChooserW( + wchar_t const * aTitle, /* NULL or "" */ + wchar_t const * aDefaultHexRGB, /* NULL or "#FF0000"*/ + unsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */ + unsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */ +{ + static wchar_t lResultHexRGB[8]; + CHOOSECOLORW cc; + COLORREF crCustColors[16]; + unsigned char lDefaultRGB[3]; + int lRet; + + HRESULT lHResult; + + if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } + + /*if (quoteDetectedW(aTitle)) return tinyfd_colorChooserW(L"INVALID TITLE WITH QUOTES", aDefaultHexRGB, aDefaultRGB, aoResultRGB); + if (quoteDetectedW(aDefaultHexRGB)) return tinyfd_colorChooserW(aTitle, L"INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultRGB, aoResultRGB);*/ + + lHResult = CoInitializeEx(NULL, 0); + + if ( aDefaultHexRGB && wcslen(aDefaultHexRGB) ) + { + Hex2RGBW(aDefaultHexRGB, lDefaultRGB); + } + else + { + lDefaultRGB[0] = aDefaultRGB[0]; + lDefaultRGB[1] = aDefaultRGB[1]; + lDefaultRGB[2] = aDefaultRGB[2]; + } + + /* we can't use aTitle */ + cc.lStructSize = sizeof(CHOOSECOLOR); + cc.hwndOwner = GetForegroundWindow(); + cc.hInstance = NULL; + cc.rgbResult = RGB(lDefaultRGB[0], lDefaultRGB[1], lDefaultRGB[2]); + cc.lpCustColors = crCustColors; + cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR ; + cc.lCustData = 0; + cc.lpfnHook = NULL; + cc.lpTemplateName = NULL; + + lRet = ChooseColorW(&cc); + + if (!lRet) + { + return NULL; + } + + aoResultRGB[0] = GetRValue(cc.rgbResult); + aoResultRGB[1] = GetGValue(cc.rgbResult); + aoResultRGB[2] = GetBValue(cc.rgbResult); + + RGB2HexW(aoResultRGB, lResultHexRGB); + + if (lHResult == S_OK || lHResult == S_FALSE) + { + CoUninitialize(); + } + + return lResultHexRGB; +} + + +static int messageBoxWinGui( + char const * aTitle, /* NULL or "" */ + char const * aMessage, /* NULL or "" may contain \n and \t */ + char const * aDialogType, /* "ok" "okcancel" "yesno" "yesnocancel" */ + char const * aIconType, /* "info" "warning" "error" "question" */ + int aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ +{ + int lIntRetVal; + wchar_t lTitle[128] = L""; + wchar_t * lMessage = NULL; + wchar_t lDialogType[16] = L""; + wchar_t lIconType[16] = L""; + wchar_t * lTmpWChar; + + if (aTitle) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); + else lTmpWChar = tinyfd_mbcsTo16(aTitle); + wcscpy(lTitle, lTmpWChar); + } + if (aMessage) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage); + else lTmpWChar = tinyfd_mbcsTo16(aMessage); + lMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t)); + if (lMessage) wcscpy(lMessage, lTmpWChar); + } + if (aDialogType) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDialogType); + else lTmpWChar = tinyfd_mbcsTo16(aDialogType); + wcscpy(lDialogType, lTmpWChar); + } + if (aIconType) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aIconType); + else lTmpWChar = tinyfd_mbcsTo16(aIconType); + wcscpy(lIconType, lTmpWChar); + } + + lIntRetVal = tinyfd_messageBoxW(lTitle, lMessage, lDialogType, lIconType, aDefaultButton); + + free(lMessage); + + return lIntRetVal; +} + + +static int notifyWinGui( + char const * aTitle, /* NULL or "" */ + char const * aMessage, /* NULL or "" may NOT contain \n nor \t */ + char const * aIconType) +{ + wchar_t lTitle[128] = L""; + wchar_t * lMessage = NULL; + wchar_t lIconType[16] = L""; + wchar_t * lTmpWChar; + + if (aTitle) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); + else lTmpWChar = tinyfd_mbcsTo16(aTitle); + wcscpy(lTitle, lTmpWChar); + } + if (aMessage) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage); + else lTmpWChar = tinyfd_mbcsTo16(aMessage); + lMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t)); + if (lMessage) wcscpy(lMessage, lTmpWChar); + } + if (aIconType) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aIconType); + else lTmpWChar = tinyfd_mbcsTo16(aIconType); + wcscpy(lIconType, lTmpWChar); + } + + tinyfd_notifyPopupW(lTitle, lMessage, lIconType); + + free(lMessage); + + return 1; +} + + +static int inputBoxWinGui( + char * aoBuff, + char const * aTitle, /* NULL or "" */ + char const * aMessage, /* NULL or "" may NOT contain \n nor \t */ + char const * aDefaultInput) /* "" , if NULL it's a passwordBox */ +{ + wchar_t lTitle[128] = L""; + wchar_t * lMessage = NULL; + wchar_t lDefaultInput[MAX_PATH_OR_CMD] = L""; + wchar_t * lTmpWChar; + char * lTmpChar; + + if (aTitle) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); + else lTmpWChar = tinyfd_mbcsTo16(aTitle); + wcscpy(lTitle, lTmpWChar); + } + if (aMessage) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage); + else lTmpWChar = tinyfd_mbcsTo16(aMessage); + lMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t)); + if (lMessage) wcscpy(lMessage, lTmpWChar); + } + if (aDefaultInput) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultInput); + else lTmpWChar = tinyfd_mbcsTo16(aDefaultInput); + wcscpy(lDefaultInput, lTmpWChar); + lTmpWChar = tinyfd_inputBoxW(lTitle, lMessage, lDefaultInput); + } + else lTmpWChar = tinyfd_inputBoxW(lTitle, lMessage, NULL); + + free(lMessage); + + if (!lTmpWChar) + { + aoBuff[0] = '\0'; + return 0; + } + + if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); + else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); + + strcpy(aoBuff, lTmpChar); + + return 1; +} + + +static char * saveFileDialogWinGui( + char * aoBuff, + char const * aTitle, /* NULL or "" */ + char const * aDefaultPathAndOrFile, /* NULL or "" */ + int aNumOfFilterPatterns, /* 0 */ + char const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ + char const * aSingleFilterDescription) /* NULL or "image files" */ +{ + wchar_t lTitle[128] = L""; + wchar_t lDefaultPathAndFile[MAX_PATH_OR_CMD] = L""; + wchar_t lSingleFilterDescription[128] = L""; + wchar_t * * lFilterPatterns; + wchar_t * lTmpWChar; + char * lTmpChar; + int i; + + lFilterPatterns = (wchar_t **) malloc(aNumOfFilterPatterns*sizeof(wchar_t *)); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aFilterPatterns[i]); + else lTmpWChar = tinyfd_mbcsTo16(aFilterPatterns[i]); + lFilterPatterns[i] = (wchar_t *) malloc((wcslen(lTmpWChar) + 1) * sizeof(wchar_t *)); + if (lFilterPatterns[i]) wcscpy(lFilterPatterns[i], lTmpWChar); + } + + if (aTitle) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); + else lTmpWChar = tinyfd_mbcsTo16(aTitle); + wcscpy(lTitle, lTmpWChar); + } + if (aDefaultPathAndOrFile) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPathAndOrFile); + else lTmpWChar = tinyfd_mbcsTo16(aDefaultPathAndOrFile); + wcscpy(lDefaultPathAndFile, lTmpWChar); + } + if (aSingleFilterDescription) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aSingleFilterDescription); + else lTmpWChar = tinyfd_mbcsTo16(aSingleFilterDescription); + wcscpy(lSingleFilterDescription, lTmpWChar); + } + + lTmpWChar = tinyfd_saveFileDialogW( + lTitle, + lDefaultPathAndFile, + aNumOfFilterPatterns, + (wchar_t const**) lFilterPatterns, /*stupid cast for gcc*/ + lSingleFilterDescription); + + for (i = 0; i < aNumOfFilterPatterns; i++) + { + free(lFilterPatterns[i]); + } + free(lFilterPatterns); + + if (!lTmpWChar) + { + return NULL; + } + + if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); + else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); + strcpy(aoBuff, lTmpChar); + if (tinyfd_winUtf8) (void)tinyfd_utf16to8(NULL); + else (void)tinyfd_utf16toMbcs(NULL); + + return aoBuff; +} + + +static char * openFileDialogWinGui( + char const * aTitle, /* NULL or "" */ + char const * aDefaultPathAndOrFile, /* NULL or "" */ + int aNumOfFilterPatterns, /* 0 */ + char const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ + char const * aSingleFilterDescription, /* NULL or "image files" */ + int aAllowMultipleSelects) /* 0 or 1 */ +{ + wchar_t lTitle[128] = L""; + wchar_t lDefaultPathAndFile[MAX_PATH_OR_CMD] = L""; + wchar_t lSingleFilterDescription[128] = L""; + wchar_t * * lFilterPatterns; + wchar_t * lTmpWChar; + char * lTmpChar; + int i; + + lFilterPatterns = (wchar_t * *) malloc(aNumOfFilterPatterns*sizeof(wchar_t *)); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aFilterPatterns[i]); + else lTmpWChar = tinyfd_mbcsTo16(aFilterPatterns[i]); + lFilterPatterns[i] = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)*sizeof(wchar_t *)); + if (lFilterPatterns[i]) wcscpy(lFilterPatterns[i], lTmpWChar); + } + + if (aTitle) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); + else lTmpWChar = tinyfd_mbcsTo16(aTitle); + wcscpy(lTitle, lTmpWChar); + } + if (aDefaultPathAndOrFile) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPathAndOrFile); + else lTmpWChar = tinyfd_mbcsTo16(aDefaultPathAndOrFile); + wcscpy(lDefaultPathAndFile, lTmpWChar); + } + if (aSingleFilterDescription) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aSingleFilterDescription); + else lTmpWChar = tinyfd_mbcsTo16(aSingleFilterDescription); + wcscpy(lSingleFilterDescription, lTmpWChar); + } + + lTmpWChar = tinyfd_openFileDialogW( + lTitle, + lDefaultPathAndFile, + aNumOfFilterPatterns, + (wchar_t const**) lFilterPatterns, /*stupid cast for gcc*/ + lSingleFilterDescription, + aAllowMultipleSelects); + + for (i = 0; i < aNumOfFilterPatterns; i++) + { + free(lFilterPatterns[i]); + } + free(lFilterPatterns); + + if (!lTmpWChar) return NULL; + + if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); + else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); + (void)tinyfd_openFileDialogW(NULL, NULL, 0, NULL, NULL, -1); + + return lTmpChar; +} + + +static char * selectFolderDialogWinGui( + char * aoBuff, + char const * aTitle, /* NULL or "" */ + char const * aDefaultPath) /* NULL or "" */ +{ + wchar_t lTitle[128] = L""; + wchar_t lDefaultPath[MAX_PATH_OR_CMD] = L""; + wchar_t * lTmpWChar; + char * lTmpChar; + + if (aTitle) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); + else lTmpWChar = tinyfd_mbcsTo16(aTitle); + wcscpy(lTitle, lTmpWChar); + } + if (aDefaultPath) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPath); + else lTmpWChar = tinyfd_mbcsTo16(aDefaultPath); + wcscpy(lDefaultPath, lTmpWChar); + } + + lTmpWChar = tinyfd_selectFolderDialogW( + lTitle, + lDefaultPath); + + if (!lTmpWChar) + { + return NULL; + } + + if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); + else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); + strcpy(aoBuff, lTmpChar); + + return aoBuff; +} + + +static char * colorChooserWinGui( + char const * aTitle, /* NULL or "" */ + char const * aDefaultHexRGB, /* NULL or "#FF0000"*/ + unsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */ + unsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */ +{ + static char lResultHexRGB[8]; + + wchar_t lTitle[128]; + wchar_t * lTmpWChar; + char * lTmpChar; + wchar_t lDefaultHexRGB[16] = L""; + + if (aTitle) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); + else lTmpWChar = tinyfd_mbcsTo16(aTitle); + wcscpy(lTitle, lTmpWChar); + } + if (aDefaultHexRGB) + { + if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultHexRGB); + else lTmpWChar = tinyfd_mbcsTo16(aDefaultHexRGB); + wcscpy(lDefaultHexRGB, lTmpWChar); + } + + lTmpWChar = tinyfd_colorChooserW( + lTitle, + lDefaultHexRGB, + aDefaultRGB, + aoResultRGB ); + + if (!lTmpWChar) + { + return NULL; + } + + if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); + else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); + strcpy(lResultHexRGB, lTmpChar); + + return lResultHexRGB; +} + + +static int dialogPresent(void) +{ + static int lDialogPresent = -1 ; + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + char const * lString = "dialog.exe"; + if (!tinyfd_allowCursesDialogs) return 0; + if (lDialogPresent < 0) + { + lIn = _popen("where dialog.exe", "r"); + if ( ! lIn ) + { + lDialogPresent = 0 ; + return 0 ; + } + while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + {} + _pclose( lIn ) ; + if ( lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + if ( strcmp(lBuff+strlen(lBuff)-strlen(lString),lString) ) + { + lDialogPresent = 0 ; + } + else + { + lDialogPresent = 1 ; + } + } + return lDialogPresent; +} + + +static int messageBoxWinConsole( + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" may contain \n and \t */ + char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ + char const * aIconType , /* "info" "warning" "error" "question" */ + int aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ +{ + char lDialogString[MAX_PATH_OR_CMD]; + char lDialogFile[MAX_PATH_OR_CMD]; + FILE * lIn; + char lBuff[MAX_PATH_OR_CMD] = ""; + (void)aIconType; + + strcpy(lDialogString, "dialog "); + if (aTitle && strlen(aTitle)) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + if ( aDialogType && ( !strcmp( "okcancel" , aDialogType ) + || !strcmp("yesno", aDialogType) || !strcmp("yesnocancel", aDialogType) ) ) + { + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, "tab: move focus") ; + strcat(lDialogString, "\" ") ; + } + + if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) + { + if ( ! aDefaultButton ) + { + strcat( lDialogString , "--defaultno " ) ; + } + strcat( lDialogString , + "--yes-label \"Ok\" --no-label \"Cancel\" --yesno " ) ; + } + else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) + { + if ( ! aDefaultButton ) + { + strcat( lDialogString , "--defaultno " ) ; + } + strcat( lDialogString , "--yesno " ) ; + } + else if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + if (!aDefaultButton) + { + strcat(lDialogString, "--defaultno "); + } + strcat(lDialogString, "--menu "); + } + else + { + strcat( lDialogString , "--msgbox " ) ; + } + + strcat( lDialogString , "\"" ) ; + if ( aMessage && strlen(aMessage) ) + { + tfd_replaceSubStr( aMessage , "\n" , "\\n" , lBuff ) ; + strcat(lDialogString, lBuff) ; + lBuff[0]='\0'; + } + strcat(lDialogString, "\" "); + + if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + strcat(lDialogString, "0 60 0 Yes \"\" No \"\""); + strcat(lDialogString, "2>>"); + } + else + { + strcat(lDialogString, "10 60"); + strcat(lDialogString, " && echo 1 > "); + } + + strcpy(lDialogFile, getenv("TEMP")); + strcat(lDialogFile, "\\tinyfd.txt"); + strcat(lDialogString, lDialogFile); + + /*if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ;*/ + system( lDialogString ) ; + + if (!(lIn = fopen(lDialogFile, "r"))) + { + remove(lDialogFile); + return 0 ; + } + while (fgets(lBuff, sizeof(lBuff), lIn) != NULL) + {} + fclose(lIn); + remove(lDialogFile); + if ( lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + + /* if (tinyfd_verbose) printf("lBuff: %s\n", lBuff); */ + if ( ! strlen(lBuff) ) + { + return 0; + } + + if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + if (lBuff[0] == 'Y') return 1; + else return 2; + } + + return 1; +} + + +static int inputBoxWinConsole( + char * aoBuff , + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" may NOT contain \n nor \t */ + char const * aDefaultInput ) /* "" , if NULL it's a passwordBox */ +{ + char lDialogString[MAX_PATH_OR_CMD]; + char lDialogFile[MAX_PATH_OR_CMD]; + FILE * lIn; + int lResult; + + strcpy(lDialogFile, getenv("TEMP")); + strcat(lDialogFile, "\\tinyfd.txt"); + strcpy(lDialogString , "echo|set /p=1 >" ) ; + strcat(lDialogString, lDialogFile); + strcat( lDialogString , " & " ) ; + + strcat( lDialogString , "dialog " ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, "tab: move focus") ; + if ( ! aDefaultInput ) + { + strcat(lDialogString, " (sometimes nothing, no blink nor star, is shown in text field)") ; + } + + strcat(lDialogString, "\" ") ; + + if ( ! aDefaultInput ) + { + strcat( lDialogString , "--insecure --passwordbox" ) ; + } + else + { + strcat( lDialogString , "--inputbox" ) ; + } + strcat( lDialogString , " \"" ) ; + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage) ; + } + strcat(lDialogString,"\" 10 60 ") ; + if ( aDefaultInput && strlen(aDefaultInput) ) + { + strcat(lDialogString, "\"") ; + strcat(lDialogString, aDefaultInput) ; + strcat(lDialogString, "\" ") ; + } + + strcat(lDialogString, "2>>"); + strcpy(lDialogFile, getenv("TEMP")); + strcat(lDialogFile, "\\tinyfd.txt"); + strcat(lDialogString, lDialogFile); + strcat(lDialogString, " || echo 0 > "); + strcat(lDialogString, lDialogFile); + + /* printf( "lDialogString: %s\n" , lDialogString ) ; */ + system( lDialogString ) ; + + if (!(lIn = fopen(lDialogFile, "r"))) + { + remove(lDialogFile); + aoBuff[0] = '\0'; + return 0; + } + while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) + {} + fclose(lIn); + + wipefile(lDialogFile); + remove(lDialogFile); + if ( aoBuff[strlen( aoBuff ) -1] == '\n' ) + { + aoBuff[strlen( aoBuff ) -1] = '\0' ; + } + /* printf( "aoBuff: %s\n" , aoBuff ) ; */ + + /* printf( "aoBuff: %s len: %lu \n" , aoBuff , strlen(aoBuff) ) ; */ + lResult = strncmp( aoBuff , "1" , 1) ? 0 : 1 ; + /* printf( "lResult: %d \n" , lResult ) ; */ + if ( ! lResult ) + { + aoBuff[0] = '\0'; + return 0 ; + } + /* printf( "aoBuff+1: %s\n" , aoBuff+1 ) ; */ + strcpy(aoBuff, aoBuff+3); + return 1; +} + + +static char * saveFileDialogWinConsole( + char * aoBuff , + char const * aTitle , /* NULL or "" */ + char const * aDefaultPathAndOrFile ) /* NULL or "" */ +{ + char lDialogString[MAX_PATH_OR_CMD]; + char lPathAndFile[MAX_PATH_OR_CMD] = ""; + FILE * lIn; + + strcpy( lDialogString , "dialog " ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, + "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; + strcat(lDialogString, "\" ") ; + + strcat( lDialogString , "--fselect \"" ) ; + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + /* dialog.exe uses unix separators even on windows */ + strcpy(lPathAndFile, aDefaultPathAndOrFile); + replaceChr( lPathAndFile , '\\' , '/' ) ; + } + + /* dialog.exe needs at least one separator */ + if ( ! strchr(lPathAndFile, '/') ) + { + strcat(lDialogString, "./") ; + } + strcat(lDialogString, lPathAndFile) ; + strcat(lDialogString, "\" 0 60 2>"); + strcpy(lPathAndFile, getenv("TEMP")); + strcat(lPathAndFile, "\\tinyfd.txt"); + strcat(lDialogString, lPathAndFile); + + /* printf( "lDialogString: %s\n" , lDialogString ) ; */ + system( lDialogString ) ; + + if (!(lIn = fopen(lPathAndFile, "r"))) + { + remove(lPathAndFile); + return NULL; + } + while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) + {} + fclose(lIn); + remove(lPathAndFile); + replaceChr( aoBuff , '/' , '\\' ) ; + /* printf( "aoBuff: %s\n" , aoBuff ) ; */ + getLastName(lDialogString,aoBuff); + if ( ! strlen(lDialogString) ) + { + return NULL; + } + return aoBuff; +} + + +static char * openFileDialogWinConsole( + char const * aTitle , /* NULL or "" */ + char const * aDefaultPathAndOrFile ) /* NULL or "" */ +{ + char lFilterPatterns[MAX_PATH_OR_CMD] = ""; + char lDialogString[MAX_PATH_OR_CMD] ; + FILE * lIn; + + static char aoBuff[MAX_PATH_OR_CMD]; + + strcpy( lDialogString , "dialog " ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, + "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; + strcat(lDialogString, "\" ") ; + + strcat( lDialogString , "--fselect \"" ) ; + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + /* dialog.exe uses unix separators even on windows */ + strcpy(lFilterPatterns, aDefaultPathAndOrFile); + replaceChr( lFilterPatterns , '\\' , '/' ) ; + } + + /* dialog.exe needs at least one separator */ + if ( ! strchr(lFilterPatterns, '/') ) + { + strcat(lDialogString, "./") ; + } + strcat(lDialogString, lFilterPatterns) ; + strcat(lDialogString, "\" 0 60 2>"); + strcpy(lFilterPatterns, getenv("TEMP")); + strcat(lFilterPatterns, "\\tinyfd.txt"); + strcat(lDialogString, lFilterPatterns); + + /* printf( "lDialogString: %s\n" , lDialogString ) ; */ + system( lDialogString ) ; + + if (!(lIn = fopen(lFilterPatterns, "r"))) + { + remove(lFilterPatterns); + return NULL; + } + while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) + {} + fclose(lIn); + remove(lFilterPatterns); + replaceChr( aoBuff , '/' , '\\' ) ; + /* printf( "aoBuff: %s\n" , aoBuff ) ; */ + return aoBuff; +} + + +static char * selectFolderDialogWinConsole( + char * aoBuff , + char const * aTitle , /* NULL or "" */ + char const * aDefaultPath ) /* NULL or "" */ +{ + char lDialogString[MAX_PATH_OR_CMD] ; + char lString[MAX_PATH_OR_CMD] ; + FILE * lIn ; + + strcpy( lDialogString , "dialog " ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, + "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; + strcat(lDialogString, "\" ") ; + + strcat( lDialogString , "--dselect \"" ) ; + if ( aDefaultPath && strlen(aDefaultPath) ) + { + /* dialog.exe uses unix separators even on windows */ + strcpy(lString, aDefaultPath) ; + ensureFinalSlash(lString); + replaceChr( lString , '\\' , '/' ) ; + strcat(lDialogString, lString) ; + } + else + { + /* dialog.exe needs at least one separator */ + strcat(lDialogString, "./") ; + } + strcat(lDialogString, "\" 0 60 2>"); + strcpy(lString, getenv("TEMP")); + strcat(lString, "\\tinyfd.txt"); + strcat(lDialogString, lString); + + /* printf( "lDialogString: %s\n" , lDialogString ) ; */ + system( lDialogString ) ; + + if (!(lIn = fopen(lString, "r"))) + { + remove(lString); + return NULL; + } + while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) + {} + fclose(lIn); + remove(lString); + replaceChr( aoBuff , '/' , '\\' ) ; + /* printf( "aoBuff: %s\n" , aoBuff ) ; */ + return aoBuff; +} + +static void writeUtf8( char const * aUtf8String ) +{ + unsigned long lNum; + void * lConsoleHandle; + wchar_t * lTmpWChar; + + lConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); + lTmpWChar = tinyfd_utf8to16(aUtf8String); + (void)WriteConsoleW(lConsoleHandle, lTmpWChar, (DWORD) wcslen(lTmpWChar), &lNum, NULL); +} + + +int tinyfd_messageBox( + char const * aTitle, /* NULL or "" */ + char const * aMessage, /* NULL or "" may contain \n and \t */ + char const * aDialogType, /* "ok" "okcancel" "yesno" "yesnocancel" */ + char const * aIconType, /* "info" "warning" "error" "question" */ + int aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ +{ + char lChar; + UINT lOriginalCP = 0; + UINT lOriginalOutputCP = 0; + + if (tfd_quoteDetected(aTitle)) return tinyfd_messageBox("INVALID TITLE WITH QUOTES", aMessage, aDialogType, aIconType, aDefaultButton); + if (tfd_quoteDetected(aMessage)) return tinyfd_messageBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDialogType, aIconType, aDefaultButton); + + if ((!tinyfd_forceConsole || !(GetConsoleWindow() || dialogPresent())) + && (!getenv("SSH_CLIENT") || getenvDISPLAY())) + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "windows"); return 1; } + return messageBoxWinGui(aTitle, aMessage, aDialogType, aIconType, aDefaultButton); + } + else if (dialogPresent()) + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return 0; } + return messageBoxWinConsole( + aTitle, aMessage, aDialogType, aIconType, aDefaultButton); + } + else + { + if (!tinyfd_winUtf8) + { + lOriginalCP = GetConsoleCP(); + lOriginalOutputCP = GetConsoleOutputCP(); + (void)SetConsoleCP(GetACP()); + (void)SetConsoleOutputCP(GetACP()); + } + + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return 0; } + if (!gWarningDisplayed && !tinyfd_forceConsole) + { + gWarningDisplayed = 1; + printf("\n\n%s\n", gTitle); + printf("%s\n\n", tinyfd_needs); + } + + if (aTitle && strlen(aTitle)) + { + printf("\n"); + if (tinyfd_winUtf8) writeUtf8(aTitle); + else printf("%s", aTitle); + printf("\n\n"); + } + if (aDialogType && !strcmp("yesno", aDialogType)) + { + do + { + if (aMessage && strlen(aMessage)) + { + if (tinyfd_winUtf8) writeUtf8(aMessage); + else printf("%s", aMessage); + printf("\n"); + } + printf("y/n: "); + lChar = (char)tolower(_getch()); + printf("\n\n"); + } while (lChar != 'y' && lChar != 'n'); + if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } + return lChar == 'y' ? 1 : 0; + } + else if (aDialogType && !strcmp("okcancel", aDialogType)) + { + do + { + if (aMessage && strlen(aMessage)) + { + if (tinyfd_winUtf8) writeUtf8(aMessage); + else printf("%s", aMessage); + printf("\n"); + } + printf("[O]kay/[C]ancel: "); + lChar = (char)tolower(_getch()); + printf("\n\n"); + } while (lChar != 'o' && lChar != 'c'); + if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } + return lChar == 'o' ? 1 : 0; + } + else if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + do + { + if (aMessage && strlen(aMessage)) + { + if (tinyfd_winUtf8) writeUtf8(aMessage); + else printf("%s", aMessage); + printf("\n"); + } + printf("[Y]es/[N]o/[C]ancel: "); + lChar = (char)tolower(_getch()); + printf("\n\n"); + } while (lChar != 'y' && lChar != 'n' && lChar != 'c'); + if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } + return (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0; + } + else + { + if (aMessage && strlen(aMessage)) + { + if (tinyfd_winUtf8) writeUtf8(aMessage); + else printf("%s", aMessage); + printf("\n\n"); + } + printf("press enter to continue "); + lChar = (char)_getch(); + printf("\n\n"); + if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } + return 1; + } + } +} + + +/* return has only meaning for tinyfd_query */ +int tinyfd_notifyPopup( + char const * aTitle, /* NULL or "" */ + char const * aMessage , /* NULL or "" may contain \n \t */ + char const * aIconType ) /* "info" "warning" "error" */ +{ + if (tfd_quoteDetected(aTitle)) return tinyfd_notifyPopup("INVALID TITLE WITH QUOTES", aMessage, aIconType); + if (tfd_quoteDetected(aMessage)) return tinyfd_notifyPopup(aTitle, "INVALID MESSAGE WITH QUOTES", aIconType); + + if ( powershellPresent() && (!tinyfd_forceConsole || !( + GetConsoleWindow() || + dialogPresent())) + && (!getenv("SSH_CLIENT") || getenvDISPLAY())) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return 1;} + return notifyWinGui(aTitle, aMessage, aIconType); + } + else + return tinyfd_messageBox(aTitle, aMessage, "ok" , aIconType, 0); +} - /* printf( "lResult: %d\n" , lResult ) ; */ - free(lDialogString); - return lResult; -} /* returns NULL on cancel */ -static char const *selectFolderUsingInputBox(char const *const aTitle) /* NULL or "" */ -{ - static char lBuff[MAX_PATH_OR_CMD]; - char *lDialogString = NULL; - FILE *lIn; - int lResult; - int lWasBasicXterm = 0; - struct termios oldt; - struct termios newt; - char *lEOF; - size_t lTitleLen; - - lBuff[0] = '\0'; - - lTitleLen = aTitle ? strlen(aTitle) : 0; - lDialogString = (char *) malloc(MAX_PATH_OR_CMD + lTitleLen); - - if (gxmessagePresent() || gmessagePresent()) { - if (gxmessagePresent()) { - strcpy(lDialogString, "szAnswer=$(gxmessage -buttons Ok:1,Cancel:0 -center \""); - } else { - strcpy(lDialogString, "szAnswer=$(gmessage -buttons Ok:1,Cancel:0 -center \""); - } +char * tinyfd_inputBox( + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" (\n and \t have no effect) */ + char const * aDefaultInput ) /* "" , if NULL it's a passwordBox */ +{ + static char lBuff[MAX_PATH_OR_CMD] = ""; + char * lEOF; + + DWORD mode = 0; + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + + unsigned long lNum; + void * lConsoleHandle; + char * lTmpChar; + wchar_t lBuffW[1024]; + + UINT lOriginalCP = 0; + UINT lOriginalOutputCP = 0; + + if (!aTitle && !aMessage && !aDefaultInput) return lBuff; /* now I can fill lBuff from outside */ + + if (tfd_quoteDetected(aTitle)) return tinyfd_inputBox("INVALID TITLE WITH QUOTES", aMessage, aDefaultInput); + if (tfd_quoteDetected(aMessage)) return tinyfd_inputBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDefaultInput); + if (tfd_quoteDetected(aDefaultInput)) return tinyfd_inputBox(aTitle, aMessage, "INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\x60 instead."); + + mode = 0; + hStdin = GetStdHandle(STD_INPUT_HANDLE); + + if ((!tinyfd_forceConsole || !( + GetConsoleWindow() || + dialogPresent())) + && (!getenv("SSH_CLIENT") || getenvDISPLAY())) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} + lBuff[0]='\0'; + if (inputBoxWinGui(lBuff, aTitle, aMessage, aDefaultInput)) return lBuff; + else return NULL; + } + else if ( dialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + lBuff[0]='\0'; + if (inputBoxWinConsole(lBuff, aTitle, aMessage, aDefaultInput) ) return lBuff; + else return NULL; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return (char *)0;} + lBuff[0]='\0'; + if (!gWarningDisplayed && !tinyfd_forceConsole) + { + gWarningDisplayed = 1 ; + printf("\n\n%s\n", gTitle); + printf("%s\n\n", tinyfd_needs); + } + + if (!tinyfd_winUtf8) + { + lOriginalCP = GetConsoleCP(); + lOriginalOutputCP = GetConsoleOutputCP(); + (void)SetConsoleCP(GetACP()); + (void)SetConsoleOutputCP(GetACP()); + } + + if (aTitle && strlen(aTitle)) + { + printf("\n"); + if (tinyfd_winUtf8) writeUtf8(aTitle); + else printf("%s", aTitle); + printf("\n\n"); + } + if ( aMessage && strlen(aMessage) ) + { + if (tinyfd_winUtf8) writeUtf8(aMessage); + else printf("%s", aMessage); + printf("\n"); + } + printf("(ctrl-Z + enter to cancel): "); + if ( ! aDefaultInput ) + { + (void) GetConsoleMode(hStdin, &mode); + (void) SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT)); + } + if (tinyfd_winUtf8) + { + lConsoleHandle = GetStdHandle(STD_INPUT_HANDLE); + (void) ReadConsoleW(lConsoleHandle, lBuffW, MAX_PATH_OR_CMD, &lNum, NULL); + if (!aDefaultInput) + { + (void)SetConsoleMode(hStdin, mode); + printf("\n"); + } + lBuffW[lNum] = '\0'; + if (lBuffW[wcslen(lBuffW) - 1] == '\n') lBuffW[wcslen(lBuffW) - 1] = '\0'; + if (lBuffW[wcslen(lBuffW) - 1] == '\r') lBuffW[wcslen(lBuffW) - 1] = '\0'; + lTmpChar = tinyfd_utf16to8(lBuffW); + if (lTmpChar) + { + strcpy(lBuff, lTmpChar); + return lBuff; + } + else + return NULL; + } + else + { + lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); + if (!aDefaultInput) + { + (void)SetConsoleMode(hStdin, mode); + printf("\n"); + } + + if (!tinyfd_winUtf8) + { + (void)SetConsoleCP(lOriginalCP); + (void)SetConsoleOutputCP(lOriginalOutputCP); + } + + if (!lEOF) + { + return NULL; + } + printf("\n"); + if (strchr(lBuff, 27)) + { + return NULL; + } + if (lBuff[strlen(lBuff) - 1] == '\n') + { + lBuff[strlen(lBuff) - 1] = '\0'; + } + return lBuff; + } + } +} - strcat(lDialogString, "\""); - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, " -title \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\" "); - } - strcat(lDialogString, " -entrytext \""); - strcat(lDialogString, "\""); - strcat(lDialogString, ");echo $?$szAnswer"); - } else if (gdialogPresent() || xdialogPresent()) { - if (gdialogPresent()) { - strcpy(lDialogString, "(gdialog "); - } else if (xdialogPresent()) { - strcpy(lDialogString, "(Xdialog "); - } - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, "--title \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\" "); - } +char * tinyfd_saveFileDialog( + char const * aTitle , /* NULL or "" */ + char const * aDefaultPathAndOrFile , /* NULL or "" */ + int aNumOfFilterPatterns , /* 0 */ + char const * const * aFilterPatterns , /* NULL or {"*.jpg","*.png"} */ + char const * aSingleFilterDescription ) /* NULL or "image files" */ +{ + static char lBuff[MAX_PATH_OR_CMD] ; + char lString[MAX_PATH_OR_CMD] ; + char * p ; + char * lPointerInputBox; + int i; + + lBuff[0]='\0'; + + if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; + if (tfd_quoteDetected(aTitle)) return tinyfd_saveFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); + if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_saveFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); + if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_saveFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES"); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_saveFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL); + } + + + if ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) ) + && (!getenv("SSH_CLIENT") || getenvDISPLAY())) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} + p = saveFileDialogWinGui(lBuff, + aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, (char const * const *)aFilterPatterns, aSingleFilterDescription); + } + else if (dialogPresent()) + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } + p = saveFileDialogWinConsole(lBuff, aTitle, aDefaultPathAndOrFile); + } + else + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } + strcpy(lBuff, "Save file in "); + strcat(lBuff, getCurDir()); + + lPointerInputBox = tinyfd_inputBox(NULL,NULL,NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, lBuff, ""); + if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; + if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ + p = lBuff; + } + + if ( ! p || ! strlen( p ) ) + { + return NULL; + } + getPathWithoutFinalSlash( lString , p ) ; + if ( strlen( lString ) && ! dirExists( lString ) ) + { + return NULL ; + } + getLastName(lString,p); + if ( ! filenameValid(lString) ) + { + return NULL; + } + return p ; +} - strcat(lDialogString, "--inputbox"); - strcat(lDialogString, " \"\" 10 60 "); - strcat(lDialogString, ") 2>/tmp/tinyfd.txt;\ -if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ -tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes"); - } else if (!isTerminalRunning() && terminalName()) { - lWasBasicXterm = 1; - strcpy(lDialogString, terminalName()); - strcat(lDialogString, "'"); - if (!gWarningDisplayed) { - gWarningDisplayed = 1; - tinyfd_messageBox(gTitle, tinyfd_needs, "ok", "warning", 0); - } - if (aTitle && strlen(aTitle)) { - strcat(lDialogString, "echo \""); - strcat(lDialogString, aTitle); - strcat(lDialogString, "\";echo;"); - } - strcat(lDialogString, "echo \""); - strcat(lDialogString, "\";read "); - strcat(lDialogString, "-s "); - strcat(lDialogString, "-p \""); - strcat(lDialogString, "(esc+enter to cancel): \" ANSWER "); - strcat(lDialogString, ";echo 1$ANSWER >/tmp/tinyfd.txt';"); - strcat(lDialogString, "cat -v /tmp/tinyfd.txt"); - } else if (!gWarningDisplayed && !isTerminalRunning() && !terminalName()) { - gWarningDisplayed = 1; - tinyfd_messageBox(gTitle, tinyfd_needs, "ok", "warning", 0); - return NULL; - } else { - if (!gWarningDisplayed) { - gWarningDisplayed = 1; - tinyfd_messageBox(gTitle, tinyfd_needs, "ok", "warning", 0); - } - if (aTitle && strlen(aTitle)) { - printf("\n%s\n", aTitle); +/* in case of multiple files, the separator is | */ +char * tinyfd_openFileDialog( + char const * aTitle , /* NULL or "" */ + char const * aDefaultPathAndOrFile, /* NULL or "" */ + int aNumOfFilterPatterns , /* 0 */ + char const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ + char const * aSingleFilterDescription, /* NULL or "image files" */ + int aAllowMultipleSelects ) /* 0 or 1 */ +{ + static char lBuff[MAX_PATH_OR_CMD]; + char lString[MAX_PATH_OR_CMD]; + char * p; + char * lPointerInputBox; + int i; + + if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; + if (tfd_quoteDetected(aTitle)) return tinyfd_openFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); + if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_openFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); + if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_openFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES", aAllowMultipleSelects); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_openFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects); + } + + if ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) ) + && (!getenv("SSH_CLIENT") || getenvDISPLAY())) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} + p = openFileDialogWinGui( aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, + (char const * const *)aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); + } + else if (dialogPresent()) + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } + p = openFileDialogWinConsole(aTitle, aDefaultPathAndOrFile); + } + else + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } + strcpy(lBuff, "Open file from "); + strcat(lBuff, getCurDir()); + lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, lBuff, ""); + if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; + if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ + p = lBuff; + } + + if ( ! p || ! strlen( p ) ) + { + return NULL; + } + if ( aAllowMultipleSelects && strchr(p, '|') ) + { + p = ensureFilesExist( (char *) p , p ) ; + } + else if ( ! fileExists(p) ) + { + return NULL ; + } + /* printf( "lBuff3: %s\n" , p ) ; */ + return p ; +} + + +char * tinyfd_selectFolderDialog( + char const * aTitle , /* NULL or "" */ + char const * aDefaultPath ) /* NULL or "" */ +{ + static char lBuff[MAX_PATH_OR_CMD]; + char * p; + char * lPointerInputBox; + char lString[MAX_PATH_OR_CMD]; + + if (tfd_quoteDetected(aTitle)) return tinyfd_selectFolderDialog("INVALID TITLE WITH QUOTES", aDefaultPath); + if (tfd_quoteDetected(aDefaultPath)) return tinyfd_selectFolderDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES"); + + if ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) ) + && (!getenv("SSH_CLIENT") || getenvDISPLAY())) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} + p = selectFolderDialogWinGui(lBuff, aTitle, aDefaultPath); + } + else + if (dialogPresent()) + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } + p = selectFolderDialogWinConsole(lBuff, aTitle, aDefaultPath); + } + else + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } + strcpy(lBuff, "Select folder from "); + strcat(lBuff, getCurDir()); + lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, lBuff, ""); + if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; + if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ + p = lBuff; + } + + if ( ! p || ! strlen( p ) || ! dirExists( p ) ) + { + return NULL ; + } + return p ; +} + + +/* aDefaultRGB is used only if aDefaultHexRGB is absent */ +/* aDefaultRGB and aoResultRGB can be the same array */ +/* returns NULL on cancel */ +/* returns the hexcolor as a string "#FF0000" */ +/* aoResultRGB also contains the result */ +char * tinyfd_colorChooser( + char const * aTitle, /* NULL or "" */ + char const * aDefaultHexRGB, /* NULL or "" or "#FF0000"*/ + unsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */ + unsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */ +{ + static char lDefaultHexRGB[16]; + int i; + char * p ; + char * lPointerInputBox; + char lString[MAX_PATH_OR_CMD]; + + lDefaultHexRGB[0] = '\0'; + + if (tfd_quoteDetected(aTitle)) return tinyfd_colorChooser("INVALID TITLE WITH QUOTES", aDefaultHexRGB, aDefaultRGB, aoResultRGB); + if (tfd_quoteDetected(aDefaultHexRGB)) return tinyfd_colorChooser(aTitle, "INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultRGB, aoResultRGB); + + if ( (!tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent()) ) + && (!getenv("SSH_CLIENT") || getenvDISPLAY())) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} + p = colorChooserWinGui(aTitle, aDefaultHexRGB, aDefaultRGB, aoResultRGB); + if (p) + { + strcpy(lDefaultHexRGB, p); + return lDefaultHexRGB; + } + return NULL; + } + else if (dialogPresent()) + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } + } + else + { + if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } + } + + if (aDefaultHexRGB && (strlen(aDefaultHexRGB)==7) ) + { + strncpy(lDefaultHexRGB, aDefaultHexRGB,7); + lDefaultHexRGB[7]='\0'; + } + else + { + RGB2Hex(aDefaultRGB, lDefaultHexRGB); + } + + lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, "Enter hex rgb color (i.e. #f5ca20)", lDefaultHexRGB); + + if ( !p || (strlen(p) != 7) || (p[0] != '#') ) + { + return NULL ; + } + for ( i = 1 ; i < 7 ; i ++ ) + { + if ( ! isxdigit( (int) p[i] ) ) + { + return NULL ; + } + } + Hex2RGB(p,aoResultRGB); + + strcpy(lDefaultHexRGB, p); + + if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ + + return lDefaultHexRGB; +} + + +#else /* unix */ + +static char gPython2Name[16]; +static char gPython3Name[16]; +static char gPythonName[16]; + +int tfd_isDarwin(void) +{ + static int lsIsDarwin = -1 ; + struct utsname lUtsname ; + if ( lsIsDarwin < 0 ) + { + lsIsDarwin = !uname(&lUtsname) && !strcmp(lUtsname.sysname,"Darwin") ; + } + return lsIsDarwin ; +} + + +static int dirExists( char const * aDirPath ) +{ + DIR * lDir ; + if ( ! aDirPath || ! strlen( aDirPath ) ) + return 0 ; + lDir = opendir( aDirPath ) ; + if ( ! lDir ) + { + return 0 ; + } + closedir( lDir ) ; + return 1 ; +} + + +static int detectPresence( char const * aExecutable ) +{ + char lBuff[MAX_PATH_OR_CMD] ; + char lTestedString[MAX_PATH_OR_CMD] = "command -v " ; + FILE * lIn ; +#ifdef _GNU_SOURCE + char* lAllocatedCharString; + int lSubstringUndetected; +#endif + + strcat( lTestedString , aExecutable ) ; + strcat( lTestedString, " 2>/dev/null "); + lIn = popen( lTestedString , "r" ) ; + if ( ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + && ( ! strchr( lBuff , ':' ) ) && ( strncmp(lBuff, "no ", 3) ) ) + { /* present */ + pclose( lIn ) ; + +#ifdef _GNU_SOURCE /*to bypass this, just comment out "#define _GNU_SOURCE" at the top of the file*/ + if ( lBuff[strlen( lBuff ) -1] == '\n' ) lBuff[strlen( lBuff ) -1] = '\0' ; + lAllocatedCharString = realpath(lBuff,NULL); /*same as canonicalize_file_name*/ + lSubstringUndetected = ! strstr(lAllocatedCharString, aExecutable); + free(lAllocatedCharString); + if (lSubstringUndetected) + { + if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 0); + return 0; + } +#endif /*_GNU_SOURCE*/ + + if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 1); + return 1 ; + } + else + { + pclose( lIn ) ; + if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 0); + return 0 ; + } +} + + +static char * getVersion( char const * aExecutable ) /*version must be first numeral*/ +{ + static char lBuff[MAX_PATH_OR_CMD] ; + char lTestedString[MAX_PATH_OR_CMD] ; + FILE * lIn ; + char * lTmp ; + + strcpy( lTestedString , aExecutable ) ; + strcat( lTestedString , " --version" ) ; + + lIn = popen( lTestedString , "r" ) ; + lTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ; + pclose( lIn ) ; + + lTmp += strcspn(lTmp,"0123456789"); + /* printf("lTmp:%s\n", lTmp); */ + return lTmp ; +} + + +static int * getMajorMinorPatch( char const * aExecutable ) +{ + static int lArray[3] ; + char * lTmp ; + + lTmp = (char *) getVersion(aExecutable); + lArray[0] = atoi( strtok(lTmp," ,.-") ) ; + /* printf("lArray0 %d\n", lArray[0]); */ + lArray[1] = atoi( strtok(0," ,.-") ) ; + /* printf("lArray1 %d\n", lArray[1]); */ + lArray[2] = atoi( strtok(0," ,.-") ) ; + /* printf("lArray2 %d\n", lArray[2]); */ + + if ( !lArray[0] && !lArray[1] && !lArray[2] ) return NULL; + return lArray ; +} + + +static int tryCommand( char const * aCommand ) +{ + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + + lIn = popen( aCommand , "r" ) ; + if ( fgets( lBuff , sizeof( lBuff ) , lIn ) == NULL ) + { /* present */ + pclose( lIn ) ; + return 1 ; + } + else + { + pclose( lIn ) ; + return 0 ; + } + +} + + +static int isTerminalRunning(void) +{ + static int lIsTerminalRunning = -1 ; + if ( lIsTerminalRunning < 0 ) + { + lIsTerminalRunning = isatty(1); + if (tinyfd_verbose) printf("isTerminalRunning %d\n", lIsTerminalRunning ); + } + return lIsTerminalRunning; +} + + +static char * dialogNameOnly(void) +{ + static char lDialogName[128] = "*" ; + if ( lDialogName[0] == '*' ) + { + if (!tinyfd_allowCursesDialogs) + { + strcpy(lDialogName , "" ); + } + else if ( tfd_isDarwin() && * strcpy(lDialogName , "/opt/local/bin/dialog" ) + && detectPresence( lDialogName ) ) + {} + else if ( * strcpy(lDialogName , "dialog" ) + && detectPresence( lDialogName ) ) + {} + else + { + strcpy(lDialogName , "" ); + } + } + return lDialogName ; +} + + +int isDialogVersionBetter09b(void) +{ + char const * lDialogName ; + char * lVersion ; + int lMajor ; + int lMinor ; + int lDate ; + int lResult ; + char * lMinorP ; + char * lLetter ; + char lBuff[128] ; + + /*char lTest[128] = " 0.9b-20031126" ;*/ + + lDialogName = dialogNameOnly() ; + if ( ! strlen(lDialogName) || !(lVersion = (char *) getVersion(lDialogName)) ) return 0 ; + /*lVersion = lTest ;*/ + /*printf("lVersion %s\n", lVersion);*/ + strcpy(lBuff,lVersion); + lMajor = atoi( strtok(lVersion," ,.-") ) ; + /*printf("lMajor %d\n", lMajor);*/ + lMinorP = strtok(0," ,.-abcdefghijklmnopqrstuvxyz"); + lMinor = atoi( lMinorP ) ; + /*printf("lMinor %d\n", lMinor );*/ + lDate = atoi( strtok(0," ,.-") ) ; + if (lDate<0) lDate = - lDate; + /*printf("lDate %d\n", lDate);*/ + lLetter = lMinorP + strlen(lMinorP) ; + strcpy(lVersion,lBuff); + strtok(lLetter," ,.-"); + /*printf("lLetter %s\n", lLetter);*/ + lResult = (lMajor > 0) || ( ( lMinor == 9 ) && (*lLetter == 'b') && (lDate >= 20031126) ); + /*printf("lResult %d\n", lResult);*/ + return lResult; +} + + +static int whiptailPresentOnly(void) +{ + static int lWhiptailPresent = -1 ; + if (!tinyfd_allowCursesDialogs) return 0; + if ( lWhiptailPresent < 0 ) + { + lWhiptailPresent = detectPresence( "whiptail" ) ; + } + return lWhiptailPresent ; +} + + +static char * terminalName(void) +{ + static char lTerminalName[128] = "*" ; + char lShellName[64] = "*" ; + int * lArray; + + if ( lTerminalName[0] == '*' ) + { + if ( detectPresence( "bash" ) ) + { + strcpy(lShellName , "bash -c " ) ; /*good for basic input*/ + } + else if ( strlen(dialogNameOnly()) || whiptailPresentOnly() ) + { + strcpy(lShellName , "sh -c " ) ; /*good enough for dialog & whiptail*/ + } + else + { + strcpy(lTerminalName , "" ) ; + return NULL ; + } + + if ( tfd_isDarwin() ) + { + if ( * strcpy(lTerminalName , "/opt/X11/bin/xterm" ) + && detectPresence( lTerminalName ) ) + { + strcat(lTerminalName , " -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else + { + strcpy(lTerminalName , "" ) ; + } + } + else if ( * strcpy(lTerminalName,"xterm") /*good (small without parameters)*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"terminator") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -x " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"lxterminal") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"konsole") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"kterm") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"tilix") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"xfce4-terminal") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -x " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"mate-terminal") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -x " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"Eterm") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"evilvte") /*good*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"pterm") /*good (only letters)*/ + && detectPresence(lTerminalName) ) + { + strcat(lTerminalName , " -e " ) ; + strcat(lTerminalName , lShellName ) ; + } + else if ( * strcpy(lTerminalName,"gnome-terminal") + && detectPresence(lTerminalName) && (lArray = getMajorMinorPatch(lTerminalName)) + && ((lArray[0]<3) || (lArray[0]==3 && lArray[1]<=6)) ) + { + strcat(lTerminalName , " --disable-factory -x " ) ; + strcat(lTerminalName , lShellName ) ; + } + else + { + strcpy(lTerminalName , "" ) ; + } + /* bad: koi rxterm guake tilda vala-terminal qterminal kgx + aterm Terminal terminology sakura lilyterm weston-terminal + roxterm termit xvt rxvt mrxvt urxvt */ + } + if ( strlen(lTerminalName) ) + { + return lTerminalName ; + } + else + { + return NULL ; + } +} + + +static char * dialogName(void) +{ + char * lDialogName ; + lDialogName = dialogNameOnly( ) ; + if ( strlen(lDialogName) && ( isTerminalRunning() || terminalName() ) ) + { + return lDialogName ; + } + else + { + return NULL ; + } +} + + +static int whiptailPresent(void) +{ + int lWhiptailPresent ; + lWhiptailPresent = whiptailPresentOnly( ) ; + if ( lWhiptailPresent && ( isTerminalRunning() || terminalName() ) ) + { + return lWhiptailPresent ; + } + else + { + return 0 ; + } +} + + + +static int graphicMode(void) +{ + return !( tinyfd_forceConsole && (isTerminalRunning() || terminalName()) ) + && ( getenvDISPLAY() + || (tfd_isDarwin() && (!getenv("SSH_TTY") || getenvDISPLAY() ) ) ) ; +} + + +static int ffplayPresent(void) +{ + static int lFFplayPresent = -1; + if (lFFplayPresent < 0) + { + lFFplayPresent = detectPresence("ffplay"); + } + return lFFplayPresent; +} + + +static int pactlPresent( void ) +{ + static int lPactlPresent = -1 ; + char lBuff [256] ; + FILE * lIn ; + + if ( lPactlPresent < 0 ) + { + lPactlPresent = detectPresence("pactl") ; + if ( lPactlPresent ) + { + lIn = popen( "pactl info | grep -iF pulseaudio" , "r" ) ; + if ( ! (fgets( lBuff , sizeof( lBuff ) , lIn ) && ! strstr(lBuff, "PipeWire") ) ) + { + lPactlPresent = 0 ; + } + pclose( lIn ) ; + if (tinyfd_verbose) printf("is pactl valid ? %d\n", lPactlPresent); + } + } + return lPactlPresent ; +} + + +static int speakertestPresent(void) +{ + static int lSpeakertestPresent = -1 ; + if ( lSpeakertestPresent < 0 ) + { + lSpeakertestPresent = detectPresence("speaker-test") ; + } + return lSpeakertestPresent ; +} + + +static int playPresent(void) /* play is part of sox */ +{ + static int lPlayPresent = -1; + if (lPlayPresent < 0) + { + lPlayPresent = detectPresence("sox"); /*if sox is present, play is ready*/ + } + return lPlayPresent; +} + + +static int beepexePresent(void) +{ + static int lBeepexePresent = -1; + if (lBeepexePresent < 0) + { + lBeepexePresent = detectPresence("beep.exe"); + } + return lBeepexePresent; +} + + +/*static int beepPresent(void) +{ + static int lBeepPresent = -1 ; + if ( lBeepPresent < 0 ) + { + lBeepPresent = detectPresence("beep") ; + } + return lBeepPresent ; +}*/ + + +static int playsoundPresent(void) /* playsound is part of pipewire */ +{ + static int lPlaysoundPresent = -1 ; + if (lPlaysoundPresent < 0) + { + lPlaysoundPresent = detectPresence("playsound_simple"); + if ( lPlaysoundPresent && ! fileExists("/usr/share/sounds/freedesktop/stereo/bell.oga") ) + { + lPlaysoundPresent = 0 ; } - printf("(esc+enter to cancel): "); fflush(stdout); - tcgetattr(STDIN_FILENO, &oldt); - newt = oldt; - newt.c_lflag &= ~ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &newt); - - lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); - /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */ - if (!lEOF || (lBuff[0] == '\0')) { - free(lDialogString); - return NULL; + } + return lPlaysoundPresent; +} + + +static int paplayPresent(void) /* playsound is part of pipewire */ +{ + static int lPaplayPresent = -1 ; + if (lPaplayPresent < 0) + { + lPaplayPresent = detectPresence("paplay"); + if ( lPaplayPresent && ! fileExists("/usr/share/sounds/freedesktop/stereo/bell.oga") ) + { + lPaplayPresent = 0 ; } + } + return lPaplayPresent; +} + + +static int xmessagePresent(void) +{ + static int lXmessagePresent = -1 ; + if ( lXmessagePresent < 0 ) + { + lXmessagePresent = detectPresence("xmessage");/*if not tty,not on osxpath*/ + } + return lXmessagePresent && graphicMode( ) ; +} + + +static int gxmessagePresent(void) +{ + static int lGxmessagePresent = -1 ; + if ( lGxmessagePresent < 0 ) + { + lGxmessagePresent = detectPresence("gxmessage") ; + } + return lGxmessagePresent && graphicMode( ) ; +} + + +static int gmessagePresent(void) +{ + static int lGmessagePresent = -1 ; + if ( lGmessagePresent < 0 ) + { + lGmessagePresent = detectPresence("gmessage") ; + } + return lGmessagePresent && graphicMode( ) ; +} + + +static int notifysendPresent(void) +{ + static int lNotifysendPresent = -1 ; + if ( lNotifysendPresent < 0 ) + { + lNotifysendPresent = detectPresence("notify-send") ; + } + return lNotifysendPresent && graphicMode( ) ; +} + - if (lBuff[0] == '\n') { - lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); - /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */ - if (!lEOF || (lBuff[0] == '\0')) { - free(lDialogString); - return NULL; +static int perlPresent(void) +{ + static int lPerlPresent = -1 ; + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + + if ( lPerlPresent < 0 ) + { + lPerlPresent = detectPresence("perl") ; + if (lPerlPresent) + { + lIn = popen("perl -MNet::DBus -e \"Net::DBus->session->get_service('org.freedesktop.Notifications')\" 2>&1", "r"); + if (fgets(lBuff, sizeof(lBuff), lIn) == NULL) + { + lPerlPresent = 2; + } + pclose(lIn); + if (tinyfd_verbose) printf("perl-dbus %d\n", lPerlPresent); + } + } + return graphicMode() ? lPerlPresent : 0 ; +} + + +static int afplayPresent(void) +{ + static int lAfplayPresent = -1 ; + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + + if ( lAfplayPresent < 0 ) + { + lAfplayPresent = detectPresence("afplay") ; + if ( lAfplayPresent ) + { + lIn = popen( "test -e /System/Library/Sounds/Ping.aiff || echo Ping" , "r" ) ; + if ( fgets( lBuff , sizeof( lBuff ) , lIn ) == NULL ) + { + lAfplayPresent = 2 ; + } + pclose( lIn ) ; + if (tinyfd_verbose) printf("afplay %d\n", lAfplayPresent); + } + } + return graphicMode() ? lAfplayPresent : 0 ; +} + + +static int xdialogPresent(void) +{ + static int lXdialogPresent = -1 ; + if ( lXdialogPresent < 0 ) + { + lXdialogPresent = detectPresence("Xdialog") ; + } + return lXdialogPresent && graphicMode( ) ; +} + + +static int gdialogPresent(void) +{ + static int lGdialoglPresent = -1 ; + if ( lGdialoglPresent < 0 ) + { + lGdialoglPresent = detectPresence( "gdialog" ) ; + } + return lGdialoglPresent && graphicMode( ) ; +} + + +static int osascriptPresent(void) +{ + static int lOsascriptPresent = -1 ; + if ( lOsascriptPresent < 0 ) + { + gWarningDisplayed |= !!getenv("SSH_TTY"); + lOsascriptPresent = detectPresence( "osascript" ) ; + } + return lOsascriptPresent && graphicMode() && !getenv("SSH_TTY") ; +} + + +static int dunstifyPresent(void) +{ + static int lDunstifyPresent = -1 ; + static char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + char * lTmp ; + + if ( lDunstifyPresent < 0 ) + { + lDunstifyPresent = detectPresence( "dunstify" ) ; + if ( lDunstifyPresent ) + { + lIn = popen( "dunstify -s" , "r" ) ; + lTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ; + pclose( lIn ) ; + /* printf("lTmp:%s\n", lTmp); */ + lDunstifyPresent = strstr(lTmp,"name:dunst\n") ? 1 : 0 ; + if (tinyfd_verbose) printf("lDunstifyPresent %d\n", lDunstifyPresent); + } + } + return lDunstifyPresent && graphicMode( ) ; +} + + +static int dunstPresent(void) +{ + static int lDunstPresent = -1 ; + static char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + char * lTmp ; + + if ( lDunstPresent < 0 ) + { + lDunstPresent = detectPresence( "dunst" ) ; + if ( lDunstPresent ) + { + lIn = popen( "ps -e | grep dunst | grep -v grep" , "r" ) ; /* add "| wc -l" to receive the number of lines */ + lTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ; + pclose( lIn ) ; + /* if ( lTmp ) printf("lTmp:%s\n", lTmp); */ + if ( lTmp ) lDunstPresent = 1 ; + else lDunstPresent = 0 ; + if (tinyfd_verbose) printf("lDunstPresent %d\n", lDunstPresent); + } + } + return lDunstPresent && graphicMode( ) ; +} + + +int tfd_qarmaPresent(void) +{ + static int lQarmaPresent = -1 ; + if ( lQarmaPresent < 0 ) + { + lQarmaPresent = detectPresence("qarma") ; + } + return lQarmaPresent && graphicMode( ) ; +} + + +int tfd_matedialogPresent(void) +{ + static int lMatedialogPresent = -1 ; + if ( lMatedialogPresent < 0 ) + { + lMatedialogPresent = detectPresence("matedialog") ; + } + return lMatedialogPresent && graphicMode( ) ; +} + + +int tfd_shellementaryPresent(void) +{ + static int lShellementaryPresent = -1 ; + if ( lShellementaryPresent < 0 ) + { + lShellementaryPresent = 0 ; /*detectPresence("shellementary"); shellementary is not ready yet */ + } + return lShellementaryPresent && graphicMode( ) ; +} + + +int tfd_xpropPresent(void) +{ + static int lXpropReady = 0 ; + static int lXpropDetected = -1 ; + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + + if ( lXpropDetected < 0 ) + { + lXpropDetected = detectPresence("xprop") ; + } + + if ( !lXpropReady && lXpropDetected ) + { /* xwayland Debian issue reported by Kay F. Jahnke and solved with his help */ + lIn = popen( "xprop -root 32x ' $0' _NET_ACTIVE_WINDOW" , "r" ) ; + if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + { + if ( ! strstr( lBuff , "not found" ) ) + { + if (tinyfd_verbose) printf("xprop is ready\n"); + lXpropReady = 1 ; + } + } + pclose( lIn ) ; + } + return graphicMode() ? lXpropReady : 0 ; +} + + +int tfd_zenityPresent(void) +{ + static int lZenityPresent = -1 ; + if ( lZenityPresent < 0 ) + { + lZenityPresent = detectPresence("zenity") ; + } + return lZenityPresent && graphicMode( ) ; +} + + +int tfd_yadPresent(void) +{ + static int lYadPresent = -1; + if (lYadPresent < 0) + { + lYadPresent = detectPresence("yad"); + } + return lYadPresent && graphicMode(); +} + + +int tfd_zenity3Present(void) +{ + static int lZenity3Present = -1 ; + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + int lIntTmp ; + + if ( lZenity3Present < 0 ) + { + lZenity3Present = 0 ; + if ( tfd_zenityPresent() ) + { + lIn = popen( "zenity --version" , "r" ) ; + if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + { + if ( atoi(lBuff) >= 3 ) + { + lZenity3Present = 3 ; + lIntTmp = atoi(strtok(lBuff,".")+2 ) ; + if ( lIntTmp >= 18 ) + { + lZenity3Present = 5 ; + } + else if ( lIntTmp >= 10 ) + { + lZenity3Present = 4 ; + } + } + else if ( ( atoi(lBuff) == 2 ) && ( atoi(strtok(lBuff,".")+2 ) >= 32 ) ) + { + lZenity3Present = 2 ; + } + if (tinyfd_verbose) printf("zenity type %d\n", lZenity3Present); + } + pclose( lIn ) ; + } + } + return graphicMode() ? lZenity3Present : 0 ; +} + + +int tfd_kdialogPresent(void) +{ + static int lKdialogPresent = -1 ; + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + char * lDesktop; + + if ( lKdialogPresent < 0 ) + { + lDesktop = getenv("XDG_SESSION_DESKTOP"); + if ( !lDesktop || ( strcmp(lDesktop, "KDE") && strcmp(lDesktop, "lxqt") ) ) + { + if ( tfd_zenityPresent() ) + { + lKdialogPresent = 0 ; + return lKdialogPresent ; } } - tcsetattr(STDIN_FILENO, TCSANOW, &oldt); - printf("\n"); - if (strchr(lBuff, 27)) { - free(lDialogString); - return NULL; - } - if (lBuff[strlen(lBuff) - 1] == '\n') { - lBuff[strlen(lBuff) - 1] = '\0'; + lKdialogPresent = detectPresence("kdialog") ; + if ( lKdialogPresent && !getenv("SSH_TTY") ) + { + lIn = popen( "kdialog --attach 2>&1" , "r" ) ; + if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + { + if ( ! strstr( "Unknown" , lBuff ) ) + { + lKdialogPresent = 2 ; + if (tinyfd_verbose) printf("kdialog-attach %d\n", lKdialogPresent); + } + } + pclose( lIn ) ; + + if (lKdialogPresent == 2) + { + lKdialogPresent = 1 ; + lIn = popen( "kdialog --passivepopup 2>&1" , "r" ) ; + if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + { + if ( ! strstr( "Unknown" , lBuff ) ) + { + lKdialogPresent = 2 ; + if (tinyfd_verbose) printf("kdialog-popup %d\n", lKdialogPresent); + } + } + pclose( lIn ) ; + } } - free(lDialogString); - return lBuff; } + return graphicMode() ? lKdialogPresent : 0 ; +} - if (tinyfd_verbose) printf("lDialogString: %s\n", lDialogString); - lIn = popen(lDialogString, "r"); - if (!lIn) { - remove("/tmp/tinyfd.txt"); - remove("/tmp/tinyfd0.txt"); - free(lDialogString); - return NULL; - } - while (fgets(lBuff, sizeof(lBuff), lIn) != NULL) { - } - pclose(lIn); +static int osx9orBetter(void) +{ + static int lOsx9orBetter = -1 ; + char lBuff[MAX_PATH_OR_CMD] ; + FILE * lIn ; + int V,v; + + if ( lOsx9orBetter < 0 ) + { + lOsx9orBetter = 0 ; + lIn = popen( "osascript -e 'set osver to system version of (system info)'" , "r" ) ; + V = 0 ; + if ( ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + && ( 2 == sscanf(lBuff, "%d.%d", &V, &v) ) ) + { + V = V * 100 + v; + if ( V >= 1009 ) + { + lOsx9orBetter = 1 ; + } + } + pclose( lIn ) ; + if (tinyfd_verbose) printf("Osx10 = %d, %d = %s\n", lOsx9orBetter, V, lBuff) ; + } + return lOsx9orBetter ; +} + + +static int python3Present(void) +{ + static int lPython3Present = -1 ; + + if ( lPython3Present < 0 ) + { + lPython3Present = 0 ; + strcpy(gPython3Name , "python3" ) ; + if ( detectPresence(gPython3Name) ) lPython3Present = 1; + if (tinyfd_verbose) printf("lPython3Present %d\n", lPython3Present) ; + if (tinyfd_verbose) printf("gPython3Name %s\n", gPython3Name) ; + } + return lPython3Present ; +} + - remove("/tmp/tinyfd.txt"); - remove("/tmp/tinyfd0.txt"); +static int python2Present(void) +{ + static int lPython2Present = -1 ; + + if ( lPython2Present < 0 ) + { + lPython2Present = 0 ; + strcpy(gPython2Name , "python2" ) ; + if ( detectPresence(gPython2Name) ) lPython2Present = 1; + if (tinyfd_verbose) printf("lPython2Present %d\n", lPython2Present) ; + if (tinyfd_verbose) printf("gPython2Name %s\n", gPython2Name) ; + } + return lPython2Present ; +} + + +static int tkinter3Present(void) +{ + static int lTkinter3Present = -1 ; + char lPythonCommand[256]; + char lPythonParams[128] = + "-S -c \"try:\n\timport tkinter;\nexcept:\n\tprint(0);\""; + + if ( lTkinter3Present < 0 ) + { + lTkinter3Present = 0 ; + if ( python3Present() ) + { + sprintf( lPythonCommand , "%s %s" , gPython3Name , lPythonParams ) ; + lTkinter3Present = tryCommand(lPythonCommand) ; + } + if (tinyfd_verbose) printf("lTkinter3Present %d\n", lTkinter3Present) ; + } + return lTkinter3Present && graphicMode() && !(tfd_isDarwin() && getenv("SSH_TTY") ); +} - /* printf( "len Buff: %lu\n" , strlen(lBuff) ) ; */ - /* printf( "lBuff0: %s\n" , lBuff ) ; */ - if (lBuff[strlen(lBuff) - 1] == '\n') { - lBuff[strlen(lBuff) - 1] = '\0'; + +static int tkinter2Present(void) +{ + static int lTkinter2Present = -1 ; + char lPythonCommand[256]; + char lPythonParams[128] = + "-S -c \"try:\n\timport Tkinter;\nexcept:\n\tprint 0;\""; + + if ( lTkinter2Present < 0 ) + { + lTkinter2Present = 0 ; + if ( python2Present() ) + { + sprintf( lPythonCommand , "%s %s" , gPython2Name , lPythonParams ) ; + lTkinter2Present = tryCommand(lPythonCommand) ; + } + if (tinyfd_verbose) printf("lTkinter2Present %d graphicMode %d \n", lTkinter2Present, graphicMode() ) ; + } + return lTkinter2Present && graphicMode() && !(tfd_isDarwin() && getenv("SSH_TTY") ); +} + + +static int pythonDbusPresent(void) +{ + static int lPythonDbusPresent = -1 ; + char lPythonCommand[384]; + char lPythonParams[256] = +"-c \"try:\n\timport dbus;bus=dbus.SessionBus();\ +notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');\ +notify=dbus.Interface(notif,'org.freedesktop.Notifications');\nexcept:\n\tprint(0);\""; + + if (lPythonDbusPresent < 0 ) + { + lPythonDbusPresent = 0 ; + if ( python2Present() ) + { + strcpy(gPythonName , gPython2Name ) ; + sprintf( lPythonCommand , "%s %s" , gPythonName , lPythonParams ) ; + lPythonDbusPresent = tryCommand(lPythonCommand) ; + } + + if ( !lPythonDbusPresent && python3Present() ) + { + strcpy(gPythonName , gPython3Name ) ; + sprintf( lPythonCommand , "%s %s" , gPythonName , lPythonParams ) ; + lPythonDbusPresent = tryCommand(lPythonCommand) ; + } + + if (tinyfd_verbose) printf("lPythonDbusPresent %d\n", lPythonDbusPresent) ; + if (tinyfd_verbose) printf("gPythonName %s\n", gPythonName) ; + } + return lPythonDbusPresent && graphicMode() && !(tfd_isDarwin() && getenv("SSH_TTY") ); +} + + +static void sigHandler(int signum) +{ + FILE * lIn ; + if ( ( lIn = popen( "pactl unload-module module-sine" , "r" ) ) ) + { + pclose( lIn ) ; } - /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ - if (lWasBasicXterm) { - if (strstr(lBuff, "^[")) { /* esc was pressed */ - free(lDialogString); - return NULL; + if (tinyfd_verbose) printf("tinyfiledialogs caught signal %d\n", signum); +} + + +void tinyfd_beep(void) +{ + char lDialogString[256] ; + FILE * lIn ; + + if ( pactlPresent() ) + { + signal(SIGINT, sigHandler); + strcpy( lDialogString , + "thnum=$(pactl load-module module-sine frequency=440);sleep .3;pactl unload-module $thnum" ) ; + } + else if ( osascriptPresent() ) + { + if ( afplayPresent() >= 2 ) + { + strcpy( lDialogString , "afplay /System/Library/Sounds/Ping.aiff") ; + } + else + { + strcpy( lDialogString , "osascript -e 'tell application \"System Events\" to beep'") ; } } + else if ( speakertestPresent() ) + { + /*strcpy( lDialogString , "timeout -k .3 .3 speaker-test --frequency 440 --test sine > /dev/tty" ) ;*/ + strcpy( lDialogString , "( speaker-test -t sine -f 440 > /dev/tty )& pid=$!;sleep .5; kill -9 $pid" ) ; /*.3 was too short for mac g3*/ + } + else if ( ffplayPresent() ) + { + strcpy(lDialogString, "ffplay -f lavfi -i sine=f=440:d=0.15 -autoexit -nodisp" ); + } + else if (playPresent()) /* play is part of sox */ + { + strcpy(lDialogString, "play -q -n synth .3 sine 440"); + } + else if ( playsoundPresent() ) + { + strcpy( lDialogString , "playsound_simple /usr/share/sounds/freedesktop/stereo/bell.oga") ; + } + else if ( paplayPresent() ) + { + strcpy( lDialogString , "paplay /usr/share/sounds/freedesktop/stereo/bell.oga") ; + } + else if (beepexePresent()) + { + strcpy(lDialogString, "beep.exe 440 300"); + } + /*else if ( beepPresent() ) + { + strcpy( lDialogString , "beep -f 440 -l 300" ) ; + }*/ + else + { + strcpy( lDialogString , "printf '\\a' > /dev/tty" ) ; + } + + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; - lResult = strncmp(lBuff, "1", 1) ? 0 : 1; - /* printf( "lResult: %d \n" , lResult ) ; */ - if (!lResult) { - free(lDialogString); - return NULL; + if ( ( lIn = popen( lDialogString , "r" ) ) ) + { + pclose( lIn ) ; } - /* printf( "lBuff+1: %s\n" , lBuff+1 ) ; */ - free(lDialogString); - return lBuff + 1; + if ( pactlPresent() ) + { + signal(SIGINT, SIG_DFL); + } } -char const *tinyfd_selectFolderDialog(char const *const aTitle) +int tinyfd_messageBox( + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" may contain \n and \t */ + char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ + char const * aIconType , /* "info" "warning" "error" "question" */ + int aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ { - static char resultBuff[MAX_PATH_OR_CMD]; - char dialogString[MAX_PATH_OR_CMD]; - char const *p; - resultBuff[0] = '\0'; + char lBuff[MAX_PATH_OR_CMD] ; + char * lDialogString = NULL ; + char * lpDialogString; + FILE * lIn ; + int lWasGraphicDialog = 0 ; + int lWasXterm = 0 ; + int lResult ; + char lChar ; + struct termios infoOri; + struct termios info; + size_t lTitleLen ; + size_t lMessageLen ; + + lBuff[0]='\0'; + + if (tfd_quoteDetected(aTitle)) return tinyfd_messageBox("INVALID TITLE WITH QUOTES", aMessage, aDialogType, aIconType, aDefaultButton); + if (tfd_quoteDetected(aMessage)) return tinyfd_messageBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDialogType, aIconType, aDefaultButton); + + lTitleLen = aTitle ? strlen(aTitle) : 0 ; + lMessageLen = aMessage ? strlen(aMessage) : 0 ; + if ( !aTitle || strcmp(aTitle,"tinyfd_query") ) + { + lDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen ); + } + + if ( osascriptPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return 1;} + + strcpy( lDialogString , "osascript "); + if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); + strcat( lDialogString , " -e 'try' -e 'set {vButton} to {button returned} of ( display dialog \"") ; + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage) ; + } + strcat(lDialogString, "\" ") ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "with title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + strcat(lDialogString, "with icon ") ; + if ( aIconType && ! strcmp( "error" , aIconType ) ) + { + strcat(lDialogString, "stop " ) ; + } + else if ( aIconType && ! strcmp( "warning" , aIconType ) ) + { + strcat(lDialogString, "caution " ) ; + } + else /* question or info */ + { + strcat(lDialogString, "note " ) ; + } + if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) + { + if ( ! aDefaultButton ) + { + strcat( lDialogString ,"default button \"Cancel\" " ) ; + } + } + else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) + { + strcat( lDialogString ,"buttons {\"No\", \"Yes\"} " ) ; + if (aDefaultButton) + { + strcat( lDialogString ,"default button \"Yes\" " ) ; + } + else + { + strcat( lDialogString ,"default button \"No\" " ) ; + } + strcat( lDialogString ,"cancel button \"No\"" ) ; + } + else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat( lDialogString ,"buttons {\"No\", \"Yes\", \"Cancel\"} " ) ; + switch (aDefaultButton) + { + case 1: strcat( lDialogString ,"default button \"Yes\" " ) ; break; + case 2: strcat( lDialogString ,"default button \"No\" " ) ; break; + case 0: strcat( lDialogString ,"default button \"Cancel\" " ) ; break; + } + strcat( lDialogString ,"cancel button \"Cancel\"" ) ; + } + else + { + strcat( lDialogString ,"buttons {\"OK\"} " ) ; + strcat( lDialogString ,"default button \"OK\" " ) ; + } + strcat( lDialogString, ")' ") ; + + strcat( lDialogString, +"-e 'if vButton is \"Yes\" then' -e 'return 1'\ + -e 'else if vButton is \"OK\" then' -e 'return 1'\ + -e 'else if vButton is \"No\" then' -e 'return 2'\ + -e 'else' -e 'return 0' -e 'end if' " ); + + strcat( lDialogString, "-e 'on error number -128' " ) ; + strcat( lDialogString, "-e '0' " ); + + strcat( lDialogString, "-e 'end try'") ; + if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; + } + else if ( tfd_kdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return 1;} + + strcpy( lDialogString , "kdialog" ) ; + if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + + strcat( lDialogString , " --" ) ; + if ( aDialogType && ( ! strcmp( "okcancel" , aDialogType ) + || ! strcmp( "yesno" , aDialogType ) || ! strcmp( "yesnocancel" , aDialogType ) ) ) + { + if ( aIconType && ( ! strcmp( "warning" , aIconType ) + || ! strcmp( "error" , aIconType ) ) ) + { + strcat( lDialogString , "warning" ) ; + } + if ( ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat( lDialogString , "yesnocancel" ) ; + } + else + { + strcat( lDialogString , "yesno" ) ; + } + } + else if ( aIconType && ! strcmp( "error" , aIconType ) ) + { + strcat( lDialogString , "error" ) ; + } + else if ( aIconType && ! strcmp( "warning" , aIconType ) ) + { + strcat( lDialogString , "sorry" ) ; + } + else + { + strcat( lDialogString , "msgbox" ) ; + } + strcat( lDialogString , " \"" ) ; + if ( aMessage ) + { + strcat( lDialogString , aMessage ) ; + } + strcat( lDialogString , "\"" ) ; + if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) + { + strcat( lDialogString , + " --yes-label Ok --no-label Cancel" ) ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, " --title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + } + + if ( ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat( lDialogString , "; x=$? ;if [ $x = 0 ] ;then echo 1;elif [ $x = 1 ] ;then echo 2;else echo 0;fi"); + } + else + { + strcat( lDialogString , ";if [ $? = 0 ];then echo 1;else echo 0;fi"); + } + } + else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) + { + if ( tfd_zenityPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return 1;} + strcpy( lDialogString , "szAnswer=$(zenity" ) ; + if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + else if ( tfd_matedialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return 1;} + strcpy( lDialogString , "szAnswer=$(matedialog" ) ; + } + else if ( tfd_shellementaryPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return 1;} + strcpy( lDialogString , "szAnswer=$(shellementary" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return 1;} + strcpy( lDialogString , "szAnswer=$(qarma" ) ; + if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + strcat(lDialogString, " --"); + + if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) + { + strcat( lDialogString , + "question --ok-label=Ok --cancel-label=Cancel" ) ; + } + else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) + { + strcat( lDialogString , "question" ) ; + } + else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat( lDialogString , "list --column \"\" --hide-header \"Yes\" \"No\"" ) ; + } + else if ( aIconType && ! strcmp( "error" , aIconType ) ) + { + strcat( lDialogString , "error" ) ; + } + else if ( aIconType && ! strcmp( "warning" , aIconType ) ) + { + strcat( lDialogString , "warning" ) ; + } + else + { + strcat( lDialogString , "info" ) ; + } + + strcat(lDialogString, " --title=\""); + if ( aTitle && strlen(aTitle) ) strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\""); + + if (strcmp("yesnocancel", aDialogType)) strcat(lDialogString, " --no-wrap"); + + strcat(lDialogString, " --text=\"") ; + if (aMessage && strlen(aMessage)) strcat(lDialogString, aMessage) ; + strcat(lDialogString, "\"") ; + + if ( (tfd_zenity3Present() >= 3) || (!tfd_zenityPresent() && (tfd_shellementaryPresent() || tfd_qarmaPresent()) ) ) + { + strcat( lDialogString , " --icon-name=dialog-" ) ; + if ( aIconType && (! strcmp( "question" , aIconType ) + || ! strcmp( "error" , aIconType ) + || ! strcmp( "warning" , aIconType ) ) ) + { + strcat( lDialogString , aIconType ) ; + } + else + { + strcat( lDialogString , "information" ) ; + } + } + + if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); + + if ( ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat( lDialogString , +");if [ $? = 1 ];then echo 0;elif [ $szAnswer = \"No\" ];then echo 2;else echo 1;fi"); + } + else + { + strcat( lDialogString , ");if [ $? = 0 ];then echo 1;else echo 0;fi"); + } + } + + else if (tfd_yadPresent()) + { + if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return 1; } + strcpy(lDialogString, "szAnswer=$(yad --"); + if (aDialogType && !strcmp("ok", aDialogType)) + { + strcat(lDialogString,"button=Ok:1"); + } + else if (aDialogType && !strcmp("okcancel", aDialogType)) + { + strcat(lDialogString,"button=Ok:1 --button=Cancel:0"); + } + else if (aDialogType && !strcmp("yesno", aDialogType)) + { + strcat(lDialogString, "button=Yes:1 --button=No:0"); + } + else if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + strcat(lDialogString, "button=Yes:1 --button=No:2 --button=Cancel:0"); + } + else if (aIconType && !strcmp("error", aIconType)) + { + strcat(lDialogString, "error"); + } + else if (aIconType && !strcmp("warning", aIconType)) + { + strcat(lDialogString, "warning"); + } + else + { + strcat(lDialogString, "info"); + } + if (aTitle && strlen(aTitle)) + { + strcat(lDialogString, " --title=\""); + strcat(lDialogString, aTitle); + strcat(lDialogString, "\""); + } + if (aMessage && strlen(aMessage)) + { + strcat(lDialogString, " --text=\""); + strcat(lDialogString, aMessage); + strcat(lDialogString, "\""); + } + + strcat(lDialogString, " --image=dialog-"); + if (aIconType && (!strcmp("question", aIconType) + || !strcmp("error", aIconType) + || !strcmp("warning", aIconType))) + { + strcat(lDialogString, aIconType); + } + else + { + strcat(lDialogString, "information"); + } + + if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); + strcat(lDialogString,");echo $?"); + } + + else if ( !gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter3Present() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return 1;} + + strcpy( lDialogString , gPython3Name ) ; + strcat( lDialogString , + " -S -c \"import tkinter;from tkinter import messagebox;root=tkinter.Tk();root.withdraw();"); + + strcat( lDialogString ,"res=messagebox." ) ; + if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) + { + strcat( lDialogString , "askokcancel(" ) ; + if ( aDefaultButton ) + { + strcat( lDialogString , "default=messagebox.OK," ) ; + } + else + { + strcat( lDialogString , "default=messagebox.CANCEL," ) ; + } + } + else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) + { + strcat( lDialogString , "askyesno(" ) ; + if ( aDefaultButton ) + { + strcat( lDialogString , "default=messagebox.YES," ) ; + } + else + { + strcat( lDialogString , "default=messagebox.NO," ) ; + } + } + else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat( lDialogString , "askyesnocancel(" ) ; + switch ( aDefaultButton ) + { + case 1: strcat( lDialogString , "default=messagebox.YES," ); break; + case 2: strcat( lDialogString , "default=messagebox.NO," ); break; + case 0: strcat( lDialogString , "default=messagebox.CANCEL," ); break; + } + } + else + { + strcat( lDialogString , "showinfo(" ) ; + } + + strcat( lDialogString , "icon='" ) ; + if ( aIconType && (! strcmp( "question" , aIconType ) + || ! strcmp( "error" , aIconType ) + || ! strcmp( "warning" , aIconType ) ) ) + { + strcat( lDialogString , aIconType ) ; + } + else + { + strcat( lDialogString , "info" ) ; + } + + strcat(lDialogString, "',") ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, "message='") ; + lpDialogString = lDialogString + strlen(lDialogString); + tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; + strcat(lDialogString, "'") ; + } + + if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat(lDialogString, ");\n\ +if res is None :\n\tprint(0)\n\ +elif res is False :\n\tprint(2)\n\ +else :\n\tprint (1)\n\"" ) ; + } + else + { + strcat(lDialogString, ");\n\ +if res is False :\n\tprint(0)\n\ +else :\n\tprint(1)\n\"" ) ; + } + } + else if ( !gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter2Present() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return 1;} + strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; + strcat( lDialogString , gPython2Name ) ; + if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) + { + strcat( lDialogString , " -i" ) ; /* for osx without console */ + } + + strcat( lDialogString , +" -S -c \"import Tkinter,tkMessageBox;root=Tkinter.Tk();root.withdraw();"); + + if ( tfd_isDarwin( ) ) + { + strcat( lDialogString , +"import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ +frontmost of process \\\"Python\\\" to true' ''');"); + } + + strcat( lDialogString ,"res=tkMessageBox." ) ; + if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) + { + strcat( lDialogString , "askokcancel(" ) ; + if ( aDefaultButton ) + { + strcat( lDialogString , "default=tkMessageBox.OK," ) ; + } + else + { + strcat( lDialogString , "default=tkMessageBox.CANCEL," ) ; + } + } + else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) + { + strcat( lDialogString , "askyesno(" ) ; + if ( aDefaultButton ) + { + strcat( lDialogString , "default=tkMessageBox.YES," ) ; + } + else + { + strcat( lDialogString , "default=tkMessageBox.NO," ) ; + } + } + else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat( lDialogString , "askyesnocancel(" ) ; + switch ( aDefaultButton ) + { + case 1: strcat( lDialogString , "default=tkMessageBox.YES," ); break; + case 2: strcat( lDialogString , "default=tkMessageBox.NO," ); break; + case 0: strcat( lDialogString , "default=tkMessageBox.CANCEL," ); break; + } + } + else + { + strcat( lDialogString , "showinfo(" ) ; + } + + strcat( lDialogString , "icon='" ) ; + if ( aIconType && (! strcmp( "question" , aIconType ) + || ! strcmp( "error" , aIconType ) + || ! strcmp( "warning" , aIconType ) ) ) + { + strcat( lDialogString , aIconType ) ; + } + else + { + strcat( lDialogString , "info" ) ; + } + + strcat(lDialogString, "',") ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, "message='") ; + lpDialogString = lDialogString + strlen(lDialogString); + tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; + strcat(lDialogString, "'") ; + } + + if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) + { + strcat(lDialogString, ");\n\ +if res is None :\n\tprint 0\n\ +elif res is False :\n\tprint 2\n\ +else :\n\tprint 1\n\"" ) ; + } + else + { + strcat(lDialogString, ");\n\ +if res is False :\n\tprint 0\n\ +else :\n\tprint 1\n\"" ) ; + } + } + else if ( gxmessagePresent() || gmessagePresent() || (!gdialogPresent() && !xdialogPresent() && xmessagePresent()) ) + { + if ( gxmessagePresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gxmessage");return 1;} + strcpy( lDialogString , "gxmessage"); + } + else if ( gmessagePresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gmessage");return 1;} + strcpy( lDialogString , "gmessage"); + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xmessage");return 1;} + strcpy( lDialogString , "xmessage"); + } + + if ( aDialogType && ! strcmp("okcancel" , aDialogType) ) + { + strcat( lDialogString , " -buttons Ok:1,Cancel:0"); + switch ( aDefaultButton ) + { + case 1: strcat( lDialogString , " -default Ok"); break; + case 0: strcat( lDialogString , " -default Cancel"); break; + } + } + else if ( aDialogType && ! strcmp("yesno" , aDialogType) ) + { + strcat( lDialogString , " -buttons Yes:1,No:0"); + switch ( aDefaultButton ) + { + case 1: strcat( lDialogString , " -default Yes"); break; + case 0: strcat( lDialogString , " -default No"); break; + } + } + else if ( aDialogType && ! strcmp("yesnocancel" , aDialogType) ) + { + strcat( lDialogString , " -buttons Yes:1,No:2,Cancel:0"); + switch ( aDefaultButton ) + { + case 1: strcat( lDialogString , " -default Yes"); break; + case 2: strcat( lDialogString , " -default No"); break; + case 0: strcat( lDialogString , " -default Cancel"); break; + } + } + else + { + strcat( lDialogString , " -buttons Ok:1"); + strcat( lDialogString , " -default Ok"); + } + + strcat( lDialogString , " -center \""); + if ( aMessage && strlen(aMessage) ) + { + strcat( lDialogString , aMessage ) ; + } + strcat(lDialogString, "\"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat( lDialogString , " -title \""); + strcat( lDialogString , aTitle ) ; + strcat( lDialogString, "\"" ) ; + } + strcat( lDialogString , " ; echo $? "); + } + else if ( xdialogPresent() || gdialogPresent() || dialogName() || whiptailPresent() ) + { + if ( gdialogPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gdialog");return 1;} + lWasGraphicDialog = 1 ; + strcpy( lDialogString , "(gdialog " ) ; + } + else if ( xdialogPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return 1;} + lWasGraphicDialog = 1 ; + strcpy( lDialogString , "(Xdialog " ) ; + } + else if ( dialogName( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return 0;} + if ( isTerminalRunning( ) ) + { + strcpy( lDialogString , "(dialog " ) ; + } + else + { + lWasXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'(" ) ; + strcat( lDialogString , dialogName() ) ; + strcat( lDialogString , " " ) ; + } + } + else if ( isTerminalRunning( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return 0;} + strcpy( lDialogString , "(whiptail " ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return 0;} + lWasXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'(whiptail " ) ; + } + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + if ( !xdialogPresent() && !gdialogPresent() ) + { + if ( aDialogType && ( !strcmp( "okcancel" , aDialogType ) || !strcmp( "yesno" , aDialogType ) + || !strcmp( "yesnocancel" , aDialogType ) ) ) + { + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, "tab: move focus") ; + strcat(lDialogString, "\" ") ; + } + } + + if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) + { + if ( ! aDefaultButton ) + { + strcat( lDialogString , "--defaultno " ) ; + } + strcat( lDialogString , + "--yes-label \"Ok\" --no-label \"Cancel\" --yesno " ) ; + } + else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) + { + if ( ! aDefaultButton ) + { + strcat( lDialogString , "--defaultno " ) ; + } + strcat( lDialogString , "--yesno " ) ; + } + else if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + if (!aDefaultButton) + { + strcat(lDialogString, "--defaultno "); + } + strcat(lDialogString, "--menu "); + } + else + { + strcat( lDialogString , "--msgbox " ) ; + + } + strcat( lDialogString , "\"" ) ; + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage) ; + } + strcat(lDialogString, "\" "); + + if ( lWasGraphicDialog ) + { + if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + strcat(lDialogString,"0 60 0 Yes \"\" No \"\") 2>/tmp/tinyfd.txt;\ +if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ +tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; + } + else + { + strcat(lDialogString, + "10 60 ) 2>&1;if [ $? = 0 ];then echo 1;else echo 0;fi"); + } + } + else + { + if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + strcat(lDialogString,"0 60 0 Yes \"\" No \"\" >/dev/tty ) 2>/tmp/tinyfd.txt;\ + if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ + tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; + + if ( lWasXterm ) + { + strcat(lDialogString," >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt"); + } + else + { + strcat(lDialogString, "; clear >/dev/tty") ; + } + } + else + { + strcat(lDialogString, "10 60 >/dev/tty) 2>&1;if [ $? = 0 ];"); + if ( lWasXterm ) + { + strcat( lDialogString , +"then\n\techo 1\nelse\n\techo 0\nfi >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); + } + else + { + strcat(lDialogString, + "then echo 1;else echo 0;fi;clear >/dev/tty"); + } + } + } + } + else if ( !isTerminalRunning() && terminalName() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return 0;} + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'" ) ; + if ( !gWarningDisplayed && !tinyfd_forceConsole) + { + gWarningDisplayed = 1 ; + strcat( lDialogString , "echo \"" ) ; + strcat( lDialogString, gTitle) ; + strcat( lDialogString , "\";" ) ; + strcat( lDialogString , "echo \"" ) ; + strcat( lDialogString, tinyfd_needs) ; + strcat( lDialogString , "\";echo;echo;" ) ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat( lDialogString , "echo \"" ) ; + strcat( lDialogString, aTitle) ; + strcat( lDialogString , "\";echo;" ) ; + } + if ( aMessage && strlen(aMessage) ) + { + strcat( lDialogString , "echo \"" ) ; + strcat( lDialogString, aMessage) ; + strcat( lDialogString , "\"; " ) ; + } + if ( aDialogType && !strcmp("yesno",aDialogType) ) + { + strcat( lDialogString , "echo -n \"y/n: \"; " ) ; + strcat( lDialogString , "stty sane -echo;" ) ; + strcat( lDialogString , + "answer=$( while ! head -c 1 | grep -i [ny];do true ;done);"); + strcat( lDialogString , + "if echo \"$answer\" | grep -iq \"^y\";then\n"); + strcat( lDialogString , "\techo 1\nelse\n\techo 0\nfi" ) ; + } + else if ( aDialogType && !strcmp("okcancel",aDialogType) ) + { + strcat( lDialogString , "echo -n \"[O]kay/[C]ancel: \"; " ) ; + strcat( lDialogString , "stty sane -echo;" ) ; + strcat( lDialogString , + "answer=$( while ! head -c 1 | grep -i [oc];do true ;done);"); + strcat( lDialogString , + "if echo \"$answer\" | grep -iq \"^o\";then\n"); + strcat( lDialogString , "\techo 1\nelse\n\techo 0\nfi" ) ; + } + else if ( aDialogType && !strcmp("yesnocancel",aDialogType) ) + { + strcat( lDialogString , "echo -n \"[Y]es/[N]o/[C]ancel: \"; " ) ; + strcat( lDialogString , "stty sane -echo;" ) ; + strcat( lDialogString , + "answer=$( while ! head -c 1 | grep -i [nyc];do true ;done);"); + strcat( lDialogString , + "if echo \"$answer\" | grep -iq \"^y\";then\n\techo 1\n"); + strcat( lDialogString , "elif echo \"$answer\" | grep -iq \"^n\";then\n\techo 2\n" ) ; + strcat( lDialogString , "else\n\techo 0\nfi" ) ; + } + else + { + strcat(lDialogString , "echo -n \"press enter to continue \"; "); + strcat( lDialogString , "stty sane -echo;" ) ; + strcat( lDialogString , + "answer=$( while ! head -c 1;do true ;done);echo 1"); + } + strcat( lDialogString , + " >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); + } + else if ( !isTerminalRunning() && pythonDbusPresent() && !strcmp("ok" , aDialogType) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python-dbus");return 1;} + strcpy( lDialogString , gPythonName ) ; + strcat( lDialogString ," -c \"import dbus;bus=dbus.SessionBus();"); + strcat( lDialogString ,"notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');" ) ; + strcat( lDialogString ,"notify=dbus.Interface(notif,'org.freedesktop.Notifications');" ) ; + strcat( lDialogString ,"notify.Notify('',0,'" ) ; + if ( aIconType && strlen(aIconType) ) + { + strcat( lDialogString , aIconType ) ; + } + strcat(lDialogString, "','") ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, aTitle) ; + } + strcat(lDialogString, "','") ; + if ( aMessage && strlen(aMessage) ) + { + lpDialogString = lDialogString + strlen(lDialogString); + tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; + } + strcat(lDialogString, "','','',5000)\"") ; + } + else if ( !isTerminalRunning() && (perlPresent() >= 2) && !strcmp("ok" , aDialogType) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"perl-dbus");return 1;} + + strcpy( lDialogString , "perl -e \"use Net::DBus;\ +my \\$sessionBus = Net::DBus->session;\ +my \\$notificationsService = \\$sessionBus->get_service('org.freedesktop.Notifications');\ +my \\$notificationsObject = \\$notificationsService->get_object('/org/freedesktop/Notifications',\ +'org.freedesktop.Notifications');"); + + sprintf( lDialogString + strlen(lDialogString), +"my \\$notificationId;\\$notificationId = \\$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);\" ", + aIconType?aIconType:"", aTitle?aTitle:"", aMessage?aMessage:"" ) ; + } + else if ( !isTerminalRunning() && notifysendPresent() && !strcmp("ok" , aDialogType) ) + { + + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"notifysend");return 1;} + strcpy( lDialogString , "notify-send" ) ; + if ( aIconType && strlen(aIconType) ) + { + strcat( lDialogString , " -i '" ) ; + strcat( lDialogString , aIconType ) ; + strcat( lDialogString , "'" ) ; + } + strcat( lDialogString , " \"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, aTitle) ; + strcat( lDialogString , " | " ) ; + } + if ( aMessage && strlen(aMessage) ) + { + tfd_replaceSubStr( aMessage , "\n\t" , " | " , lBuff ) ; + tfd_replaceSubStr( aMessage , "\n" , " | " , lBuff ) ; + tfd_replaceSubStr( aMessage , "\t" , " " , lBuff ) ; + strcat(lDialogString, lBuff) ; + } + strcat( lDialogString , "\"" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return 0;} + if ( !gWarningDisplayed && !tinyfd_forceConsole) + { + gWarningDisplayed = 1 ; + printf("\n\n%s\n", gTitle); + printf("%s\n\n", tinyfd_needs); + } + if ( aTitle && strlen(aTitle) ) + { + printf("\n%s\n", aTitle); + } + + tcgetattr(0, &infoOri); + tcgetattr(0, &info); + info.c_lflag &= ~ICANON; + info.c_cc[VMIN] = 1; + info.c_cc[VTIME] = 0; + tcsetattr(0, TCSANOW, &info); + if ( aDialogType && !strcmp("yesno",aDialogType) ) + { + do + { + if ( aMessage && strlen(aMessage) ) + { + printf("\n%s\n",aMessage); + } + printf("y/n: "); fflush(stdout); + lChar = (char) tolower( getchar() ) ; + printf("\n\n"); + } + while ( lChar != 'y' && lChar != 'n' ); + lResult = lChar == 'y' ? 1 : 0 ; + } + else if ( aDialogType && !strcmp("okcancel",aDialogType) ) + { + do + { + if ( aMessage && strlen(aMessage) ) + { + printf("\n%s\n",aMessage); + } + printf("[O]kay/[C]ancel: "); fflush(stdout); + lChar = (char) tolower( getchar() ) ; + printf("\n\n"); + } + while ( lChar != 'o' && lChar != 'c' ); + lResult = lChar == 'o' ? 1 : 0 ; + } + else if ( aDialogType && !strcmp("yesnocancel",aDialogType) ) + { + do + { + if ( aMessage && strlen(aMessage) ) + { + printf("\n%s\n",aMessage); + } + printf("[Y]es/[N]o/[C]ancel: "); fflush(stdout); + lChar = (char) tolower( getchar() ) ; + printf("\n\n"); + } + while ( lChar != 'y' && lChar != 'n' && lChar != 'c' ); + lResult = (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0 ; + } + else + { + if ( aMessage && strlen(aMessage) ) + { + printf("\n%s\n\n",aMessage); + } + printf("press enter to continue "); fflush(stdout); + getchar() ; + printf("\n\n"); + lResult = 1 ; + } + tcsetattr(0, TCSANOW, &infoOri); + free(lDialogString); + return lResult ; + } + + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; + + if ( ! ( lIn = popen( lDialogString , "r" ) ) ) + { + free(lDialogString); + return 0 ; + } + while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + {} + + pclose( lIn ) ; + + /* printf( "lBuff: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ + if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ + + if (aDialogType && !strcmp("yesnocancel", aDialogType)) + { + if ( lBuff[0]=='1' ) + { + if ( !strcmp( lBuff+1 , "Yes" )) strcpy(lBuff,"1"); + else if ( !strcmp( lBuff+1 , "No" )) strcpy(lBuff,"2"); + } + } + /* printf( "lBuff2: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ + + lResult = !strcmp( lBuff , "2" ) ? 2 : !strcmp( lBuff , "1" ) ? 1 : 0; + + /* printf( "lResult: %d\n" , lResult ) ; */ + free(lDialogString); + return lResult ; +} - if (osascriptPresent()) { - strcpy(dialogString, "osascript "); - strcat(dialogString, " -e 'try' -e 'POSIX path of ( choose folder "); - if (aTitle && strlen(aTitle)) { - strcat(dialogString, "with prompt \""); - strcat(dialogString, aTitle); - strcat(dialogString, "\" "); - } - strcat(dialogString, ")' "); - strcat(dialogString, "-e 'on error number -128' "); - strcat(dialogString, "-e 'end try'"); - } else if (kdialogPresent()) { - strcpy(dialogString, "kdialog"); - strcat(dialogString, " --getexistingdirectory "); - - strcat(dialogString, "$PWD/"); - - if (aTitle && strlen(aTitle)) { - strcat(dialogString, " --title \""); - strcat(dialogString, aTitle); - strcat(dialogString, "\""); - } - } else if (zenityPresent() || matedialogPresent() || qarmaPresent()) { - if (zenityPresent()) { - strcpy(dialogString, "zenity"); - } else if (matedialogPresent()) { - strcpy(dialogString, "matedialog"); - } else { - strcpy(dialogString, "qarma"); - } - strcat(dialogString, " --file-selection --directory"); - if (aTitle && strlen(aTitle)) { - strcat(dialogString, " --title=\""); - strcat(dialogString, aTitle); - strcat(dialogString, "\""); - } - if (tinyfd_silent) strcat(dialogString, " 2>/dev/null "); - } else if (xdialogPresent()) { - strcpy(dialogString, "(Xdialog "); - if (aTitle && strlen(aTitle)) { - strcat(dialogString, "--title \""); - strcat(dialogString, aTitle); - strcat(dialogString, "\" "); - } - strcat(dialogString, "--dselect \"./\" 0 60 ) 2>&1 "); - } else if (tkinter2Present()) { - strcpy(dialogString, gPython2Name); - strcat(dialogString, " -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); - strcat(dialogString, "print tkFileDialog.askdirectory("); - if (aTitle && strlen(aTitle)) { - strcat(dialogString, "title='"); - strcat(dialogString, aTitle); - strcat(dialogString, "',"); - } - strcat(dialogString, ")\""); - } else if (tkinter3Present()) { - strcpy(dialogString, gPython3Name); - strcat(dialogString, - " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"); - strcat(dialogString, "print(filedialog.askdirectory("); - if (aTitle && strlen(aTitle)) { - strcat(dialogString, "title='"); - strcat(dialogString, aTitle); - strcat(dialogString, "',"); - } - strcat(dialogString, ") )\""); - } else { - p = selectFolderUsingInputBox(aTitle ? aTitle : "Select folder"); - if (!dirExists(p)) { +/* return has only meaning for tinyfd_query */ +int tinyfd_notifyPopup( + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" may contain \n and \t */ + char const * aIconType ) /* "info" "warning" "error" */ +{ + char lBuff[MAX_PATH_OR_CMD]; + char * lDialogString = NULL ; + char * lpDialogString ; + FILE * lIn ; + size_t lTitleLen ; + size_t lMessageLen ; + + if (tfd_quoteDetected(aTitle)) return tinyfd_notifyPopup("INVALID TITLE WITH QUOTES", aMessage, aIconType); + if (tfd_quoteDetected(aMessage)) return tinyfd_notifyPopup(aTitle, "INVALID MESSAGE WITH QUOTES", aIconType); + + if ( getenv("SSH_TTY") && !dunstifyPresent() && !dunstPresent() ) + { + return tinyfd_messageBox(aTitle, aMessage, "ok", aIconType, 0); + } + + lTitleLen = aTitle ? strlen(aTitle) : 0 ; + lMessageLen = aMessage ? strlen(aMessage) : 0 ; + if ( !aTitle || strcmp(aTitle,"tinyfd_query") ) + { + lDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen ); + } + + if ( getenv("SSH_TTY") ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dunst");return 1;} + strcpy( lDialogString , "notify-send \"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat( lDialogString , aTitle ) ; + strcat( lDialogString , "\" \"" ) ; + } + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage) ; + } + strcat( lDialogString , "\"" ) ; + } + else if ( osascriptPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return 1;} + + strcpy( lDialogString , "osascript "); + if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); + strcat( lDialogString , " -e 'try' -e 'display notification \"") ; + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage) ; + } + strcat(lDialogString, " \" ") ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "with title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + strcat( lDialogString, "' -e 'end try'") ; + if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; + } + else if ( tfd_kdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return 1;} + strcpy( lDialogString , "kdialog" ) ; + + if ( aIconType && strlen(aIconType) ) + { + strcat( lDialogString , " --icon '" ) ; + strcat( lDialogString , aIconType ) ; + strcat( lDialogString , "'" ) ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat( lDialogString , " --title \"" ) ; + strcat( lDialogString , aTitle ) ; + strcat( lDialogString , "\"" ) ; + } + + strcat( lDialogString , " --passivepopup" ) ; + strcat( lDialogString , " \"" ) ; + if ( aMessage ) + { + strcat( lDialogString , aMessage ) ; + } + strcat( lDialogString , " \" 5" ) ; + } + else if ( tfd_yadPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"yad");return 1;} + strcpy( lDialogString , "yad --notification"); + + if ( aIconType && strlen( aIconType ) ) + { + strcat( lDialogString , " --image=\""); + strcat( lDialogString , aIconType ) ; + strcat( lDialogString , "\"" ) ; + } + + strcat( lDialogString , " --text=\"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\n") ; + } + if ( aMessage && strlen( aMessage ) ) + { + strcat( lDialogString , aMessage ) ; + } + strcat( lDialogString , " \"" ) ; + } + else if ( perlPresent() >= 2 ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"perl-dbus");return 1;} + + strcpy( lDialogString , "perl -e \"use Net::DBus;\ +my \\$sessionBus = Net::DBus->session;\ +my \\$notificationsService = \\$sessionBus->get_service('org.freedesktop.Notifications');\ +my \\$notificationsObject = \\$notificationsService->get_object('/org/freedesktop/Notifications',\ +'org.freedesktop.Notifications');"); + + sprintf( lDialogString + strlen(lDialogString) , +"my \\$notificationId;\\$notificationId = \\$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);\" ", +aIconType?aIconType:"", aTitle?aTitle:"", aMessage?aMessage:"" ) ; + } + else if ( pythonDbusPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python-dbus");return 1;} + strcpy( lDialogString , gPythonName ) ; + strcat( lDialogString ," -c \"import dbus;bus=dbus.SessionBus();"); + strcat( lDialogString ,"notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');" ) ; + strcat( lDialogString ,"notify=dbus.Interface(notif,'org.freedesktop.Notifications');" ) ; + strcat( lDialogString ,"notify.Notify('',0,'" ) ; + if ( aIconType && strlen(aIconType) ) + { + strcat( lDialogString , aIconType ) ; + } + strcat(lDialogString, "','") ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, aTitle) ; + } + strcat(lDialogString, "','") ; + if ( aMessage && strlen(aMessage) ) + { + lpDialogString = lDialogString + strlen(lDialogString); + tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; + } + strcat(lDialogString, "','','',5000)\"") ; + } + else if ( notifysendPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"notifysend");return 1;} + strcpy( lDialogString , "notify-send" ) ; + if ( aIconType && strlen(aIconType) ) + { + strcat( lDialogString , " -i '" ) ; + strcat( lDialogString , aIconType ) ; + strcat( lDialogString , "'" ) ; + } + strcat( lDialogString , " \"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, aTitle) ; + strcat( lDialogString , " | " ) ; + } + if ( aMessage && strlen(aMessage) ) + { + tfd_replaceSubStr( aMessage , "\n\t" , " | " , lBuff ) ; + tfd_replaceSubStr( aMessage , "\n" , " | " , lBuff ) ; + tfd_replaceSubStr( aMessage , "\t" , " " , lBuff ) ; + strcat(lDialogString, lBuff) ; + } + strcat( lDialogString , "\"" ) ; + } + else if ( (tfd_zenity3Present()>=5) ) + { + /* zenity 2.32 & 3.14 has the notification but with a bug: it doesnt return from it */ + /* zenity 3.8 show the notification as an alert ok cancel box */ + /* zenity 3.44 doesn't have the notification (3.42 has it) */ + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return 1;} + strcpy( lDialogString , "zenity --notification"); + + if ( aIconType && strlen( aIconType ) ) + { + strcat( lDialogString , " --window-icon '"); + strcat( lDialogString , aIconType ) ; + strcat( lDialogString , "'" ) ; + } + + strcat( lDialogString , " --text \"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\n") ; + } + if ( aMessage && strlen( aMessage ) ) + { + strcat( lDialogString , aMessage ) ; + } + strcat( lDialogString , " \"" ) ; + } + else + { + if (lDialogString) free(lDialogString); + return tinyfd_messageBox(aTitle, aMessage, "ok", aIconType, 0); + } + + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; + + if ( ! ( lIn = popen( lDialogString , "r" ) ) ) + { + free(lDialogString); + return 0 ; + } + + pclose( lIn ) ; + free(lDialogString); + return 1; +} + + +/* returns NULL on cancel */ +char * tinyfd_inputBox( + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" (\n and \t have no effect) */ + char const * aDefaultInput ) /* "" , if NULL it's a passwordBox */ +{ + static char lBuff[MAX_PATH_OR_CMD]; + char * lDialogString = NULL; + char * lpDialogString; + FILE * lIn ; + int lResult ; + int lWasGdialog = 0 ; + int lWasGraphicDialog = 0 ; + int lWasXterm = 0 ; + int lWasBasicXterm = 0 ; + struct termios oldt ; + struct termios newt ; + char * lEOF; + size_t lTitleLen ; + size_t lMessageLen ; + + if (!aTitle && !aMessage && !aDefaultInput) return lBuff; /* now I can fill lBuff from outside */ + + lBuff[0]='\0'; + + if (tfd_quoteDetected(aTitle)) return tinyfd_inputBox("INVALID TITLE WITH QUOTES", aMessage, aDefaultInput); + if (tfd_quoteDetected(aMessage)) return tinyfd_inputBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDefaultInput); + if (tfd_quoteDetected(aDefaultInput)) return tinyfd_inputBox(aTitle, aMessage, "INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\x60 instead."); + + lTitleLen = aTitle ? strlen(aTitle) : 0 ; + lMessageLen = aMessage ? strlen(aMessage) : 0 ; + if ( !aTitle || strcmp(aTitle,"tinyfd_query") ) + { + lDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen ); + } + + if ( osascriptPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} + strcpy( lDialogString , "osascript "); + if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); + strcat( lDialogString , " -e 'try' -e 'display dialog \"") ; + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage) ; + } + strcat(lDialogString, "\" ") ; + strcat(lDialogString, "default answer \"") ; + if ( aDefaultInput && strlen(aDefaultInput) ) + { + strcat(lDialogString, aDefaultInput) ; + } + strcat(lDialogString, "\" ") ; + if ( ! aDefaultInput ) + { + strcat(lDialogString, "hidden answer true ") ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "with title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + strcat(lDialogString, "with icon note' ") ; + strcat(lDialogString, "-e '\"1\" & text returned of result' " ); + strcat(lDialogString, "-e 'on error number -128' " ) ; + strcat(lDialogString, "-e '0' " ); + strcat(lDialogString, "-e 'end try'") ; + if ( ! osx9orBetter() ) strcat(lDialogString, " -e 'end tell'") ; + } + else if ( tfd_kdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} + strcpy( lDialogString , "szAnswer=$(kdialog" ) ; + + if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + + if ( ! aDefaultInput ) + { + strcat(lDialogString, " --password ") ; + } + else + { + strcat(lDialogString, " --inputbox ") ; + + } + strcat(lDialogString, "\"") ; + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage ) ; + } + strcat(lDialogString , "\" \"" ) ; + if ( aDefaultInput && strlen(aDefaultInput) ) + { + strcat(lDialogString, aDefaultInput ) ; + } + strcat(lDialogString , "\"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, " --title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + } + strcat( lDialogString , + ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi"); + } + else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) + { + if ( tfd_zenityPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} + strcpy( lDialogString , "szAnswer=$(zenity" ) ; + if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + else if ( tfd_matedialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} + strcpy( lDialogString , "szAnswer=$(matedialog" ) ; + } + else if ( tfd_shellementaryPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} + strcpy( lDialogString , "szAnswer=$(shellementary" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} + strcpy( lDialogString , "szAnswer=$(qarma" ) ; + if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + strcat( lDialogString ," --entry" ) ; + + strcat(lDialogString, " --title=\"") ; + if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + + strcat(lDialogString, " --text=\"") ; + if (aMessage && strlen(aMessage)) strcat(lDialogString, aMessage) ; + strcat(lDialogString, "\"") ; + + if ( aDefaultInput ) + { + strcat(lDialogString, " --entry-text=\"") ; + strcat(lDialogString, aDefaultInput) ; + strcat(lDialogString, "\"") ; + } + else + { + strcat(lDialogString, " --hide-text") ; + } + if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); + strcat( lDialogString , + ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi"); + } + else if (tfd_yadPresent()) + { + if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } + strcpy(lDialogString, "szAnswer=$(yad --entry"); + if (aTitle && strlen(aTitle)) + { + strcat(lDialogString, " --title=\""); + strcat(lDialogString, aTitle); + strcat(lDialogString, "\""); + } + if (aMessage && strlen(aMessage)) + { + strcat(lDialogString, " --text=\""); + strcat(lDialogString, aMessage); + strcat(lDialogString, "\""); + } + if (aDefaultInput && strlen(aDefaultInput)) + { + strcat(lDialogString, " --entry-text=\""); + strcat(lDialogString, aDefaultInput); + strcat(lDialogString, "\""); + } + else + { + strcat(lDialogString, " --hide-text"); + } + if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); + strcat(lDialogString, + ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi"); + } + else if ( gxmessagePresent() || gmessagePresent() ) + { + if ( gxmessagePresent() ) { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gxmessage");return (char *)1;} + strcpy( lDialogString , "szAnswer=$(gxmessage -buttons Ok:1,Cancel:0 -center \""); + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gmessage");return (char *)1;} + strcpy( lDialogString , "szAnswer=$(gmessage -buttons Ok:1,Cancel:0 -center \""); + } + + if ( aMessage && strlen(aMessage) ) + { + strcat( lDialogString , aMessage ) ; + } + strcat(lDialogString, "\"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat( lDialogString , " -title \""); + strcat( lDialogString , aTitle ) ; + strcat(lDialogString, "\" " ) ; + } + strcat(lDialogString, " -entrytext \"" ) ; + if ( aDefaultInput && strlen(aDefaultInput) ) + { + strcat( lDialogString , aDefaultInput ) ; + } + strcat(lDialogString, "\"" ) ; + strcat( lDialogString , ");echo $?$szAnswer"); + } + else if ( !gdialogPresent() && !xdialogPresent() && tkinter3Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} + strcpy( lDialogString , gPython3Name ) ; + strcat( lDialogString , + " -S -c \"import tkinter; from tkinter import simpledialog;root=tkinter.Tk();root.withdraw();"); + strcat( lDialogString ,"res=simpledialog.askstring(" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aMessage && strlen(aMessage) ) + { + + strcat(lDialogString, "prompt='") ; + lpDialogString = lDialogString + strlen(lDialogString); + tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultInput ) + { + if ( strlen(aDefaultInput) ) + { + strcat(lDialogString, "initialvalue='") ; + strcat(lDialogString, aDefaultInput) ; + strcat(lDialogString, "',") ; + } + } + else + { + strcat(lDialogString, "show='*'") ; + } + strcat(lDialogString, ");\nif res is None :\n\tprint(0)"); + strcat(lDialogString, "\nelse :\n\tprint('1'+res)\n\"" ) ; + } + else if ( !gdialogPresent() && !xdialogPresent() && tkinter2Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} + strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; + strcat( lDialogString , gPython2Name ) ; + if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) + { + strcat( lDialogString , " -i" ) ; /* for osx without console */ + } + + strcat( lDialogString , + " -S -c \"import Tkinter,tkSimpleDialog;root=Tkinter.Tk();root.withdraw();"); + + if ( tfd_isDarwin( ) ) + { + strcat( lDialogString , +"import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ +frontmost of process \\\"Python\\\" to true' ''');"); + } + + strcat( lDialogString ,"res=tkSimpleDialog.askstring(" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aMessage && strlen(aMessage) ) + { + + strcat(lDialogString, "prompt='") ; + lpDialogString = lDialogString + strlen(lDialogString); + tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultInput ) + { + if ( strlen(aDefaultInput) ) + { + strcat(lDialogString, "initialvalue='") ; + strcat(lDialogString, aDefaultInput) ; + strcat(lDialogString, "',") ; + } + } + else + { + strcat(lDialogString, "show='*'") ; + } + strcat(lDialogString, ");\nif res is None :\n\tprint 0"); + strcat(lDialogString, "\nelse :\n\tprint '1'+res\n\"" ) ; + } + else if ( gdialogPresent() || xdialogPresent() || dialogName() || whiptailPresent() ) + { + if ( gdialogPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gdialog");return (char *)1;} + lWasGraphicDialog = 1 ; + lWasGdialog = 1 ; + strcpy( lDialogString , "(gdialog " ) ; + } + else if ( xdialogPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} + lWasGraphicDialog = 1 ; + strcpy( lDialogString , "(Xdialog " ) ; + } + else if ( dialogName( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + if ( isTerminalRunning( ) ) + { + strcpy( lDialogString , "(dialog " ) ; + } + else + { + lWasXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'(" ) ; + strcat( lDialogString , dialogName() ) ; + strcat( lDialogString , " " ) ; + } + } + else if ( isTerminalRunning( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return (char *)0;} + strcpy( lDialogString , "(whiptail " ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return (char *)0;} + lWasXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'(whiptail " ) ; + } + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + if ( !xdialogPresent() && !gdialogPresent() ) + { + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, "tab: move focus") ; + if ( ! aDefaultInput && !lWasGdialog ) + { + strcat(lDialogString, " (sometimes nothing, no blink nor star, is shown in text field)") ; + } + strcat(lDialogString, "\" ") ; + } + + if ( aDefaultInput || lWasGdialog ) + { + strcat( lDialogString , "--inputbox" ) ; + } + else + { + if ( !lWasGraphicDialog && dialogName() && isDialogVersionBetter09b() ) + { + strcat( lDialogString , "--insecure " ) ; + } + strcat( lDialogString , "--passwordbox" ) ; + } + strcat( lDialogString , " \"" ) ; + if ( aMessage && strlen(aMessage) ) + { + strcat(lDialogString, aMessage) ; + } + strcat(lDialogString,"\" 10 60 ") ; + if ( aDefaultInput && strlen(aDefaultInput) ) + { + strcat(lDialogString, "\"") ; + strcat(lDialogString, aDefaultInput) ; + strcat(lDialogString, "\" ") ; + } + if ( lWasGraphicDialog ) + { + strcat(lDialogString,") 2>/tmp/tinyfd.txt;\ + if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ + tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; + } + else + { + strcat(lDialogString,">/dev/tty ) 2>/tmp/tinyfd.txt;\ + if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ + tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; + + if ( lWasXterm ) + { + strcat(lDialogString," >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt"); + } + else + { + strcat(lDialogString, "; clear >/dev/tty") ; + } + } + } + else if ( ! isTerminalRunning( ) && terminalName() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return (char *)0;} + lWasBasicXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'" ) ; + if ( !gWarningDisplayed && !tinyfd_forceConsole) + { + gWarningDisplayed = 1 ; + tinyfd_messageBox(gTitle,tinyfd_needs,"ok","warning",0); + } + if ( aTitle && strlen(aTitle) && !tinyfd_forceConsole) + { + strcat( lDialogString , "echo \"" ) ; + strcat( lDialogString, aTitle) ; + strcat( lDialogString , "\";echo;" ) ; + } + + strcat( lDialogString , "echo \"" ) ; + if ( aMessage && strlen(aMessage) ) + { + strcat( lDialogString, aMessage) ; + } + strcat( lDialogString , "\";read " ) ; + if ( ! aDefaultInput ) + { + strcat( lDialogString , "-s " ) ; + } + strcat( lDialogString , "-p \"" ) ; + strcat( lDialogString , "(esc+enter to cancel): \" ANSWER " ) ; + strcat( lDialogString , ";echo 1$ANSWER >/tmp/tinyfd.txt';" ) ; + strcat( lDialogString , "cat -v /tmp/tinyfd.txt"); + } + else if ( !gWarningDisplayed && ! isTerminalRunning( ) && ! terminalName() ) { + gWarningDisplayed = 1 ; + tinyfd_messageBox(gTitle,tinyfd_needs,"ok","warning",0); + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"no_solution");return (char *)0;} + free(lDialogString); + return NULL; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return (char *)0;} + if ( !gWarningDisplayed && !tinyfd_forceConsole) + { + gWarningDisplayed = 1 ; + tinyfd_messageBox(gTitle,tinyfd_needs,"ok","warning",0); + } + if ( aTitle && strlen(aTitle) ) + { + printf("\n%s\n", aTitle); + } + if ( aMessage && strlen(aMessage) ) + { + printf("\n%s\n",aMessage); + } + printf("(esc+enter to cancel): "); fflush(stdout); + if ( ! aDefaultInput ) + { + tcgetattr(STDIN_FILENO, & oldt) ; + newt = oldt ; + newt.c_lflag &= ~ECHO ; + tcsetattr(STDIN_FILENO, TCSANOW, & newt); + } + + lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); + /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */ + if ( ! lEOF || (lBuff[0] == '\0') ) + { + free(lDialogString); + return NULL; + } + + if ( lBuff[0] == '\n' ) + { + lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); + /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */ + if ( ! lEOF || (lBuff[0] == '\0') ) + { + free(lDialogString); + return NULL; + } + } + + if ( ! aDefaultInput ) + { + tcsetattr(STDIN_FILENO, TCSANOW, & oldt); + printf("\n"); + } + printf("\n"); + if ( strchr(lBuff,27) ) + { + free(lDialogString); + return NULL ; + } + if ( lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + free(lDialogString); + return lBuff ; + } + + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; + lIn = popen( lDialogString , "r" ); + if ( ! lIn ) + { + if ( fileExists("/tmp/tinyfd.txt") ) + { + wipefile("/tmp/tinyfd.txt"); + remove("/tmp/tinyfd.txt"); + } + if ( fileExists("/tmp/tinyfd0.txt") ) + { + wipefile("/tmp/tinyfd0.txt"); + remove("/tmp/tinyfd0.txt"); + } + free(lDialogString); + return NULL ; + } + while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + {} + + pclose( lIn ) ; + + if ( fileExists("/tmp/tinyfd.txt") ) + { + wipefile("/tmp/tinyfd.txt"); + remove("/tmp/tinyfd.txt"); + } + if ( fileExists("/tmp/tinyfd0.txt") ) + { + wipefile("/tmp/tinyfd0.txt"); + remove("/tmp/tinyfd0.txt"); + } + + /* printf( "len Buff: %lu\n" , strlen(lBuff) ) ; */ + /* printf( "lBuff0: %s\n" , lBuff ) ; */ + if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ + if ( lWasBasicXterm ) + { + if ( strstr(lBuff,"^[") ) /* esc was pressed */ + { + free(lDialogString); + return NULL ; + } + } + + lResult = strncmp( lBuff , "1" , 1) ? 0 : 1 ; + /* printf( "lResult: %d \n" , lResult ) ; */ + if ( ! lResult ) + { + free(lDialogString); + return NULL ; + } + + /* printf( "lBuff+1: %s\n" , lBuff+1 ) ; */ + free(lDialogString); + return lBuff+1 ; +} + + +char * tinyfd_saveFileDialog( + char const * aTitle , /* NULL or "" */ + char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */ + int aNumOfFilterPatterns , /* 0 */ + char const * const * aFilterPatterns , /* NULL or {"*.txt","*.doc"} */ + char const * aSingleFilterDescription ) /* NULL or "text files" */ +{ + static char lBuff[MAX_PATH_OR_CMD] ; + static char lLastDirectory[MAX_PATH_OR_CMD] = "$PWD" ; + + char lDialogString[MAX_PATH_OR_CMD] ; + char lString[MAX_PATH_OR_CMD] ; + int i ; + int lWasGraphicDialog = 0 ; + int lWasXterm = 0 ; + char * p ; + char * lPointerInputBox ; + FILE * lIn ; + lBuff[0]='\0'; + + if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; + if (tfd_quoteDetected(aTitle)) return tinyfd_saveFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); + if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_saveFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); + if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_saveFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES"); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_saveFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL); + } + + if ( osascriptPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} + strcpy( lDialogString , "osascript "); + if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"Finder\"' -e 'Activate'"); + strcat( lDialogString , " -e 'try' -e 'POSIX path of ( choose file name " ); + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "with prompt \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "default location \"") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "\" " ) ; + } + getLastName( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "default name \"") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "\" " ) ; + } + strcat( lDialogString , ")' " ) ; + strcat(lDialogString, "-e 'on error number -128' " ) ; + strcat(lDialogString, "-e 'end try'") ; + if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; + } + else if ( tfd_kdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} + + strcpy( lDialogString , "kdialog" ) ; + if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + strcat( lDialogString , " --getsavefilename " ) ; + + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + if ( aDefaultPathAndOrFile[0] != '/' ) + { + strcat(lDialogString, lLastDirectory) ; + strcat(lDialogString , "/" ) ; + } + strcat(lDialogString, "\"") ; + strcat(lDialogString, aDefaultPathAndOrFile ) ; + strcat(lDialogString , "\"" ) ; + } + else + { + strcat(lDialogString, lLastDirectory) ; + strcat(lDialogString , "/" ) ; + } + + if ( aNumOfFilterPatterns > 0 ) + { + strcat(lDialogString , " \"" ) ; + strcat( lDialogString , aFilterPatterns[0] ) ; + for ( i = 1 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , " " ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + } + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , " | " ) ; + strcat( lDialogString , aSingleFilterDescription ) ; + } + strcat( lDialogString , "\"" ) ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, " --title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + } + } + else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) + { + if ( tfd_zenityPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} + strcpy( lDialogString , "zenity" ) ; + if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + else if ( tfd_matedialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} + strcpy( lDialogString , "matedialog" ) ; + } + else if ( tfd_shellementaryPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} + strcpy( lDialogString , "shellementary" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} + strcpy( lDialogString , "qarma" ) ; + if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + strcat(lDialogString, " --file-selection --save --confirm-overwrite" ) ; + + strcat(lDialogString, " --title=\"") ; + if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + strcat(lDialogString, " --filename=\"") ; + strcat(lDialogString, aDefaultPathAndOrFile) ; + strcat(lDialogString, "\"") ; + } + if ( aNumOfFilterPatterns > 0 ) + { + strcat( lDialogString , " --file-filter='" ) ; + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , aSingleFilterDescription ) ; + strcat( lDialogString , " |" ) ; + } + for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , " " ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + } + strcat( lDialogString , "' --file-filter='All files | *'" ) ; + } + if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); + } + else if (tfd_yadPresent()) + { + if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } + strcpy(lDialogString, "yad --file --save --confirm-overwrite"); + if (aTitle && strlen(aTitle)) + { + strcat(lDialogString, " --title=\""); + strcat(lDialogString, aTitle); + strcat(lDialogString, "\""); + } + if (aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile)) + { + strcat(lDialogString, " --filename=\""); + strcat(lDialogString, aDefaultPathAndOrFile); + strcat(lDialogString, "\""); + } + if (aNumOfFilterPatterns > 0) + { + strcat(lDialogString, " --file-filter='"); + if (aSingleFilterDescription && strlen(aSingleFilterDescription)) + { + strcat(lDialogString, aSingleFilterDescription); + strcat(lDialogString, " |"); + } + for (i = 0; i < aNumOfFilterPatterns; i++) + { + strcat(lDialogString, " "); + strcat(lDialogString, aFilterPatterns[i]); + } + strcat(lDialogString, "' --file-filter='All files | *'"); + } + if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); + } + else if ( !xdialogPresent() && tkinter3Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} + strcpy( lDialogString , gPython3Name ) ; + strcat( lDialogString , + " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"); + strcat( lDialogString , "res=filedialog.asksaveasfilename("); + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialdir='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + getLastName( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialfile='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + } + if ( ( aNumOfFilterPatterns > 1 ) + || ( (aNumOfFilterPatterns == 1) /* test because poor osx behaviour */ + && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) + { + strcat(lDialogString , "filetypes=(" ) ; + strcat( lDialogString , "('" ) ; + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , aSingleFilterDescription ) ; + } + strcat( lDialogString , "',(" ) ; + for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , "'" ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + strcat( lDialogString , "'," ) ; + } + strcat( lDialogString , "))," ) ; + strcat( lDialogString , "('All files','*'))" ) ; + } + strcat( lDialogString, ");\nif not isinstance(res, tuple):\n\tprint(res)\n\"" ) ; + } + else if ( !xdialogPresent() && tkinter2Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} + strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; + strcat( lDialogString , gPython2Name ) ; + if ( ! isTerminalRunning( ) && tfd_isDarwin( )) + { + strcat( lDialogString , " -i" ) ; /* for osx without console */ + } + strcat( lDialogString , +" -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); + + if ( tfd_isDarwin( ) ) + { + strcat( lDialogString , +"import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set\ + frontmost of process \\\"Python\\\" to true' ''');"); + } + + strcat( lDialogString , "res=tkFileDialog.asksaveasfilename("); + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialdir='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + getLastName( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialfile='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + } + if ( ( aNumOfFilterPatterns > 1 ) + || ( (aNumOfFilterPatterns == 1) /* test because poor osx behaviour */ + && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) + { + strcat(lDialogString , "filetypes=(" ) ; + strcat( lDialogString , "('" ) ; + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , aSingleFilterDescription ) ; + } + strcat( lDialogString , "',(" ) ; + for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , "'" ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + strcat( lDialogString , "'," ) ; + } + strcat( lDialogString , "))," ) ; + strcat( lDialogString , "('All files','*'))" ) ; + } + strcat( lDialogString, ");\nif not isinstance(res, tuple):\n\tprint res \n\"" ) ; + } + else if ( xdialogPresent() || dialogName() ) + { + if ( xdialogPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} + lWasGraphicDialog = 1 ; + strcpy( lDialogString , "(Xdialog " ) ; + } + else if ( isTerminalRunning( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + strcpy( lDialogString , "(dialog " ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + lWasXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'(" ) ; + strcat( lDialogString , dialogName() ) ; + strcat( lDialogString , " " ) ; + } + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + if ( !xdialogPresent() && !gdialogPresent() ) + { + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, + "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; + strcat(lDialogString, "\" ") ; + } + + strcat( lDialogString , "--fselect \"" ) ; + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + if ( ! strchr(aDefaultPathAndOrFile, '/') ) + { + strcat(lDialogString, "./") ; + } + strcat(lDialogString, aDefaultPathAndOrFile) ; + } + else if ( ! isTerminalRunning( ) && !lWasGraphicDialog ) + { + strcat(lDialogString, getenv("HOME")) ; + strcat(lDialogString, "/") ; + } + else + { + strcat(lDialogString, "./") ; + } + + if ( lWasGraphicDialog ) + { + strcat(lDialogString, "\" 0 60 ) 2>&1 ") ; + } + else + { + strcat(lDialogString, "\" 0 60 >/dev/tty) ") ; + if ( lWasXterm ) + { + strcat( lDialogString , + "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); + } + else + { + strcat(lDialogString, "2>&1 ; clear >/dev/tty") ; + } + } + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} + strcpy(lBuff, "Save file in "); + strcat(lBuff, getCurDir()); + lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, lBuff, ""); + if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; + if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ + p = lBuff; + + getPathWithoutFinalSlash( lString , p ) ; + if ( strlen( lString ) && ! dirExists( lString ) ) + { + return NULL ; + } + getLastName(lString,p); + if ( ! strlen(lString) ) + { + return NULL; + } + return p ; + } + + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; + if ( ! ( lIn = popen( lDialogString , "r" ) ) ) + { + return NULL ; + } + while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + {} + pclose( lIn ) ; + if ( strlen(lBuff) && lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + /* printf( "lBuff: %s\n" , lBuff ) ; */ + if ( ! strlen(lBuff) ) + { return NULL; - } - return p; - } - if (tinyfd_verbose) printf("dialogString: %s\n", dialogString); - FILE *pin = popen(dialogString, "r"); - if (!pin) { - return NULL; } - while (fgets(resultBuff, sizeof(resultBuff), pin) != NULL) {} - pclose(pin); - if (resultBuff[strlen(resultBuff) - 1] == '\n') { - resultBuff[strlen(resultBuff) - 1] = '\0'; + + getPathWithoutFinalSlash( lString , lBuff ) ; + if ( strlen( lString ) && ! dirExists( lString ) ) + { + return NULL ; + } + strcpy(lLastDirectory, lString) ; + + getLastName(lString,lBuff); + if ( ! filenameValid(lString) ) + { + return NULL; } - if (!dirExists(resultBuff)) { - return NULL; + return lBuff ; +} + + +/* in case of multiple files, the separator is | */ +char * tinyfd_openFileDialog( + char const * aTitle , /* NULL or "" */ + char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */ + int aNumOfFilterPatterns , /* 0 */ + char const * const * aFilterPatterns , /* NULL or {"*.jpg","*.png"} */ + char const * aSingleFilterDescription , /* NULL or "image files" */ + int aAllowMultipleSelects ) /* 0 or 1 */ +{ + static char * lBuff = NULL; + static char lLastDirectory[MAX_PATH_OR_CMD] = "$PWD" ; + + char lDialogString[MAX_PATH_OR_CMD] ; + char lString[MAX_PATH_OR_CMD] ; + int i ; + FILE * lIn ; + char * p ; + char * lPointerInputBox ; + size_t lFullBuffLen ; + int lWasKdialog = 0 ; + int lWasGraphicDialog = 0 ; + int lWasXterm = 0 ; + + if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; + if (tfd_quoteDetected(aTitle)) return tinyfd_openFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); + if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_openFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); + if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_openFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES", aAllowMultipleSelects); + for (i = 0; i < aNumOfFilterPatterns; i++) + { + if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_openFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects); + } + + free(lBuff); + if (aTitle&&!strcmp(aTitle,"tinyfd_query")) + { + lBuff = NULL; + } + else + { + if (aAllowMultipleSelects) + { + lFullBuffLen = MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; + lBuff = (char *) malloc(lFullBuffLen * sizeof(char)); + if (!lBuff) + { + lFullBuffLen = LOW_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; + lBuff = (char *) malloc( lFullBuffLen * sizeof(char)); + } + } + else + { + lFullBuffLen = MAX_PATH_OR_CMD + 1; + lBuff = (char *) malloc(lFullBuffLen * sizeof(char)); + } + if (!lBuff) return NULL; + lBuff[0]='\0'; + } + + if ( osascriptPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} + strcpy( lDialogString , "osascript "); + if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); + strcat( lDialogString , " -e 'try' -e '" ); + if ( ! aAllowMultipleSelects ) + { + + + strcat( lDialogString , "POSIX path of ( " ); + } + else + { + strcat( lDialogString , "set mylist to " ); + } + strcat( lDialogString , "choose file " ); + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "with prompt \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "default location \"") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "\" " ) ; + } + if ( aNumOfFilterPatterns > 0 ) + { + strcat(lDialogString , "of type {\"" ); + strcat( lDialogString , aFilterPatterns[0] + 2 ) ; + strcat( lDialogString , "\"" ) ; + for ( i = 1 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , ",\"" ) ; + strcat( lDialogString , aFilterPatterns[i] + 2) ; + strcat( lDialogString , "\"" ) ; + } + strcat( lDialogString , "} " ) ; + } + if ( aAllowMultipleSelects ) + { + strcat( lDialogString , "multiple selections allowed true ' " ) ; + strcat( lDialogString , + "-e 'set mystring to POSIX path of item 1 of mylist' " ); + strcat( lDialogString , + "-e 'repeat with i from 2 to the count of mylist' " ); + strcat( lDialogString , "-e 'set mystring to mystring & \"|\"' " ); + strcat( lDialogString , + "-e 'set mystring to mystring & POSIX path of item i of mylist' " ); + strcat( lDialogString , "-e 'end repeat' " ); + strcat( lDialogString , "-e 'mystring' " ); + } + else + { + strcat( lDialogString , ")' " ) ; + } + strcat(lDialogString, "-e 'on error number -128' " ) ; + strcat(lDialogString, "-e 'end try'") ; + if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; + } + else if ( tfd_kdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} + lWasKdialog = 1 ; + + strcpy( lDialogString , "kdialog" ) ; + if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + strcat( lDialogString , " --getopenfilename " ) ; + + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + if ( aDefaultPathAndOrFile[0] != '/' ) + { + strcat(lDialogString, lLastDirectory) ; + strcat(lDialogString , "/" ) ; + } + strcat(lDialogString, "\"") ; + strcat(lDialogString, aDefaultPathAndOrFile ) ; + strcat(lDialogString , "\"" ) ; + } + else + { + strcat(lDialogString, lLastDirectory) ; + strcat(lDialogString , "/" ) ; + } + + if ( aNumOfFilterPatterns > 0 ) + { + strcat(lDialogString , " \"" ) ; + strcat( lDialogString , aFilterPatterns[0] ) ; + for ( i = 1 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , " " ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + } + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , " | " ) ; + strcat( lDialogString , aSingleFilterDescription ) ; + } + strcat( lDialogString , "\"" ) ; + } + if ( aAllowMultipleSelects ) + { + strcat( lDialogString , " --multiple --separate-output" ) ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, " --title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + } + } + else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) + { + if ( tfd_zenityPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} + strcpy( lDialogString , "zenity" ) ; + if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + else if ( tfd_matedialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} + strcpy( lDialogString , "matedialog" ) ; + } + else if ( tfd_shellementaryPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} + strcpy( lDialogString , "shellementary" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} + strcpy( lDialogString , "qarma" ) ; + if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + strcat( lDialogString , " --file-selection" ) ; + + if ( aAllowMultipleSelects ) + { + strcat( lDialogString , " --multiple" ) ; + } + + strcat(lDialogString, " --title=\"") ; + if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + strcat(lDialogString, " --filename=\"") ; + strcat(lDialogString, aDefaultPathAndOrFile) ; + strcat(lDialogString, "\"") ; + } + if ( aNumOfFilterPatterns > 0 ) + { + strcat( lDialogString , " --file-filter='" ) ; + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , aSingleFilterDescription ) ; + strcat( lDialogString , " |" ) ; + } + for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , " " ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + } + strcat( lDialogString , "' --file-filter='All files | *'" ) ; + } + if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); + } + else if (tfd_yadPresent()) + { + if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } + strcpy(lDialogString, "yad --file"); + if (aAllowMultipleSelects) + { + strcat(lDialogString, " --multiple"); + } + if (aTitle && strlen(aTitle)) + { + strcat(lDialogString, " --title=\""); + strcat(lDialogString, aTitle); + strcat(lDialogString, "\""); + } + if (aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile)) + { + strcat(lDialogString, " --filename=\""); + strcat(lDialogString, aDefaultPathAndOrFile); + strcat(lDialogString, "\""); + } + if (aNumOfFilterPatterns > 0) + { + strcat(lDialogString, " --file-filter='"); + if (aSingleFilterDescription && strlen(aSingleFilterDescription)) + { + strcat(lDialogString, aSingleFilterDescription); + strcat(lDialogString, " |"); + } + for (i = 0; i < aNumOfFilterPatterns; i++) + { + strcat(lDialogString, " "); + strcat(lDialogString, aFilterPatterns[i]); + } + strcat(lDialogString, "' --file-filter='All files | *'"); + } + if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); + } + else if ( tkinter3Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} + strcpy( lDialogString , gPython3Name ) ; + strcat( lDialogString , + " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"); + strcat( lDialogString , "lFiles=filedialog.askopenfilename("); + if ( aAllowMultipleSelects ) + { + strcat( lDialogString , "multiple=1," ) ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialdir='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + getLastName( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialfile='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + } + if ( ( aNumOfFilterPatterns > 1 ) + || ( ( aNumOfFilterPatterns == 1 ) /*test because poor osx behaviour*/ + && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) + { + strcat(lDialogString , "filetypes=(" ) ; + strcat( lDialogString , "('" ) ; + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , aSingleFilterDescription ) ; + } + strcat( lDialogString , "',(" ) ; + for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , "'" ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + strcat( lDialogString , "'," ) ; + } + strcat( lDialogString , "))," ) ; + strcat( lDialogString , "('All files','*'))" ) ; + } + strcat( lDialogString , ");\ +\nif not isinstance(lFiles, tuple):\n\tprint(lFiles)\nelse:\ +\n\tlFilesString=''\n\tfor lFile in lFiles:\n\t\tlFilesString+=str(lFile)+'|'\ +\n\tprint(lFilesString[:-1])\n\"" ) ; + } + else if ( tkinter2Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} + strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; + strcat( lDialogString , gPython2Name ) ; + if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) + { + strcat( lDialogString , " -i" ) ; /* for osx without console */ + } + strcat( lDialogString , +" -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); + + if ( tfd_isDarwin( ) ) + { + strcat( lDialogString , +"import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ +frontmost of process \\\"Python\\\" to true' ''');"); + } + strcat( lDialogString , "lFiles=tkFileDialog.askopenfilename("); + if ( aAllowMultipleSelects ) + { + strcat( lDialogString , "multiple=1," ) ; + } + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialdir='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + getLastName( lString , aDefaultPathAndOrFile ) ; + if ( strlen(lString) ) + { + strcat(lDialogString, "initialfile='") ; + strcat(lDialogString, lString ) ; + strcat(lDialogString , "'," ) ; + } + } + if ( ( aNumOfFilterPatterns > 1 ) + || ( ( aNumOfFilterPatterns == 1 ) /*test because poor osx behaviour*/ + && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) + { + strcat(lDialogString , "filetypes=(" ) ; + strcat( lDialogString , "('" ) ; + if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) + { + strcat( lDialogString , aSingleFilterDescription ) ; + } + strcat( lDialogString , "',(" ) ; + for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) + { + strcat( lDialogString , "'" ) ; + strcat( lDialogString , aFilterPatterns[i] ) ; + strcat( lDialogString , "'," ) ; + } + strcat( lDialogString , "))," ) ; + strcat( lDialogString , "('All files','*'))" ) ; + } + strcat( lDialogString , ");\ +\nif not isinstance(lFiles, tuple):\n\tprint lFiles\nelse:\ +\n\tlFilesString=''\n\tfor lFile in lFiles:\n\t\tlFilesString+=str(lFile)+'|'\ +\n\tprint lFilesString[:-1]\n\"" ) ; + } + else if ( xdialogPresent() || dialogName() ) + { + if ( xdialogPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} + lWasGraphicDialog = 1 ; + strcpy( lDialogString , "(Xdialog " ) ; + } + else if ( isTerminalRunning( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + strcpy( lDialogString , "(dialog " ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + lWasXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'(" ) ; + strcat( lDialogString , dialogName() ) ; + strcat( lDialogString , " " ) ; + } + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + if ( !xdialogPresent() && !gdialogPresent() ) + { + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, + "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; + strcat(lDialogString, "\" ") ; + } + + strcat( lDialogString , "--fselect \"" ) ; + if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) + { + if ( ! strchr(aDefaultPathAndOrFile, '/') ) + { + strcat(lDialogString, "./") ; + } + strcat(lDialogString, aDefaultPathAndOrFile) ; + } + else if ( ! isTerminalRunning( ) && !lWasGraphicDialog ) + { + strcat(lDialogString, getenv("HOME")) ; + strcat(lDialogString, "/"); + } + else + { + strcat(lDialogString, "./") ; + } + + if ( lWasGraphicDialog ) + { + strcat(lDialogString, "\" 0 60 ) 2>&1 ") ; + } + else + { + strcat(lDialogString, "\" 0 60 >/dev/tty) ") ; + if ( lWasXterm ) + { + strcat( lDialogString , + "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); + } + else + { + strcat(lDialogString, "2>&1 ; clear >/dev/tty") ; + } + } + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} + strcpy(lBuff, "Open file from "); + strcat(lBuff, getCurDir()); + lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, lBuff, ""); + if ( p ) strcpy(lBuff, p); else lBuff[0] = '\0'; + if (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */ + if ( ! fileExists(lBuff) ) + { + free(lBuff); + lBuff = NULL; + } + else + { + lBuff = (char *)( realloc( lBuff, (strlen(lBuff)+1) * sizeof(char))); + } + return lBuff ; + } + + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; + if ( ! ( lIn = popen( lDialogString , "r" ) ) ) + { + free(lBuff); + lBuff = NULL; + return NULL ; + } + lBuff[0]='\0'; + p = lBuff; + while ( fgets( p , sizeof( lBuff ) , lIn ) != NULL ) + { + p += strlen( p ); + } + pclose( lIn ) ; + + if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + /* printf( "strlen lBuff: %d\n" , strlen( lBuff ) ) ; */ + if ( lWasKdialog && aAllowMultipleSelects ) + { + p = lBuff ; + while ( ( p = strchr( p , '\n' ) ) ) + * p = '|' ; + } + /* printf( "lBuff2: %s\n" , lBuff ) ; */ + if ( ! strlen( lBuff ) ) + { + free(lBuff); + lBuff = NULL; + return NULL; + } + if ( aAllowMultipleSelects && strchr(lBuff, '|') ) + { + if( ! ensureFilesExist( lBuff , lBuff ) ) + { + free(lBuff); + lBuff = NULL; + return NULL; + } + } + else if ( !fileExists(lBuff) ) + { + free(lBuff); + lBuff = NULL; + return NULL; + } + + p = strrchr(lBuff, '|'); + if ( !p ) p = lBuff ; + else p ++ ; + getPathWithoutFinalSlash( lString , p ) ; + /* printf( "lString [%lu]: %s\n" , strlen(lString) , lString ) ; */ + if ( strlen( lString ) && ! dirExists( lString ) ) + { + return NULL ; + } + strcpy(lLastDirectory, lString) ; + + lBuff = (char *)( realloc( lBuff, (strlen(lBuff)+1) * sizeof(char))); + + /*printf( "lBuff3 [%lu]: %s\n" , strlen(lBuff) , lBuff ) ; */ + return lBuff ; +} + + +char * tinyfd_selectFolderDialog( + char const * aTitle , /* "" */ + char const * aDefaultPath ) /* "" */ +{ + static char lBuff[MAX_PATH_OR_CMD] ; + static char lLastDirectory[MAX_PATH_OR_CMD] = "$PWD" ; + + char lDialogString[MAX_PATH_OR_CMD] ; + FILE * lIn ; + char * p ; + char * lPointerInputBox ; + int lWasGraphicDialog = 0 ; + int lWasXterm = 0 ; + lBuff[0]='\0'; + + if (tfd_quoteDetected(aTitle)) return tinyfd_selectFolderDialog("INVALID TITLE WITH QUOTES", aDefaultPath); + if (tfd_quoteDetected(aDefaultPath)) return tinyfd_selectFolderDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES"); + + if ( osascriptPresent( )) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} + strcpy( lDialogString , "osascript "); + if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); + strcat( lDialogString , " -e 'try' -e 'POSIX path of ( choose folder "); + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "with prompt \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + if ( aDefaultPath && strlen(aDefaultPath) ) + { + strcat(lDialogString, "default location \"") ; + strcat(lDialogString, aDefaultPath ) ; + strcat(lDialogString , "\" " ) ; + } + strcat( lDialogString , ")' " ) ; + strcat(lDialogString, "-e 'on error number -128' " ) ; + strcat(lDialogString, "-e 'end try'") ; + if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; + } + else if ( tfd_kdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} + strcpy( lDialogString , "kdialog" ) ; + if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + strcat( lDialogString , " --getexistingdirectory " ) ; + + if ( aDefaultPath && strlen(aDefaultPath) ) + { + if ( aDefaultPath[0] != '/' ) + { + strcat(lDialogString, lLastDirectory) ; + strcat(lDialogString , "/" ) ; + } + strcat(lDialogString, "\"") ; + strcat(lDialogString, aDefaultPath ) ; + strcat(lDialogString , "\"" ) ; + } + else + { + strcat(lDialogString, lLastDirectory) ; + strcat(lDialogString , "/" ) ; + } + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, " --title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + } + } + else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) + { + if ( tfd_zenityPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} + strcpy( lDialogString , "zenity" ) ; + if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + else if ( tfd_matedialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} + strcpy( lDialogString , "matedialog" ) ; + } + else if ( tfd_shellementaryPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} + strcpy( lDialogString , "shellementary" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} + strcpy( lDialogString , "qarma" ) ; + if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + strcat( lDialogString , " --file-selection --directory" ) ; + + strcat(lDialogString, " --title=\"") ; + if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + + if ( aDefaultPath && strlen(aDefaultPath) ) + { + strcat(lDialogString, " --filename=\"") ; + strcat(lDialogString, aDefaultPath) ; + strcat(lDialogString, "\"") ; + } + if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); + } + else if (tfd_yadPresent()) + { + if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } + strcpy(lDialogString, "yad --file --directory"); + if (aTitle && strlen(aTitle)) + { + strcat(lDialogString, " --title=\""); + strcat(lDialogString, aTitle); + strcat(lDialogString, "\""); + } + if (aDefaultPath && strlen(aDefaultPath)) + { + strcat(lDialogString, " --filename=\""); + strcat(lDialogString, aDefaultPath); + strcat(lDialogString, "\""); + } + if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); + } + else if ( !xdialogPresent() && tkinter3Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} + strcpy( lDialogString , gPython3Name ) ; + strcat( lDialogString , + " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"); + strcat( lDialogString , "res=filedialog.askdirectory("); + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultPath && strlen(aDefaultPath) ) + { + strcat(lDialogString, "initialdir='") ; + strcat(lDialogString, aDefaultPath ) ; + strcat(lDialogString , "'" ) ; + } + strcat( lDialogString, ");\nif not isinstance(res, tuple):\n\tprint(res)\n\"" ) ; + } + else if ( !xdialogPresent() && tkinter2Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} + strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; + strcat( lDialogString , gPython2Name ) ; + if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) + { + strcat( lDialogString , " -i" ) ; /* for osx without console */ + } + strcat( lDialogString , +" -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); + + if ( tfd_isDarwin( ) ) + { + strcat( lDialogString , +"import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ +frontmost of process \\\"Python\\\" to true' ''');"); + } + + strcat( lDialogString , "print tkFileDialog.askdirectory("); + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "',") ; + } + if ( aDefaultPath && strlen(aDefaultPath) ) + { + strcat(lDialogString, "initialdir='") ; + strcat(lDialogString, aDefaultPath ) ; + strcat(lDialogString , "'" ) ; + } + strcat( lDialogString , ")\"" ) ; + } + else if ( xdialogPresent() || dialogName() ) + { + if ( xdialogPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} + lWasGraphicDialog = 1 ; + strcpy( lDialogString , "(Xdialog " ) ; + } + else if ( isTerminalRunning( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + strcpy( lDialogString , "(dialog " ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} + lWasXterm = 1 ; + strcpy( lDialogString , terminalName() ) ; + strcat( lDialogString , "'(" ) ; + strcat( lDialogString , dialogName() ) ; + strcat( lDialogString , " " ) ; + } + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, "--title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\" ") ; + } + + if ( !xdialogPresent() && !gdialogPresent() ) + { + strcat(lDialogString, "--backtitle \"") ; + strcat(lDialogString, + "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; + strcat(lDialogString, "\" ") ; + } + + strcat( lDialogString , "--dselect \"" ) ; + if ( aDefaultPath && strlen(aDefaultPath) ) + { + strcat(lDialogString, aDefaultPath) ; + ensureFinalSlash(lDialogString); + } + else if ( ! isTerminalRunning( ) && !lWasGraphicDialog ) + { + strcat(lDialogString, getenv("HOME")) ; + strcat(lDialogString, "/"); + } + else + { + strcat(lDialogString, "./") ; + } + + if ( lWasGraphicDialog ) + { + strcat(lDialogString, "\" 0 60 ) 2>&1 ") ; + } + else + { + strcat(lDialogString, "\" 0 60 >/dev/tty) ") ; + if ( lWasXterm ) + { + strcat( lDialogString , + "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); + } + else + { + strcat(lDialogString, "2>&1 ; clear >/dev/tty") ; + } + } + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} + strcpy(lBuff, "Select folder from "); + strcat(lBuff, getCurDir()); + lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, lBuff, ""); + if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; + if (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */ + p = lBuff; + + if ( !p || ! strlen( p ) || ! dirExists( p ) ) + { + return NULL ; + } + return p ; + } + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; + if ( ! ( lIn = popen( lDialogString , "r" ) ) ) + { + return NULL ; + } + while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + {} + pclose( lIn ) ; + if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + /* printf( "lBuff: %s\n" , lBuff ) ; */ + if ( ! strlen( lBuff ) || ! dirExists( lBuff ) ) + { + return NULL ; } - return resultBuff; + + getPathWithoutFinalSlash( lLastDirectory , lBuff ) ; + + return lBuff ; +} + + +/* aDefaultRGB is used only if aDefaultHexRGB is absent */ +/* aDefaultRGB and aoResultRGB can be the same array */ +/* returns NULL on cancel */ +/* returns the hexcolor as a string "#FF0000" */ +/* aoResultRGB also contains the result */ +char * tinyfd_colorChooser( + char const * aTitle , /* NULL or "" */ + char const * aDefaultHexRGB , /* NULL or "#FF0000"*/ + unsigned char const aDefaultRGB[3] , /* { 0 , 255 , 255 } */ + unsigned char aoResultRGB[3] ) /* { 0 , 0 , 0 } */ +{ + static char lDefaultHexRGB[16]; + char lBuff[128] ; + + char lTmp[128] ; +#if !((defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)) + char * lTmp2 ; +#endif + char lDialogString[MAX_PATH_OR_CMD] ; + unsigned char lDefaultRGB[3]; + char * p; + char * lPointerInputBox; + FILE * lIn ; + int i ; + int lWasZenity3 = 0 ; + int lWasOsascript = 0 ; + int lWasXdialog = 0 ; + lBuff[0]='\0'; + + if (tfd_quoteDetected(aTitle)) return tinyfd_colorChooser("INVALID TITLE WITH QUOTES", aDefaultHexRGB, aDefaultRGB, aoResultRGB); + if (tfd_quoteDetected(aDefaultHexRGB)) return tinyfd_colorChooser(aTitle, "INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultRGB, aoResultRGB); + + if (aDefaultHexRGB && (strlen(aDefaultHexRGB)==7) ) + { + Hex2RGB(aDefaultHexRGB, lDefaultRGB); + strcpy(lDefaultHexRGB, aDefaultHexRGB); + } + else + { + lDefaultRGB[0] = aDefaultRGB[0]; + lDefaultRGB[1] = aDefaultRGB[1]; + lDefaultRGB[2] = aDefaultRGB[2]; + RGB2Hex(aDefaultRGB, lDefaultHexRGB); + } + + if ( osascriptPresent( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} + lWasOsascript = 1 ; + strcpy( lDialogString , "osascript"); + + if ( ! osx9orBetter() ) + { + strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); + strcat( lDialogString , " -e 'try' -e 'set mycolor to choose color default color {"); + } + else + { + strcat( lDialogString , +" -e 'try' -e 'tell app (path to frontmost application as Unicode text) \ +to set mycolor to choose color default color {"); + } + + sprintf(lTmp, "%d", 256 * lDefaultRGB[0] ) ; + strcat(lDialogString, lTmp ) ; + strcat(lDialogString, "," ) ; + sprintf(lTmp, "%d", 256 * lDefaultRGB[1] ) ; + strcat(lDialogString, lTmp ) ; + strcat(lDialogString, "," ) ; + sprintf(lTmp, "%d", 256 * lDefaultRGB[2] ) ; + strcat(lDialogString, lTmp ) ; + strcat(lDialogString, "}' " ) ; + strcat( lDialogString , +"-e 'set mystring to ((item 1 of mycolor) div 256 as integer) as string' " ); + strcat( lDialogString , +"-e 'repeat with i from 2 to the count of mycolor' " ); + strcat( lDialogString , +"-e 'set mystring to mystring & \" \" & ((item i of mycolor) div 256 as integer) as string' " ); + strcat( lDialogString , "-e 'end repeat' " ); + strcat( lDialogString , "-e 'mystring' "); + strcat(lDialogString, "-e 'on error number -128' " ) ; + strcat(lDialogString, "-e 'end try'") ; + if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; + } + else if ( tfd_kdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} + strcpy( lDialogString , "kdialog" ) ; + if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + sprintf( lDialogString + strlen(lDialogString) , " --getcolor --default '%s'" , lDefaultHexRGB ) ; + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, " --title \"") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + } + } + else if ( tfd_zenity3Present() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) + { + lWasZenity3 = 1 ; + if ( tfd_zenity3Present() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity3");return (char *)1;} + strcpy( lDialogString , "zenity" ); + if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + else if ( tfd_matedialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} + strcpy( lDialogString , "matedialog" ) ; + } + else if ( tfd_shellementaryPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} + strcpy( lDialogString , "shellementary" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} + strcpy( lDialogString , "qarma" ) ; + if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) + { + strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ + } + } + strcat( lDialogString , " --color-selection --show-palette" ) ; + sprintf( lDialogString + strlen(lDialogString), " --color=%s" , lDefaultHexRGB ) ; + + strcat(lDialogString, " --title=\"") ; + if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; + strcat(lDialogString, "\"") ; + + if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); + } + else if (tfd_yadPresent()) + { + if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } + strcpy(lDialogString, "yad --color"); + sprintf(lDialogString + strlen(lDialogString), " --init-color=%s", lDefaultHexRGB); + if (aTitle && strlen(aTitle)) + { + strcat(lDialogString, " --title=\""); + strcat(lDialogString, aTitle); + strcat(lDialogString, "\""); + } + if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); + } + else if ( xdialogPresent() ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} + lWasXdialog = 1 ; + strcpy( lDialogString , "Xdialog --colorsel \"" ) ; + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, aTitle) ; + } + strcat(lDialogString, "\" 0 60 ") ; +#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) + sprintf(lTmp,"%hhu %hhu %hhu",lDefaultRGB[0],lDefaultRGB[1],lDefaultRGB[2]); +#else + sprintf(lTmp,"%hu %hu %hu",lDefaultRGB[0],lDefaultRGB[1],lDefaultRGB[2]); +#endif + strcat(lDialogString, lTmp) ; + strcat(lDialogString, " 2>&1"); + } + else if ( tkinter3Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} + strcpy( lDialogString , gPython3Name ) ; + strcat( lDialogString , + " -S -c \"import tkinter;from tkinter import colorchooser;root=tkinter.Tk();root.withdraw();"); + strcat( lDialogString , "res=colorchooser.askcolor(color='" ) ; + strcat(lDialogString, lDefaultHexRGB ) ; + strcat(lDialogString, "'") ; + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, ",title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "'") ; + } + strcat( lDialogString , ");\ +\nif res[1] is not None:\n\tprint(res[1])\"" ) ; + } + else if ( tkinter2Present( ) ) + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} + strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; + strcat( lDialogString , gPython2Name ) ; + if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) + { + strcat( lDialogString , " -i" ) ; /* for osx without console */ + } + + strcat( lDialogString , +" -S -c \"import Tkinter,tkColorChooser;root=Tkinter.Tk();root.withdraw();"); + + if ( tfd_isDarwin( ) ) + { + strcat( lDialogString , +"import os;os.system('''osascript -e 'tell app \\\"Finder\\\" to set \ +frontmost of process \\\"Python\\\" to true' ''');"); + } + + strcat( lDialogString , "res=tkColorChooser.askcolor(color='" ) ; + strcat(lDialogString, lDefaultHexRGB ) ; + strcat(lDialogString, "'") ; + + + if ( aTitle && strlen(aTitle) ) + { + strcat(lDialogString, ",title='") ; + strcat(lDialogString, aTitle) ; + strcat(lDialogString, "'") ; + } + strcat( lDialogString , ");\ +\nif res[1] is not None:\n\tprint res[1]\"" ) ; + } + else + { + if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} + lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ + if (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ + p = tinyfd_inputBox(aTitle, "Enter hex rgb color (i.e. #f5ca20)", lDefaultHexRGB); + + if ( !p || (strlen(p) != 7) || (p[0] != '#') ) + { + return NULL ; + } + for ( i = 1 ; i < 7 ; i ++ ) + { + if ( ! isxdigit( (int) p[i] ) ) + { + return NULL ; + } + } + Hex2RGB(p,aoResultRGB); + strcpy(lDefaultHexRGB, p); + if (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */ + return lDefaultHexRGB; + } + + if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; + if ( ! ( lIn = popen( lDialogString , "r" ) ) ) + { + return NULL ; + } + while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) + { + } + pclose( lIn ) ; + if ( ! strlen( lBuff ) ) + { + return NULL ; + } + /* printf( "len Buff: %lu\n" , strlen(lBuff) ) ; */ + /* printf( "lBuff0: %s\n" , lBuff ) ; */ + if ( lBuff[strlen( lBuff ) -1] == '\n' ) + { + lBuff[strlen( lBuff ) -1] = '\0' ; + } + + if ( lWasZenity3 ) + { + if ( lBuff[0] == '#' ) + { + if ( strlen(lBuff)>7 ) + { + lBuff[3]=lBuff[5]; + lBuff[4]=lBuff[6]; + lBuff[5]=lBuff[9]; + lBuff[6]=lBuff[10]; + lBuff[7]='\0'; + } + Hex2RGB(lBuff,aoResultRGB); + } + else if ( lBuff[3] == '(' ) { +#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) + sscanf(lBuff,"rgb(%hhu,%hhu,%hhu", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]); +#else + aoResultRGB[0] = (unsigned char) strtol(lBuff+4, & lTmp2, 10 ); + aoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 ); + aoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 ); +#endif + RGB2Hex(aoResultRGB,lBuff); + } + else if ( lBuff[4] == '(' ) { +#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) + sscanf(lBuff,"rgba(%hhu,%hhu,%hhu", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]); +#else + aoResultRGB[0] = (unsigned char) strtol(lBuff+5, & lTmp2, 10 ); + aoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 ); + aoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 ); +#endif + RGB2Hex(aoResultRGB,lBuff); + } + } + else if ( lWasOsascript || lWasXdialog ) + { + /* printf( "lBuff: %s\n" , lBuff ) ; */ +#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) + sscanf(lBuff,"%hhu %hhu %hhu", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]); +#else + aoResultRGB[0] = (unsigned char) strtol(lBuff, & lTmp2, 10 ); + aoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 ); + aoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 ); +#endif + RGB2Hex(aoResultRGB,lBuff); + } + else + { + Hex2RGB(lBuff,aoResultRGB); + } + /* printf("%d %d %d\n", aoResultRGB[0],aoResultRGB[1],aoResultRGB[2]); */ + /* printf( "lBuff: %s\n" , lBuff ) ; */ + + strcpy(lDefaultHexRGB,lBuff); + return lDefaultHexRGB ; } #endif /* _WIN32 */ +/* Modified prototypes for R */ + +void tfd_messageBox( + char const * aTitle , + char const * aMessage , + char const * aDialogType , + char const * aIconType , + int * aiDefaultButton ) +{ + * aiDefaultButton = tinyfd_messageBox( aTitle , aMessage , aDialogType , aIconType , * aiDefaultButton ) ; +} + + +void tfd_inputBox( + char const * aTitle , + char const * aMessage , + char * * aiDefaultInput ) +{ + char * lReturnedInput ; + if ( ! strcmp( * aiDefaultInput , "NULL") ) lReturnedInput = tinyfd_inputBox( aTitle , aMessage , NULL ) ; + else lReturnedInput = tinyfd_inputBox( aTitle , aMessage , * aiDefaultInput ) ; + + if ( lReturnedInput ) strcpy ( * aiDefaultInput , lReturnedInput ) ; + else strcpy ( * aiDefaultInput , "NULL" ) ; +} + + +void tfd_saveFileDialog( + char const * aTitle , + char * * aiDefaultPathAndFile , + int const * aNumOfFilterPatterns , + char const * const * aFilterPatterns , + char const * aSingleFilterDescription ) +{ + char * lSavefile ; + + /* printf( "aFilterPatterns %s\n" , aFilterPatterns [0]); */ + + lSavefile = tinyfd_saveFileDialog( aTitle , * aiDefaultPathAndFile , * aNumOfFilterPatterns , + aFilterPatterns, aSingleFilterDescription ) ; + if ( lSavefile ) strcpy ( * aiDefaultPathAndFile , lSavefile ) ; + else strcpy ( * aiDefaultPathAndFile , "NULL" ) ; +} + + +void tfd_openFileDialog( + char const * aTitle , + char * * aiDefaultPathAndFile , + int const * aNumOfFilterPatterns , + char const * const * aFilterPatterns , + char const * aSingleFilterDescription , + int const * aAllowMultipleSelects ) +{ + char * lOpenfile ; + + /* printf( "aFilterPatterns %s\n" , aFilterPatterns [0]); */ + + lOpenfile = tinyfd_openFileDialog( aTitle , * aiDefaultPathAndFile , * aNumOfFilterPatterns , + aFilterPatterns , aSingleFilterDescription , * aAllowMultipleSelects ) ; + + if ( lOpenfile ) strcpy ( * aiDefaultPathAndFile , lOpenfile ) ; + else strcpy ( * aiDefaultPathAndFile , "NULL" ) ; +} + + +void tfd_selectFolderDialog( + char const * aTitle , + char * * aiDefaultPath ) +{ + char * lSelectedfolder ; + lSelectedfolder = tinyfd_selectFolderDialog( aTitle, * aiDefaultPath ) ; + if ( lSelectedfolder ) strcpy ( * aiDefaultPath , lSelectedfolder ) ; + else strcpy ( * aiDefaultPath , "NULL" ) ; +} + + +void tfd_colorChooser( + char const * aTitle , + char * * aiDefaultHexRGB ) +{ + unsigned char const aDefaultRGB [ 3 ] = {128,128,128} ; + unsigned char aoResultRGB [ 3 ] = {128,128,128} ; + char * lChosenColor ; + lChosenColor = tinyfd_colorChooser( aTitle, * aiDefaultHexRGB, aDefaultRGB, aoResultRGB ) ; + if ( lChosenColor ) strcpy ( * aiDefaultHexRGB , lChosenColor ) ; + else strcpy ( * aiDefaultHexRGB , "NULL" ) ; +} + +/* end of Modified prototypes for R */ + + + +/* +int main( int argc , char * argv[] ) +{ +char const * lTmp; +char const * lTheSaveFileName; +char const * lTheOpenFileName; +char const * lTheSelectFolderName; +char const * lTheHexColor; +char const * lWillBeGraphicMode; +unsigned char lRgbColor[3]; +FILE * lIn; +char lBuffer[1024]; +char lString[1024]; +char const * lFilterPatterns[2] = { "*.txt", "*.text" }; + +tinyfd_verbose = argc - 1; +tinyfd_silent = 1; + +lWillBeGraphicMode = tinyfd_inputBox("tinyfd_query", NULL, NULL); + +strcpy(lBuffer, "v"); +strcat(lBuffer, tinyfd_version); +if (lWillBeGraphicMode) +{ + strcat(lBuffer, "\ngraphic mode: "); +} +else +{ + strcat(lBuffer, "\nconsole mode: "); +} +strcat(lBuffer, tinyfd_response); +strcat(lBuffer, "\n"); +strcat(lBuffer, tinyfd_needs+78); +strcpy(lString, "tinyfiledialogs"); +tinyfd_messageBox(lString, lBuffer, "ok", "info", 0); + +tinyfd_notifyPopup("the title", "the message\n\tfrom outer-space", "info"); + +if (lWillBeGraphicMode && !tinyfd_forceConsole) +{ + tinyfd_forceConsole = ! tinyfd_messageBox("Hello World", + "graphic dialogs [yes] / console mode [no]?", + "yesno", "question", 1); +} + +lTmp = tinyfd_inputBox( + "a password box", "your password will be revealed", NULL); + +if (!lTmp) return 1; + +strcpy(lString, lTmp); + +lTheSaveFileName = tinyfd_saveFileDialog( + "let us save this password", + "passwordFile.txt", + 2, + lFilterPatterns, + NULL); + +if (!lTheSaveFileName) +{ + tinyfd_messageBox( + "Error", + "Save file name is NULL", + "ok", + "error", + 1); + return 1; +} + +lIn = fopen(lTheSaveFileName, "w"); +if (!lIn) +{ + tinyfd_messageBox( + "Error", + "Can not open this file in write mode", + "ok", + "error", + 1); + return 1; +} +fputs(lString, lIn); +fclose(lIn); + +lTheOpenFileName = tinyfd_openFileDialog( + "let us read the password back", + "", + 2, + lFilterPatterns, + NULL, + 0); + +if (!lTheOpenFileName) +{ + tinyfd_messageBox( + "Error", + "Open file name is NULL", + "ok", + "error", + 1); + return 1; +} + +lIn = fopen(lTheOpenFileName, "r"); + +if (!lIn) +{ + tinyfd_messageBox( + "Error", + "Can not open this file in read mode", + "ok", + "error", + 1); + return(1); +} +lBuffer[0] = '\0'; +fgets(lBuffer, sizeof(lBuffer), lIn); +fclose(lIn); + +tinyfd_messageBox("your password is", + lBuffer, "ok", "info", 1); + +lTheSelectFolderName = tinyfd_selectFolderDialog( + "let us just select a directory", NULL); + +if (!lTheSelectFolderName) +{ + tinyfd_messageBox( + "Error", + "Select folder name is NULL", + "ok", + "error", + 1); + return 1; +} + +tinyfd_messageBox("The selected folder is", + lTheSelectFolderName, "ok", "info", 1); + +lTheHexColor = tinyfd_colorChooser( + "choose a nice color", + "#FF0077", + lRgbColor, + lRgbColor); + +if (!lTheHexColor) +{ + tinyfd_messageBox( + "Error", + "hexcolor is NULL", + "ok", + "error", + 1); + return 1; +} + +tinyfd_messageBox("The selected hexcolor is", + lTheHexColor, "ok", "info", 1); + + tinyfd_beep(); + + return 0; +} +*/ + #ifdef _MSC_VER #pragma warning(default:4996) #pragma warning(default:4100) #pragma warning(default:4706) #endif -#endif /* USE_TINYFILEDIALOGS */ +#endif // USE_TINYFILEDIALOGS diff --git a/ext/tinyfiledialogs/tinyfiledialogs.h b/ext/tinyfiledialogs/tinyfiledialogs.h index bb294da2e5..59997e7ab5 100644 --- a/ext/tinyfiledialogs/tinyfiledialogs.h +++ b/ext/tinyfiledialogs/tinyfiledialogs.h @@ -1,26 +1,48 @@ -/* -Note: This file is a heavily stripped-down version of Tinyfiledialogs, customized for Julius. - -If you are interested in Tinyfiledialogs, please download its original version from the links below. +/* SPDX-License-Identifier: Zlib +Copyright (c) 2014 - 2024 Guillaume Vareille http://ysengrin.com + ____________________________________________________________________ + | | + | 100% compatible C C++ -> You can rename tinfiledialogs.c as .cpp | + |____________________________________________________________________| -Tinyfiledialogs: -Copyright (c) 2014 - 2018 Guillaume Vareille http://ysengrin.com -http://tinyfiledialogs.sourceforge.net +********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE ********* + _________ + / \ tinyfiledialogs.h v3.18.1 [May 2, 2024] + |tiny file| Unique header file created [November 9, 2014] + | dialogs | + \____ ___/ http://tinyfiledialogs.sourceforge.net + \| git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd + ____________________________________________ +| | +| email: tinyfiledialogs at ysengrin.com | +|____________________________________________| + ________________________________________________________________________________ +| ____________________________________________________________________________ | +| | | | +| | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | | +| | | | +| | on windows: | | +| | - for UTF-16, use the wchar_t functions at the bottom of the header file | | +| | | | +| | - _wfopen() requires wchar_t | | +| | - fopen() uses char but expects ASCII or MBCS (not UTF-8) | | +| | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | | +| | | | +| | - alternatively, tinyfiledialogs provides | | +| | functions to convert between UTF-8, UTF-16 and MBCS | | +| |____________________________________________________________________________| | +|________________________________________________________________________________| -Thanks for contributions, bug corrections & thorough testing to: -- Don Heyse http://ldglite.sf.net for bug corrections & thorough testing! -- Paul Rouget +If you like tinyfiledialogs, please upvote my stackoverflow answer +https://stackoverflow.com/a/47651444 - License - - This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. - Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be @@ -28,6 +50,13 @@ appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. + + __________________________________________ + | ______________________________________ | + | | | | + | | DO NOT USE USER INPUT IN THE DIALOGS | | + | |______________________________________| | + |__________________________________________| */ #ifndef TINYFILEDIALOGS_H @@ -43,27 +72,252 @@ misrepresented as being the original software. extern "C" { #endif -extern char const tinyfd_needs[]; /* info about requirements */ +/******************************************************************************************************/ +/**************************************** UTF-8 on Windows ********************************************/ +/******************************************************************************************************/ +#ifdef _WIN32 +#include + +/* On windows, if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of this file ) +Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */ +extern int tinyfd_winUtf8; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */ +/* for MBCS change this to 0, in tinyfiledialogs.c or in your code */ + +/* Here are some functions to help you convert between UTF-16 UTF-8 MBSC */ +char * tinyfd_utf8toMbcs(char const * aUtf8string); +char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string); +wchar_t * tinyfd_mbcsTo16(char const * aMbcsString); +char * tinyfd_mbcsTo8(char const * aMbcsString); +wchar_t * tinyfd_utf8to16(char const * aUtf8string); +char * tinyfd_utf16to8(wchar_t const * aUtf16string); +#endif +/******************************************************************************************************/ +/******************************************************************************************************/ +/******************************************************************************************************/ + +/************* 3 funtions for C# (you don't need this in C or C++) : */ +char const * tinyfd_getGlobalChar(char const * aCharVariableName); /* returns NULL on error */ +int tinyfd_getGlobalInt(char const * aIntVariableName); /* returns -1 on error */ +int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue); /* returns -1 on error */ +/* aCharVariableName: "tinyfd_version" "tinyfd_needs" "tinyfd_response" + aIntVariableName : "tinyfd_verbose" "tinyfd_silent" "tinyfd_allowCursesDialogs" + "tinyfd_forceConsole" "tinyfd_assumeGraphicDisplay" "tinyfd_winUtf8" +**************/ + +extern char tinyfd_version[8]; /* contains tinyfd current version number */ +extern char tinyfd_needs[]; /* info about requirements */ extern int tinyfd_verbose; /* 0 (default) or 1 : on unix, prints the command line calls */ -extern int tinyfd_silent; /* 1 (default) or 0 : on unix, - hide errors and warnings from called dialog*/ +extern int tinyfd_silent; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */ + +/** Curses dialogs are difficult to use and counter-intuitive. +On windows they are only ascii and still uses the unix backslash ! **/ +extern int tinyfd_allowCursesDialogs; /* 0 (default) or 1 */ + +extern int tinyfd_forceConsole; /* 0 (default) or 1 */ +/* for unix & windows: 0 (graphic mode) or 1 (console mode). +0: try to use a graphic solution, if it fails then it uses console mode. +1: forces all dialogs into console mode even when an X server is present. + if enabled, it can use the package Dialog or dialog.exe. + on windows it only make sense for console applications */ + +extern int tinyfd_assumeGraphicDisplay; /* 0 (default) or 1 */ +/* some systems don't set the environment variable DISPLAY even when a graphic display is present. +set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */ + +extern char tinyfd_response[1024]; +/* if you pass "tinyfd_query" as aTitle, +the functions will not display the dialogs +but will return 0 for console mode, 1 for graphic mode. +tinyfd_response is then filled with the retain solution. +possible values for tinyfd_response are (all lowercase) +for graphic mode: + windows_wchar windows applescript kdialog zenity zenity3 yad matedialog + shellementary qarma python2-tkinter python3-tkinter python-dbus + perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst +for console mode: + dialog whiptail basicinput no_solution */ + +void tinyfd_beep(void); + +int tinyfd_notifyPopup( + char const * aTitle, /* NULL or "" */ + char const * aMessage, /* NULL or "" may contain \n \t */ + char const * aIconType); /* "info" "warning" "error" */ + /* return has only meaning for tinyfd_query */ int tinyfd_messageBox( - char const * const aTitle , /* NULL or "" */ - char const * const aMessage , /* NULL or "" may contain \n \t */ - char const * const aDialogType , /* "ok" "okcancel" */ - char const * const aIconType , /* "info" "warning" "error" "question" */ - int const aDefaultButton ) ; - /* 0 for cancel, 1 for ok */ - -char const * tinyfd_selectFolderDialog( - char const * const aTitle); /* NULL or "" */ + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" may contain \n \t */ + char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ + char const * aIconType , /* "info" "warning" "error" "question" */ + int aDefaultButton ) ; + /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ + +char * tinyfd_inputBox( + char const * aTitle , /* NULL or "" */ + char const * aMessage , /* NULL or "" (\n and \t have no effect) */ + char const * aDefaultInput ) ; /* NULL = passwordBox, "" = inputbox */ + /* returns NULL on cancel */ + +char * tinyfd_saveFileDialog( + char const * aTitle , /* NULL or "" */ + char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */ + int aNumOfFilterPatterns , /* 0 (1 in the following example) */ + char const * const * aFilterPatterns , /* NULL or char const * lFilterPatterns[1]={"*.txt"} */ + char const * aSingleFilterDescription ) ; /* NULL or "text files" */ + /* returns NULL on cancel */ + +char * tinyfd_openFileDialog( + char const * aTitle, /* NULL or "" */ + char const * aDefaultPathAndOrFile, /* NULL or "" , ends with / to set only a directory */ + int aNumOfFilterPatterns , /* 0 (2 in the following example) */ + char const * const * aFilterPatterns, /* NULL or char const * lFilterPatterns[2]={"*.png","*.jpg"}; */ + char const * aSingleFilterDescription, /* NULL or "image files" */ + int aAllowMultipleSelects ) ; /* 0 or 1 */ + /* in case of multiple files, the separator is | */ /* returns NULL on cancel */ +char * tinyfd_selectFolderDialog( + char const * aTitle, /* NULL or "" */ + char const * aDefaultPath); /* NULL or "" */ + /* returns NULL on cancel */ + +char * tinyfd_colorChooser( + char const * aTitle, /* NULL or "" */ + char const * aDefaultHexRGB, /* NULL or "" or "#FF0000" */ + unsigned char const aDefaultRGB[3] , /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ + unsigned char aoResultRGB[3] ) ; /* unsigned char lResultRGB[3]; */ + /* aDefaultRGB is used only if aDefaultHexRGB is absent */ + /* aDefaultRGB and aoResultRGB can be the same array */ + /* returns NULL on cancel */ + /* returns the hexcolor as a string "#FF0000" */ + /* aoResultRGB also contains the result */ + + +/************ WINDOWS ONLY SECTION ************************/ +#ifdef _WIN32 +/* windows only - utf-16 version */ +int tinyfd_notifyPopupW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ + wchar_t const * aIconType); /* L"info" L"warning" L"error" */ + +/* windows only - utf-16 version */ +int tinyfd_messageBoxW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ + wchar_t const * aDialogType, /* L"ok" L"okcancel" L"yesno" */ + wchar_t const * aIconType, /* L"info" L"warning" L"error" L"question" */ + int aDefaultButton ); /* 0 for cancel/no , 1 for ok/yes */ + /* returns 0 for cancel/no , 1 for ok/yes */ + +/* windows only - utf-16 version */ +wchar_t * tinyfd_inputBoxW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aMessage, /* NULL or L"" (\n nor \t not respected) */ + wchar_t const * aDefaultInput); /* NULL passwordBox, L"" inputbox */ + +/* windows only - utf-16 version */ +wchar_t * tinyfd_saveFileDialogW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */ + int aNumOfFilterPatterns, /* 0 (1 in the following example) */ + wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[1]={L"*.txt"} */ + wchar_t const * aSingleFilterDescription); /* NULL or L"text files" */ + /* returns NULL on cancel */ + +/* windows only - utf-16 version */ +wchar_t * tinyfd_openFileDialogW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */ + int aNumOfFilterPatterns , /* 0 (2 in the following example) */ + wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[2]={L"*.png","*.jpg"} */ + wchar_t const * aSingleFilterDescription, /* NULL or L"image files" */ + int aAllowMultipleSelects ) ; /* 0 or 1 */ + /* in case of multiple files, the separator is | */ + /* returns NULL on cancel */ + +/* windows only - utf-16 version */ +wchar_t * tinyfd_selectFolderDialogW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aDefaultPath); /* NULL or L"" */ + /* returns NULL on cancel */ + +/* windows only - utf-16 version */ +wchar_t * tinyfd_colorChooserW( + wchar_t const * aTitle, /* NULL or L"" */ + wchar_t const * aDefaultHexRGB, /* NULL or L"#FF0000" */ + unsigned char const aDefaultRGB[3], /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ + unsigned char aoResultRGB[3]); /* unsigned char lResultRGB[3]; */ + /* returns the hexcolor as a string L"#FF0000" */ + /* aoResultRGB also contains the result */ + /* aDefaultRGB is used only if aDefaultHexRGB is NULL */ + /* aDefaultRGB and aoResultRGB can be the same array */ + /* returns NULL on cancel */ + +#endif /*_WIN32 */ + #ifdef __cplusplus -} +} /*extern "C"*/ #endif #endif /* USE_TINYFILEDIALOGS */ #endif /* TINYFILEDIALOGS_H */ + +/* + ________________________________________________________________________________ +| ____________________________________________________________________________ | +| | | | +| | on windows: | | +| | - for UTF-16, use the wchar_t functions at the bottom of the header file | | +| | - _wfopen() requires wchar_t | | +| | | | +| | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | | +| | - but fopen() expects MBCS (not UTF-8) | | +| | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | | +| | | | +| | - alternatively, tinyfiledialogs provides | | +| | functions to convert between UTF-8, UTF-16 and MBCS | | +| |____________________________________________________________________________| | +|________________________________________________________________________________| + +- This is not for ios nor android (it works in termux though). +- The files can be renamed with extension ".cpp" as the code is 100% compatible C C++ + (just comment out << extern "C" >> in the header file) +- Windows is fully supported from XP to 10 (maybe even older versions) +- C# & LUA via dll, see files in the folder EXTRAS +- OSX supported from 10.4 to latest (maybe even older versions) +- Do not use " and ' as the dialogs will be displayed with a warning + instead of the title, message, etc... +- There's one file filter only, it may contain several patterns. +- If no filter description is provided, + the list of patterns will become the description. +- On windows link against Comdlg32.lib and Ole32.lib + (on windows the no linking claim is a lie) +- On unix: it tries command line calls, so no such need (NO LINKING). +- On unix you need one of the following: + applescript, kdialog, zenity, matedialog, shellementary, qarma, yad, + python (2 or 3)/tkinter/python-dbus (optional), Xdialog + or curses dialogs (opens terminal if running without console). +- One of those is already included on most (if not all) desktops. +- In the absence of those it will use gdialog, gxmessage or whiptail + with a textinputbox. If nothing is found, it switches to basic console input, + it opens a console if needed (requires xterm + bash). +- for curses dialogs you must set tinyfd_allowCursesDialogs=1 +- You can query the type of dialog that will be used (pass "tinyfd_query" as aTitle) +- String memory is preallocated statically for all the returned values. +- File and path names are tested before return, they should be valid. +- tinyfd_forceConsole=1; at run time, forces dialogs into console mode. +- On windows, console mode only make sense for console applications. +- On windows, console mode is not implemented for wchar_T UTF-16. +- Mutiple selects are not possible in console mode. +- The package dialog must be installed to run in curses dialogs in console mode. + It is already installed on most unix systems. +- On osx, the package dialog can be installed via + http://macappstore.org/dialog or http://macports.org +- On windows, for curses dialogs console mode, + dialog.exe should be copied somewhere on your executable path. + It can be found at the bottom of the following page: + http://andrear.altervista.org/home/cdialog.php +*/ diff --git a/res/asset_packer/src/asset_packer.c b/res/asset_packer/src/asset_packer.c index 93955741f8..6dfc284f99 100644 --- a/res/asset_packer/src/asset_packer.c +++ b/res/asset_packer/src/asset_packer.c @@ -71,6 +71,16 @@ typedef struct { static array(packed_asset) packed_assets; +const char *pref_user_dir(void) +{ + return ""; +} + +int random_from_stdlib(void) +{ + return 0; +} + static int remove_file(const char *filename, long unused) { snprintf(current_file, FILE_NAME_MAX, "%s/%s", PACKED_ASSETS_DIR, filename); @@ -92,8 +102,8 @@ static int prepare_packed_assets_dir(void) return 0; } } - if (!platform_file_manager_create_directory(PACKED_ASSETS_DIR "/" ASSETS_IMAGE_PATH, 1) || - !platform_file_manager_create_directory(PACKED_ASSETS_DIR "/" CURSORS_DIR, 1)) { + if (!platform_file_manager_create_directory(PACKED_ASSETS_DIR "/" ASSETS_IMAGE_PATH, 0, 1) || + !platform_file_manager_create_directory(PACKED_ASSETS_DIR "/" CURSORS_DIR, 0, 1)) { log_error("Failed to create directories", 0, 0); return 0; } @@ -765,7 +775,7 @@ int main(int argc, char **argv) log_info("Copying other assets...", 0, 0); - platform_file_manager_copy_directory(ASSETS_DIRECTORY, PACKED_ASSETS_DIR); + platform_file_manager_copy_directory(ASSETS_DIRECTORY, PACKED_ASSETS_DIR, 1); log_info("All done!", 0, 0); diff --git a/src/campaign/campaign.c b/src/campaign/campaign.c index 0077515acb..dbff763fb4 100644 --- a/src/campaign/campaign.c +++ b/src/campaign/campaign.c @@ -161,7 +161,7 @@ int campaign_load_scenario(int scenario_id) if (!scenario_data) { return 0; } - log_info("Loading custom campaign scenario", scenario->path, scenario->id); + log_info("Loading custom campaign scenario", file_remove_directory(scenario->path), scenario->id); int is_save_game = file_has_extension(scenario->path, "sav") || file_has_extension(scenario->path, "svx"); int result = game_file_start_scenario_from_buffer(scenario_data, (int) length, is_save_game); free(scenario_data); diff --git a/src/campaign/file.c b/src/campaign/file.c index b1a860afad..0b983aef4e 100644 --- a/src/campaign/file.c +++ b/src/campaign/file.c @@ -19,8 +19,8 @@ static struct { int campaign_file_exists(const char *filename) { if (data.is_folder) { - snprintf(&data.file_name[data.file_name_offset], FILE_NAME_MAX - data.file_name_offset, "%s", filename); - return file_exists(data.file_name, NOT_LOCALIZED); + snprintf(&data.file_name[data.file_name_offset], FILE_NAME_MAX - data.file_name_offset, "/%s", filename); + return dir_get_file_at_location(data.file_name, PATH_LOCATION_CAMPAIGN) != 0; } int close_at_end = data.zip.parser == 0; if (!campaign_file_open_zip()) { @@ -37,8 +37,12 @@ int campaign_file_exists(const char *filename) static void *load_file_from_folder(const char *file, size_t *length) { *length = 0; - snprintf(&data.file_name[data.file_name_offset], FILE_NAME_MAX - data.file_name_offset, "%s", file); - FILE *campaign_file = file_open(data.file_name, "rb"); + snprintf(&data.file_name[data.file_name_offset], FILE_NAME_MAX - data.file_name_offset, "/%s", file); + const char *filename = dir_get_file_at_location(data.file_name, PATH_LOCATION_CAMPAIGN); + if (!filename) { + return 0; + } + FILE *campaign_file = file_open(filename, "rb"); if (!campaign_file) { return 0; } @@ -109,12 +113,7 @@ void campaign_file_set_path(const char *path) campaign_file_close_zip(); if (path && path[0]) { data.is_folder = !file_has_extension(path, "campaign"); - if (data.is_folder) { - data.file_name_offset = snprintf(data.file_name, FILE_NAME_MAX, "%s/%s/", CAMPAIGNS_DIR_NAME, path); - } else { - snprintf(data.file_name, FILE_NAME_MAX, "%s", path); - data.file_name_offset = 0; - } + data.file_name_offset = snprintf(data.file_name, FILE_NAME_MAX, "%s", path); } else { data.file_name[0] = 0; data.file_name_offset = 0; @@ -149,7 +148,11 @@ int campaign_file_open_zip(void) return 0; } if (!data.zip.stream) { - data.zip.stream = file_open(data.file_name, "rb"); + const char *filename = dir_get_file_at_location(data.file_name, PATH_LOCATION_CAMPAIGN); + if (!filename) { + return 0; + } + data.zip.stream = file_open(filename, "rb"); if (!data.zip.stream) { return 0; } diff --git a/src/core/config.c b/src/core/config.c index a5f5cf63cc..2869641468 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -1,5 +1,6 @@ #include "config.h" +#include "core/dir.h" #include "core/file.h" #include "core/log.h" @@ -9,6 +10,7 @@ #define MAX_LINE 100 static const char *INI_FILENAME = "augustus.ini"; +static int needs_user_directory_setup; // Keep this in the same order as the config_keys in config.h static const char *ini_keys[] = { @@ -16,6 +18,7 @@ static const char *ini_keys[] = { "master_volume", "enable_audio_in_videos", "video_volume", + "has_set_user_directories", "gameplay_fix_immigration", "gameplay_fix_100y_ghosts", "screen_display_scale", @@ -74,7 +77,7 @@ static const char *ini_keys[] = { }; static const char *ini_string_keys[] = { - "ui_language_dir" + "ui_language_dir", }; static int values[CONFIG_MAX_ENTRIES]; @@ -85,6 +88,7 @@ static int default_values[CONFIG_MAX_ENTRIES] = { [CONFIG_GENERAL_MASTER_VOLUME] = 100, [CONFIG_GENERAL_ENABLE_VIDEO_SOUND] = 1, [CONFIG_GENERAL_VIDEO_VOLUME] = 100, + [CONFIG_GENERAL_HAS_SET_USER_DIRECTORIES] = 1, [CONFIG_UI_SIDEBAR_INFO] = 1, [CONFIG_UI_SMOOTH_SCROLLING] = 1, [CONFIG_UI_SHOW_WATER_STRUCTURE_RANGE] = 1, @@ -97,7 +101,7 @@ static int default_values[CONFIG_MAX_ENTRIES] = { [CONFIG_UI_SHOW_DESIRABILITY_RANGE] = 0, }; -static const char default_string_values[CONFIG_STRING_MAX_ENTRIES][CONFIG_STRING_VALUE_MAX] = { 0 }; +static const char default_string_values[CONFIG_STRING_MAX_ENTRIES][CONFIG_STRING_VALUE_MAX]; int config_get(config_key key) { @@ -145,7 +149,14 @@ static void set_defaults(void) void config_load(void) { set_defaults(); - FILE *fp = file_open(INI_FILENAME, "rt"); + // Override default, if value is the same at end, then we never setup the directories + needs_user_directory_setup = 0; + values[CONFIG_GENERAL_HAS_SET_USER_DIRECTORIES] = -1; + const char *file_name = dir_get_file_at_location(INI_FILENAME, PATH_LOCATION_CONFIG); + if (!file_name) { + return; + } + FILE *fp = file_open(file_name, "rt"); if (!fp) { return; } @@ -180,11 +191,24 @@ void config_load(void) } } file_close(fp); + if (values[CONFIG_GENERAL_HAS_SET_USER_DIRECTORIES] == -1) { + values[CONFIG_GENERAL_HAS_SET_USER_DIRECTORIES] = default_values[CONFIG_GENERAL_HAS_SET_USER_DIRECTORIES]; + needs_user_directory_setup = 1; + } +} + +int config_must_configure_user_directory(void) +{ + return 1; //needs_user_directory_setup; } void config_save(void) { - FILE *fp = file_open(INI_FILENAME, "wt"); + const char *file_name = dir_append_location(INI_FILENAME, PATH_LOCATION_CONFIG); + if (!file_name) { + return; + } + FILE *fp = file_open(file_name, "wt"); if (!fp) { log_error("Unable to write configuration file", INI_FILENAME, 0); return; @@ -196,4 +220,5 @@ void config_save(void) fprintf(fp, "%s=%s\n", ini_string_keys[i], string_values[i]); } file_close(fp); + needs_user_directory_setup = 0; } diff --git a/src/core/config.h b/src/core/config.h index 7a759eba0c..184f78c0d2 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -8,6 +8,7 @@ typedef enum { CONFIG_GENERAL_MASTER_VOLUME, CONFIG_GENERAL_ENABLE_VIDEO_SOUND, CONFIG_GENERAL_VIDEO_VOLUME, + CONFIG_GENERAL_HAS_SET_USER_DIRECTORIES, CONFIG_GP_FIX_IMMIGRATION_BUG, CONFIG_GP_FIX_100_YEAR_GHOSTS, CONFIG_SCREEN_DISPLAY_SCALE, @@ -118,6 +119,13 @@ const char *config_get_default_string_value(config_string_key key); */ void config_load(void); +/** + * Whether the user directory must be configured + * + * @return 1 If the user directory still needs to be set, 0 otherwise + */ +int config_must_configure_user_directory(void); + /** * Save config to file */ diff --git a/src/core/dir.c b/src/core/dir.c index 8b384c80c8..d05a588830 100644 --- a/src/core/dir.c +++ b/src/core/dir.c @@ -14,6 +14,7 @@ static struct { dir_listing listing; int max_files; char *cased_filename; + char current_dir[FILE_NAME_MAX]; } data; static void allocate_listing_files(int min, int max) @@ -27,6 +28,7 @@ static void allocate_listing_files(int min, int max) static void clear_dir_listing(void) { data.listing.num_files = 0; + data.current_dir[0] = 0; if (data.max_files <= 0) { data.listing.files = (dir_entry *) malloc(BASE_MAX_FILES * sizeof(dir_entry)); allocate_listing_files(0, BASE_MAX_FILES); @@ -70,19 +72,31 @@ static int add_to_listing(const char *filename, long modified_time) const dir_listing *dir_find_files_with_extension(const char *dir, const char *extension) { clear_dir_listing(); + snprintf(data.current_dir, FILE_NAME_MAX, "%s", dir); platform_file_manager_list_directory_contents(dir, TYPE_FILE, extension, add_to_listing); qsort(data.listing.files, data.listing.num_files, sizeof(dir_entry), compare_lower); return &data.listing; } +const dir_listing *dir_find_files_with_extension_at_location(int location, const char *extension) +{ + return dir_find_files_with_extension(platform_file_manager_get_directory_for_location(location, 0), extension); +} + const dir_listing *dir_find_all_subdirectories(const char *dir) { clear_dir_listing(); + snprintf(data.current_dir, FILE_NAME_MAX, "%s", dir); platform_file_manager_list_directory_contents(dir, TYPE_DIR, 0, add_to_listing); qsort(data.listing.files, data.listing.num_files, sizeof(dir_entry), compare_lower); return &data.listing; } +const dir_listing *dir_find_all_subdirectories_at_location(int location) +{ + return dir_find_all_subdirectories(platform_file_manager_get_directory_for_location(location, 0)); +} + static int compare_case(const char *filename, long unused) { if (platform_file_manager_compare_filename(filename, data.cased_filename) == 0) { @@ -171,7 +185,7 @@ static const char *get_case_corrected_file(const char *dir, const char *filepath const dir_listing *dir_append_files_with_extension(const char *extension) { - platform_file_manager_list_directory_contents(0, TYPE_FILE, extension, add_to_listing); + platform_file_manager_list_directory_contents(data.current_dir, TYPE_FILE, extension, add_to_listing); qsort(data.listing.files, data.listing.num_files, sizeof(dir_entry), compare_lower); return &data.listing; } @@ -193,7 +207,15 @@ const char *dir_get_file(const char *filepath, int localizable) return get_case_corrected_file(0, filepath); } -const char *dir_get_asset(const char *asset_path, const char *filepath) +const char *dir_get_file_at_location(const char *filename, int location) +{ + return get_case_corrected_file(platform_file_manager_get_directory_for_location(location, 0), filename); +} + +const char *dir_append_location(const char *filename, int location) { - return get_case_corrected_file(asset_path, filepath); + static char corrected_filename[FILE_NAME_MAX]; + snprintf(corrected_filename, FILE_NAME_MAX, "%s%s", + platform_file_manager_get_directory_for_location(location, 0), filename); + return corrected_filename; } diff --git a/src/core/dir.h b/src/core/dir.h index e2a564d28c..551152dcb4 100644 --- a/src/core/dir.h +++ b/src/core/dir.h @@ -17,6 +17,21 @@ enum { MUST_BE_LOCALIZED = 2 }; +enum { + PATH_LOCATION_ROOT = 0, + PATH_LOCATION_CONFIG = 1, + PATH_LOCATION_ASSET = 2, + PATH_LOCATION_SAVEGAME = 3, + PATH_LOCATION_SCENARIO = 4, + PATH_LOCATION_CAMPAIGN = 5, + PATH_LOCATION_SCREENSHOT = 6, + PATH_LOCATION_COMMUNITY = 7, + PATH_LOCATION_EDITOR_CUSTOM_EMPIRES = 8, + PATH_LOCATION_EDITOR_CUSTOM_MESSAGES = 9, + PATH_LOCATION_EDITOR_CUSTOM_EVENTS = 10, + PATH_LOCATION_MAX = 11 +}; + /** * File information */ @@ -40,6 +55,20 @@ typedef struct { * @return Directory listing */ const dir_listing *dir_find_files_with_extension(const char *dir, const char *extension); + +/** + * Finds files with the given extension at the requested location ID + * @param location The location ID where the files should reside + * @param extension Extension of the files to find + * @return Directory listing + */ +const dir_listing *dir_find_files_with_extension_at_location(int location, const char *extension); + +/** + * Appends files with the given extension to the current directory listing + * @param extension Extension of the files to find + * @return Directory listing + */ const dir_listing *dir_append_files_with_extension(const char *extension); /** @@ -49,6 +78,13 @@ const dir_listing *dir_append_files_with_extension(const char *extension); */ const dir_listing *dir_find_all_subdirectories(const char *dir); +/** + * Finds all subdirectories at the requested location ID + * @param location The location ID where the subdirectories should reside + * @return Directory listing + */ +const dir_listing *dir_find_all_subdirectories_at_location(int location); + /** * Get the case sensitive and localized filename of the file * @param filepath File path to match to a case-sensitive file on the filesystem @@ -58,11 +94,19 @@ const dir_listing *dir_find_all_subdirectories(const char *dir); const char *dir_get_file(const char *filepath, int localizable); /** - * Get the case sensitive filename of the asset - * @param asset_path The path to the asset directory - * @param filepath Asset path to match to a case-sensitive asset file on the filesystem + * Get the case sensitive filename from the requested location ID + * @param filename File path to match to a case-sensitive file on the filesystem + * @param location The location ID where the file should reside * @return Corrected file, or NULL if the file was not found */ -const char *dir_get_asset(const char *asset_path, const char *filepath); +const char *dir_get_file_at_location(const char *filepath, int location); + +/** + * Appends the location to the filename + * @param filename File path to append the location to + * @param location The location ID to append to the filename + * @return Filename with location appended + */ +const char *dir_append_location(const char *filename, int location); #endif // CORE_DIR_H diff --git a/src/core/encoding.h b/src/core/encoding.h index 09265b9e77..cc9c6a88d7 100644 --- a/src/core/encoding.h +++ b/src/core/encoding.h @@ -63,7 +63,7 @@ int encoding_can_display(const char *utf8_char); void encoding_to_utf8(const uint8_t *input, char *output, int output_length, int decompose); /** - * Converts the internally-encoded input to UTF-8 output + * Converts the UTF-8 to internally-encoded output * @param input Input to convert, UTF-8 encoded * @param output Output buffer to store the internally encoded input * @param output_length Length of the output buffer diff --git a/src/core/file.c b/src/core/file.c index ca2b182bbc..d38ab62b7b 100644 --- a/src/core/file.c +++ b/src/core/file.c @@ -30,63 +30,56 @@ int file_has_extension(const char *filename, const char *extension) if (!extension || !*extension) { return 1; } - char c; - do { - c = *filename; - filename++; - } while (c != '.' && c); - if (!c) { - filename--; - } - return platform_file_manager_compare_filename(filename, extension) == 0; + filename = strrchr(filename, '.'); + return filename ? platform_file_manager_compare_filename(filename + 1, extension) == 0 : 0; } void file_change_extension(char *filename, const char *new_extension) { - char c; - do { - c = *filename; - filename++; - } while (c != '.' && c); - if (c == '.') { - filename[0] = new_extension[0]; - filename[1] = new_extension[1]; - filename[2] = new_extension[2]; - filename[3] = 0; + if (!new_extension || !*new_extension) { + return; + } + filename = strrchr(filename, '.'); + if (!filename) { + return; } + filename++; + snprintf(filename, strlen(filename) + 1, "%s", new_extension); } -void file_append_extension(char *filename, const char *extension) +void file_append_extension(char *filename, const char *extension, size_t length) { - char c; - do { - c = *filename; - filename++; - } while (c); - filename--; - *filename = '.'; - filename++; - size_t len = strlen(extension); - for (size_t i = 0; i < len; i++) { - *filename = extension[i]; - filename++; + if (!extension || !*extension) { + return; } - *filename = 0; + size_t actual_length = strlen(filename); + if (actual_length + strlen(extension) + 1 > length) { + return; + } + snprintf(filename + actual_length, length - actual_length, ".%s", extension); } void file_remove_extension(char *filename) { - uint8_t c; - do { - c = *filename; - filename++; - } while (c != '.' && c); - if (c == '.') { - filename--; + filename = strrchr(filename, '.'); + if (filename) { *filename = 0; } } +const char *file_remove_directory(const char *filename) +{ + char *filename_without_directory = strrchr(filename, '/'); + if (filename_without_directory) { + return filename_without_directory + 1; + } + filename_without_directory = strrchr(filename, '\\'); + if (filename_without_directory) { + return filename_without_directory + 1; + } + return filename; +} + int file_exists(const char *filename, int localizable) { return NULL != dir_get_file(filename, localizable); diff --git a/src/core/file.h b/src/core/file.h index c37d048343..b992f48933 100644 --- a/src/core/file.h +++ b/src/core/file.h @@ -61,8 +61,9 @@ void file_change_extension(char *filename, const char *new_extension); * Appends the extension to the file * @param[in,out] filename Filename to change * @param extension Extension to append + * @param length Length of the filename buffer */ -void file_append_extension(char *filename, const char *extension); +void file_append_extension(char *filename, const char *extension, size_t length); /** * Removes the extension from the file @@ -70,6 +71,13 @@ void file_append_extension(char *filename, const char *extension); */ void file_remove_extension(char *filename); +/** + * Removes the directory from the filename + * @param filename Filename to change + * @return Filename without directory + */ +const char *file_remove_directory(const char *filename); + /** * Check if file exists * @param filename Filename to check diff --git a/src/empire/empire.c b/src/empire/empire.c index f71ae39746..ba86c2375c 100644 --- a/src/empire/empire.c +++ b/src/empire/empire.c @@ -59,19 +59,26 @@ static void set_image_id(const char *path) return; } char *paths[] = { - CAMPAIGNS_DIRECTORY "/image/", - "community/image/", + CAMPAIGNS_DIRECTORY "/image", + "image", 0 }; for (int i = 0; paths[i]; i++) { char full_path[FILE_NAME_MAX]; - snprintf(full_path, FILE_NAME_MAX, "%s%s", paths[i], path); - if (campaign_has_file(full_path) || file_exists(full_path, NOT_LOCALIZED)) { - data.image.id = assets_get_external_image(full_path, 1); + const char *found_path = 0; + snprintf(full_path, FILE_NAME_MAX, "%s/%s", paths[i], path); + if (campaign_has_file(full_path)) { + found_path = full_path; + } else { + found_path = dir_get_file_at_location(full_path, PATH_LOCATION_COMMUNITY); + } + if (found_path) { + data.image.id = assets_get_external_image(found_path, 1); snprintf(data.image.path, FILE_NAME_MAX, "%s", path); return; } } + log_error("Unable to find map image file", path, 0); data.image.id = image_group(editor_is_active() ? GROUP_EDITOR_EMPIRE_MAP : GROUP_EMPIRE_MAP); data.image.path[0] = 0; } diff --git a/src/game/file.c b/src/game/file.c index 3992598dc2..3a6ad8a189 100644 --- a/src/game/file.c +++ b/src/game/file.c @@ -223,10 +223,6 @@ static int load_scenario_data(const char *scenario_file) static int load_custom_scenario(const uint8_t *scenario_name, const char *scenario_file) { - if (!file_exists(scenario_file, NOT_LOCALIZED)) { - return 0; - } - clear_scenario_data(); if (!load_scenario_data(scenario_file)) { return 0; @@ -339,11 +335,15 @@ static int start_scenario(const uint8_t *scenario_name, const char *scenario_fil map_bookmarks_clear(); int is_save_game = 0; if (scenario_is_custom()) { - if (!load_custom_scenario(scenario_name, scenario_file)) { - uint8_t scenario_mapx_name[FILE_NAME_MAX]; - string_copy(scenario_name, scenario_mapx_name, FILE_NAME_MAX); - if (game_file_load_saved_game(scenario_file) == FILE_LOAD_SUCCESS) { + const char *full_scenario_file = dir_get_file_at_location(scenario_file, PATH_LOCATION_SCENARIO); + if (!full_scenario_file) { + return 0; + } + if (!load_custom_scenario(scenario_name, full_scenario_file)) { + if (game_file_load_saved_game(full_scenario_file) == FILE_LOAD_SUCCESS) { is_save_game = 1; + uint8_t scenario_mapx_name[FILE_NAME_MAX]; + string_copy(scenario_name, scenario_mapx_name, FILE_NAME_MAX); scenario_set_name(scenario_mapx_name); } else { return 0; @@ -383,7 +383,7 @@ static const char *get_scenario_filename(const uint8_t *scenario_name, const cha static char filename[FILE_NAME_MAX]; encoding_to_utf8(scenario_name, filename, FILE_NAME_MAX, decomposed); if (!file_has_extension(filename, extension)) { - file_append_extension(filename, extension); + file_append_extension(filename, extension, FILE_NAME_MAX); } return filename; } @@ -518,7 +518,7 @@ void game_file_write_mission_saved_game(void) encoding_to_utf8(encoded_filename, localized_filename, FILE_NAME_MAX, encoding_system_uses_decomposed()); filename = localized_filename; } - if (!file_exists(filename, NOT_LOCALIZED)) { - game_file_io_write_saved_game(filename); + if (!dir_get_file_at_location(filename, PATH_LOCATION_SAVEGAME)) { + game_file_io_write_saved_game(dir_append_location(filename, PATH_LOCATION_SAVEGAME)); } } diff --git a/src/game/file_io.c b/src/game/file_io.c index 3640103364..a6de537f34 100644 --- a/src/game/file_io.c +++ b/src/game/file_io.c @@ -1079,7 +1079,7 @@ static int load_scenario_from_buffer(buffer *buf) static int load_scenario_to_buffers(const char *filename, scenario_version_t *version) { - FILE *fp = file_open(dir_get_file(filename, NOT_LOCALIZED), "rb"); + FILE *fp = file_open(filename, "rb"); if (!fp) { return 0; } @@ -1414,7 +1414,7 @@ int game_file_io_read_save_game_from_buffer(buffer *buf) int game_file_io_read_saved_game(const char *filename, int offset) { log_info("Loading saved game", filename, 0); - FILE *fp = file_open(dir_get_file(filename, NOT_LOCALIZED), "rb"); + FILE *fp = file_open(filename, "rb"); if (!fp) { log_error("Unable to load game, unable to open file.", 0, 0); return FILE_LOAD_DOES_NOT_EXIST; @@ -1717,7 +1717,7 @@ int game_file_io_read_saved_game_info(const char *filename, saved_game_info *inf return SAVEGAME_STATUS_INVALID; } memset(info, 0, sizeof(saved_game_info)); - FILE *fp = file_open(dir_get_file(filename, NOT_LOCALIZED), "rb"); + FILE *fp = file_open(filename, "rb"); if (!fp) { return SAVEGAME_STATUS_INVALID; } diff --git a/src/game/game.c b/src/game/game.c index b2a1cb594c..ab7018c7c8 100644 --- a/src/game/game.c +++ b/src/game/game.c @@ -27,6 +27,9 @@ #include "graphics/text.h" #include "graphics/video.h" #include "graphics/window.h" +#include "platform/file_manager.h" +#include "platform/prefs.h" +#include "platform/user_path.h" #include "scenario/property.h" #include "scenario/scenario.h" #include "sound/city.h" @@ -111,8 +114,29 @@ int game_init(void) sound_system_init(); game_state_init(); resource_init(); + int actions = ACTION_NONE; + if (missing_fonts) { + actions |= ACTION_SHOW_MESSAGE_MISSING_FONTS; + } + if (is_unpatched()) { + actions |= ACTION_SHOW_MESSAGE_MISSING_PATCH; + } int missing_assets = !assets_get_image_id("Admin_Logistics", "roadblock"); // If can't find roadblocks asset, extra assets not installed properly - window_logo_show(missing_fonts ? MESSAGE_MISSING_FONTS : (is_unpatched() ? MESSAGE_MISSING_PATCH : (missing_assets ? MESSAGE_MISSING_EXTRA_ASSETS : MESSAGE_NONE))); + if (missing_assets) { + actions |= ACTION_SHOW_MESSAGE_MISSING_EXTRA_ASSETS; + } + if (config_must_configure_user_directory()) { + actions |= ACTION_SETUP_USER_DIR; + } else if (!platform_file_manager_is_directory_writeable(pref_user_dir())) { + actions |= ACTION_SHOW_MESSAGE_USER_DIR_NOT_WRITABLE; + } else { + platform_user_path_create_subdirectories(); + } + if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { + actions |= ACTION_SHOW_INTRO_VIDEOS; + } + + window_logo_show(actions); return 1; } diff --git a/src/game/settings.c b/src/game/settings.c index 1fd7b80950..e913fe1346 100644 --- a/src/game/settings.c +++ b/src/game/settings.c @@ -3,7 +3,8 @@ #include "city/constants.h" #include "core/buffer.h" #include "core/calc.h" -#include "core/io.h" +#include "core/dir.h" +#include "core/file.h" #include "core/string.h" #define INF_SIZE 560 @@ -118,8 +119,23 @@ void settings_load(void) { load_default_settings(); - int size = io_read_file_into_buffer("c3.inf", NOT_LOCALIZED, data.inf_file, INF_SIZE); - if (!size) { + const char *settings_file = dir_get_file_at_location("c3.inf", PATH_LOCATION_CONFIG); + if (!settings_file) { + return; + } + FILE *fp = file_open(settings_file, "rb"); + if (!fp) { + return; + } + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + if (size > INF_SIZE) { + size = INF_SIZE; + } + fseek(fp, 0, SEEK_SET); + size_t bytes_read = fread(data.inf_file, 1, INF_SIZE, fp); + file_close(fp); + if (!bytes_read) { return; } @@ -180,7 +196,14 @@ void settings_save(void) buffer_write_i32(buf, data.difficulty); buffer_write_i32(buf, data.gods_enabled); - io_write_buffer_to_file("c3.inf", data.inf_file, INF_SIZE); + // Find existing file to overwrite + const char *settings_file = dir_append_location("c3.inf", PATH_LOCATION_CONFIG); + FILE *fp = file_open(settings_file, "wb"); + if (!fp) { + return; + } + fwrite(data.inf_file, 1, INF_SIZE, fp); + file_close(fp); } int setting_fullscreen(void) diff --git a/src/game/system.h b/src/game/system.h index 51848e9626..75cf23361b 100644 --- a/src/game/system.h +++ b/src/game/system.h @@ -192,6 +192,14 @@ void system_set_mouse_position(int *x, int *y); */ void system_setup_crash_handler(void); +/** + * Shows a "select folder" dialog + * @param title Title of the dialog + * @param default_path Default path to show + * @return The selected folder, or 0 if the dialog was cancelled + */ +const char *system_show_select_folder_dialog(const char *title, const char *default_path); + /** * Exit the game */ diff --git a/src/game/tick.c b/src/game/tick.c index 0772df7265..d5b8a4f895 100644 --- a/src/game/tick.c +++ b/src/game/tick.c @@ -32,6 +32,7 @@ #include "city/trade.h" #include "city/victory.h" #include "core/config.h" +#include "core/dir.h" #include "core/random.h" #include "editor/editor.h" #include "empire/city.h" @@ -117,10 +118,10 @@ static void advance_month(void) scenario_events_progress_paused(1); scenario_events_process_all(); if (setting_monthly_autosave()) { - game_file_write_saved_game("autosave.svx"); + game_file_write_saved_game(dir_append_location("autosave.svx", PATH_LOCATION_SAVEGAME)); } if (new_year && config_get(CONFIG_GP_CH_YEARLY_AUTOSAVE)) { - game_file_write_saved_game("autosave-year.svx"); + game_file_write_saved_game(dir_append_location("autosave-year.svx", PATH_LOCATION_SAVEGAME)); } } diff --git a/src/graphics/graphics.c b/src/graphics/graphics.c index ce7ef33ecf..9ddea0c3fd 100644 --- a/src/graphics/graphics.c +++ b/src/graphics/graphics.c @@ -14,12 +14,13 @@ static void set_translation(int x, int y) void graphics_in_dialog(void) { - set_translation(screen_dialog_offset_x(), screen_dialog_offset_y()); + graphics_in_dialog_with_size(640, 480); } void graphics_in_dialog_with_size(int width, int height) { set_translation((screen_width() - width) / 2, (screen_height() - height) / 2); + screen_set_dialog_offset(width, height); } void graphics_reset_dialog(void) diff --git a/src/graphics/rich_text.c b/src/graphics/rich_text.c index 9e4f5f2379..b31c5df5bb 100644 --- a/src/graphics/rich_text.c +++ b/src/graphics/rich_text.c @@ -253,19 +253,20 @@ static int get_raw_text_width(const uint8_t *str) static int get_external_image_id(const char *filename) { char full_path[FILE_NAME_MAX]; - char *paths[] = { CAMPAIGNS_DIRECTORY "/image", "community/image" }; - int found_path = 0; - for (int i = 0; i < 2; i++) { + char *paths[] = { CAMPAIGNS_DIRECTORY "/image", "image" }; + const char *found_path = 0; + for (int i = 0; i < 2 && !found_path; i++) { snprintf(full_path, FILE_NAME_MAX, "%s/%s", paths[i], filename); - if (campaign_has_file(full_path) || file_exists(full_path, NOT_LOCALIZED)) { - found_path = 1; - break; + if (campaign_has_file(full_path)) { + found_path = full_path; + } else { + found_path = dir_get_file_at_location(full_path, PATH_LOCATION_COMMUNITY); } } if (!found_path) { return 0; } - return assets_get_external_image(full_path, 0); + return assets_get_external_image(found_path, 0); } int rich_text_parse_image_id(const uint8_t **position, int default_image_group, int can_be_filepath) diff --git a/src/graphics/screen.c b/src/graphics/screen.c index 683a8ac42d..3d2b0d7a1a 100644 --- a/src/graphics/screen.c +++ b/src/graphics/screen.c @@ -14,12 +14,17 @@ static struct { } dialog_offset; } data; +void screen_set_dialog_offset(int width, int height) +{ + data.dialog_offset.x = (data.width - width) / 2; + data.dialog_offset.y = (data.height - height) / 2; +} + void screen_set_resolution(int width, int height) { data.width = width; data.height = height; - data.dialog_offset.x = (width - 640) / 2; - data.dialog_offset.y = (height - 480) / 2; + screen_set_dialog_offset(640, 480); graphics_renderer()->clear_screen(); graphics_renderer()->set_clip_rectangle(0, 0, width, height); diff --git a/src/graphics/screen.h b/src/graphics/screen.h index 764a3f4227..d65f2fba03 100644 --- a/src/graphics/screen.h +++ b/src/graphics/screen.h @@ -3,6 +3,8 @@ #include "graphics/color.h" +void screen_set_dialog_offset(int width, int height); + void screen_set_resolution(int width, int height); color_t *screen_pixel(int x, int y); diff --git a/src/graphics/screenshot.c b/src/graphics/screenshot.c index c4152a7bfc..5fbaa5f7ad 100644 --- a/src/graphics/screenshot.c +++ b/src/graphics/screenshot.c @@ -93,7 +93,7 @@ static int image_create(int width, int height, int has_alpha_channel, int rows_i static const char *generate_filename(screenshot_type type) { - static char filename[FILE_NAME_MAX]; + char filename[FILE_NAME_MAX]; time_t curtime = time(NULL); struct tm *loctime = localtime(&curtime); switch (type) { @@ -108,7 +108,7 @@ static const char *generate_filename(screenshot_type type) strftime(filename, FILE_NAME_MAX, "city %Y-%m-%d %H.%M.%S.png", loctime); break; } - return filename; + return dir_append_location(filename, PATH_LOCATION_SCREENSHOT); } static int image_begin_io(const char *filename) diff --git a/src/graphics/window.h b/src/graphics/window.h index 71d4707b57..94ab49cdde 100644 --- a/src/graphics/window.h +++ b/src/graphics/window.h @@ -92,7 +92,8 @@ typedef enum { // New window types WINDOW_ASSET_PREVIEWER, WINDOW_CUSTOM_MESSAGE, - WINDOW_TEXT_INPUT + WINDOW_TEXT_INPUT, + WINDOW_USER_PATH_SETUP } window_id; typedef struct { diff --git a/src/platform/android/android.h b/src/platform/android/android.h index 38acfe7a1a..2285604d51 100644 --- a/src/platform/android/android.h +++ b/src/platform/android/android.h @@ -3,6 +3,8 @@ #ifdef __ANDROID__ +#define PLATFORM_NO_USER_DIRECTORIES + const char *android_show_c3_path_dialog(int again); float android_get_screen_density(void); int android_get_file_descriptor(const char *filename, const char *mode); diff --git a/src/platform/augustus.c b/src/platform/augustus.c index bc4049815c..21bce42b3d 100644 --- a/src/platform/augustus.c +++ b/src/platform/augustus.c @@ -121,6 +121,15 @@ static void post_event(int code) SDL_PushEvent(&event); } +const char *system_show_select_folder_dialog(const char *title, const char *default_path) +{ +#ifdef USE_TINYFILEDIALOGS + return tinyfd_selectFolderDialog(title, default_path); +#else + return 0; +#endif +} + void system_exit(void) { post_event(USER_EVENT_QUIT); @@ -456,11 +465,10 @@ static int init_sdl(int enable_joysticks) #ifdef SHOW_FOLDER_SELECT_DIALOG static const char *ask_for_data_dir(int again) { -#ifdef __ANDROID__ if (again) { const SDL_MessageBoxButtonData buttons[] = { - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "OK"}, - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "Cancel"} + {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "OK"}, + {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "Cancel"} }; const SDL_MessageBoxData messageboxdata = { SDL_MESSAGEBOX_WARNING, NULL, "Wrong folder selected", @@ -476,19 +484,10 @@ static const char *ask_for_data_dir(int again) return NULL; } } +#ifdef __ANDROID__ return android_show_c3_path_dialog(again); #else - if (again) { - int result = tinyfd_messageBox("Wrong folder selected", - "Augustus requires the original files from Caesar 3 to run.\n\n" - "The selected folder is not a proper Caesar 3 folder.\n\n" - "Press OK to select another folder or Cancel to exit.", - "okcancel", "warning", 1); - if (!result) { - return NULL; - } - } - return tinyfd_selectFolderDialog("Please select your Caesar 3 folder"); + return system_show_select_folder_dialog("Please select your Caesar 3 folder", 0); #endif } #endif @@ -532,7 +531,7 @@ static int pre_init(const char *custom_data_dir) #ifdef SHOW_FOLDER_SELECT_DIALOG const char *user_dir = pref_data_dir(); - if (user_dir) { + if (*user_dir) { SDL_Log("Loading game from user pref %s", user_dir); if (platform_file_manager_set_base_path(user_dir) && game_pre_init()) { return 1; diff --git a/src/platform/emscripten/emscripten.h b/src/platform/emscripten/emscripten.h index e80807dc08..62b91a4db7 100644 --- a/src/platform/emscripten/emscripten.h +++ b/src/platform/emscripten/emscripten.h @@ -5,5 +5,7 @@ #include +#define PLATFORM_NO_USER_DIRECTORIES + #endif // __EMSCRIPTEN__ #endif // PLATFORM_EMSCRIPTEN_H diff --git a/src/platform/file_manager.c b/src/platform/file_manager.c index 3ca469c3ec..ef44933567 100644 --- a/src/platform/file_manager.c +++ b/src/platform/file_manager.c @@ -1,13 +1,16 @@ #include "file_manager.h" #include "assets/assets.h" +#include "core/config.h" #include "core/file.h" #include "core/log.h" +#include "core/random.h" #include "core/string.h" #include "platform/android/android.h" #include "platform/emscripten/emscripten.h" #include "platform/file_manager_cache.h" #include "platform/platform.h" +#include "platform/prefs.h" #include "platform/vita/vita.h" #ifndef BUILDING_ASSET_PACKER @@ -30,6 +33,7 @@ #ifdef _WIN32 #include +#include #define fs_dir_type _WDIR #define fs_dir_entry struct _wdirent @@ -66,6 +70,17 @@ static wchar_t *utf8_to_wchar(const char *str) return result; } +static void copy_file_times(FILE *src, FILE *dst) +{ + stat_info file_info; + if (_fstat(_fileno(src), &file_info) == -1) { + return; + } + struct _utimbuf ut; + ut.actime = file_info.st_atime; + ut.modtime = file_info.st_mtime; + _futime(_fileno(dst), &ut); +} #else // not _WIN32 #include #include @@ -83,6 +98,26 @@ static wchar_t *utf8_to_wchar(const char *str) #define fs_remove remove typedef struct stat stat_info; typedef char file_name; + +static void copy_file_times(FILE *src, FILE *dst) +{ + stat_info file_info; + if (fstat(fileno(src), &file_info) == -1) { + return; + } +#ifndef __SWITCH__ + struct timespec times[2] = { 0 }; + times[0].tv_sec = file_info.st_atime; + times[1].tv_sec = file_info.st_mtime; + futimens(fileno(dst), times); +#else + struct timeval times[2] = { 0 }; + times[0].tv_sec = file_info.st_atime; + times[1].tv_sec = file_info.st_mtime; + futimes(fileno(dst), times); +#endif +} + #endif #ifndef S_ISLNK @@ -121,7 +156,7 @@ static int is_file(int mode) #endif #ifdef __ANDROID__ -static const char *assets_directory = ASSETS_DIRECTORY; +static const char *const assets_directory = ASSETS_DIRECTORY; #else #define MAX_ASSET_DIRS 10 @@ -179,8 +214,35 @@ static int write_base_path_to(char *dest) static struct { char current_src_path[FILE_NAME_MAX]; char current_dst_path[FILE_NAME_MAX]; + int overwrite_files; } directory_copy_data; +static struct { + const char *root; + const char *configs; + const char *assets; + const char *savegames; + const char *scenarios; + const char *campaigns; + const char *screenshots; + const char *community; + const char *editor_custom_empires; + const char *editor_custom_messages; + const char *editor_custom_events; +} paths = { + "", + "config/", + assets_directory, + "savegames/", + "scenarios/", + "campaigns/", + "screenshots/", + "community/", + "editor/empires/", + "editor/messages/", + "editor/events/" +}; + static void set_assets_directory(void) { #ifndef __ANDROID__ @@ -299,7 +361,8 @@ int platform_file_manager_list_directory_contents( } else { char full_asset_path[FILE_NAME_MAX]; // Prevent double slashes as they may not work - if (*assets_directory && assets_directory[strlen(assets_directory) - 1] == '/' && dir[assets_directory_length] == '/') { + if (*assets_directory && assets_directory[strlen(assets_directory) - 1] == '/' && + dir[assets_directory_length] == '/') { assets_directory_length++; } snprintf(full_asset_path, FILE_NAME_MAX, "%s%s", assets_directory, dir + assets_directory_length); @@ -451,6 +514,98 @@ int platform_file_manager_set_base_path(const char *path) #endif } +const char *platform_file_manager_get_directory_for_location(int location, const char *user_directory) +{ + static char full_path[FILE_NAME_MAX]; + if (!user_directory) { + user_directory = pref_user_dir(); + } + int cursor = 0; + char slash[2] = { 0 }; + size_t user_directory_length = strlen(user_directory); + if (user_directory_length && + user_directory[user_directory_length - 1] != '/' && user_directory[user_directory_length - 1] != '\\') { + slash[0] = '/'; + } + + switch (location) { + default: + full_path[0] = 0; + break; + case PATH_LOCATION_CONFIG: + if (*user_directory) { + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.configs); + } else { + full_path[0] = 0; + } + break; + case PATH_LOCATION_ASSET: + set_assets_directory(); + cursor = snprintf(full_path, FILE_NAME_MAX, "%s", paths.assets); + break; + case PATH_LOCATION_SAVEGAME: + if (*user_directory) { + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.savegames); + } else { + full_path[0] = 0; + } + break; + case PATH_LOCATION_SCENARIO: + if (*user_directory) { + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.scenarios); + } else { + full_path[0] = 0; + } + break; + case PATH_LOCATION_CAMPAIGN: + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.campaigns); + break; + case PATH_LOCATION_SCREENSHOT: + if (*user_directory) { + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.screenshots); + } else { + full_path[0] = 0; + } + break; + case PATH_LOCATION_COMMUNITY: + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.community); + break; + case PATH_LOCATION_EDITOR_CUSTOM_EMPIRES: + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.editor_custom_empires); + break; + case PATH_LOCATION_EDITOR_CUSTOM_EVENTS: + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.editor_custom_events); + break; + case PATH_LOCATION_EDITOR_CUSTOM_MESSAGES: + cursor = snprintf(full_path, FILE_NAME_MAX, "%s%s%s", user_directory, slash, paths.editor_custom_messages); + break; + } + + if (cursor >= FILE_NAME_MAX) { + log_error("Path ID too long for location: ", 0, location); + } + return full_path; +} + +int platform_file_manager_is_directory_writeable(const char *directory) +{ + char file_name[FILE_NAME_MAX]; + int attempt = 0; + do { + snprintf(file_name, FILE_NAME_MAX, "%s/test_%d.txt", directory, random_from_stdlib()); + attempt++; + } while (file_exists(file_name, NOT_LOCALIZED) && attempt < 5); + FILE *fp = file_open(file_name, "w"); + if (!fp) { + return 0; + } + fclose(fp); + if (!file_remove(file_name)) { + return 0; + } + return 1; +} + #if defined(__ANDROID__) FILE *platform_file_manager_open_file(const char *filename, const char *mode) @@ -517,9 +672,8 @@ int platform_file_manager_remove_file(const char *filename) FILE *platform_file_manager_open_asset(const char *asset, const char *mode) { - set_assets_directory(); - const char *cased_asset_path = dir_get_asset(assets_directory, asset); - return platform_file_manager_open_file(cased_asset_path, mode); + const char *cased_asset_path = dir_get_file_at_location(asset, PATH_LOCATION_ASSET); + return cased_asset_path ? platform_file_manager_open_file(cased_asset_path, mode) : 0; } #endif @@ -537,14 +691,30 @@ int platform_file_manager_close_file(FILE *stream) return result == 0; } -int platform_file_manager_create_directory(const char *name, int overwrite) +int platform_file_manager_create_directory(const char *name, const char *location, int overwrite) { char tokenized_name[FILE_NAME_MAX]; char temporary_path[FILE_NAME_MAX] = { 0 }; - snprintf(tokenized_name, FILE_NAME_MAX, "%s", name); - char *token = strtok(tokenized_name, "/\\"); int overwrite_last = 0; int cursor = 0; + if (location) { + cursor = snprintf(temporary_path, FILE_NAME_MAX, "%s", location); + if (cursor > FILE_NAME_MAX) { + log_error("Path too long", name, 0); + return 0; + } + size_t location_length = strlen(location); + if (strncmp(name, location, location_length) == 0) { + name += location_length; + } + } + if (*name == '/' || *name == '\\') { + cursor += snprintf(&temporary_path[cursor], FILE_NAME_MAX - cursor, "/"); + name++; + } + snprintf(tokenized_name, FILE_NAME_MAX, "%s", name); + char *token = strtok(tokenized_name, "/\\"); + while (token) { overwrite_last = 0; cursor += snprintf(&temporary_path[cursor], FILE_NAME_MAX - cursor, "%s", token); @@ -591,6 +761,7 @@ int platform_file_manager_create_directory(const char *name, int overwrite) return !overwrite_last; } + int platform_file_manager_copy_file(const char *src, const char *dst) { FILE *in = platform_file_manager_open_file(src, "rb"); @@ -606,13 +777,20 @@ int platform_file_manager_copy_file(const char *src, const char *dst) char buf[1024]; size_t read = 0; + while ((read = fread(buf, 1, 1024, in)) == 1024) { fwrite(buf, 1, 1024, out); } fwrite(buf, 1, read, out); - file_close(out); + out = platform_file_manager_open_file(dst, "rb"); + if (!out) { + fclose(in); + return 1; + } + copy_file_times(in, out); file_close(in); + file_close(out); return 1; } @@ -642,6 +820,13 @@ static void move_up_path(void) static int copy_file(const char *name, long unused) { append_name_to_path(name); + if (!directory_copy_data.overwrite_files) { + FILE *file = platform_file_manager_open_file(directory_copy_data.current_dst_path, "rb"); + if (file) { + file_close(file); + return LIST_CONTINUE; + } + } int result = platform_file_manager_copy_file(directory_copy_data.current_src_path, directory_copy_data.current_dst_path) != 0; @@ -658,7 +843,7 @@ static int copy_directory(const char *name, long unused) if (name) { append_name_to_path(name); } - if (!platform_file_manager_create_directory(directory_copy_data.current_dst_path, 0)) { + if (!platform_file_manager_create_directory(directory_copy_data.current_dst_path, 0, 0)) { if (name) { move_up_path(); return LIST_CONTINUE; @@ -702,10 +887,19 @@ static void copy_directory_name(const char *name, char *dst) } } -int platform_file_manager_copy_directory(const char *src, const char *dst) +static int do_nothing(const char *name, long unused) { + return LIST_MATCH; +} + +int platform_file_manager_copy_directory(const char *src, const char *dst, int overwrite_files) +{ + if (!platform_file_manager_list_directory_contents(src, TYPE_DIR, 0, do_nothing)) { + return 0; + } copy_directory_name(src, directory_copy_data.current_src_path); copy_directory_name(dst, directory_copy_data.current_dst_path); + directory_copy_data.overwrite_files = overwrite_files; return copy_directory(0, 0); } diff --git a/src/platform/file_manager.h b/src/platform/file_manager.h index a53df1ed62..c7cc25e63f 100644 --- a/src/platform/file_manager.h +++ b/src/platform/file_manager.h @@ -24,6 +24,23 @@ enum { */ int platform_file_manager_set_base_path(const char *path); +/** + * Gets a directory location for the specified type + * @param location The location to get + * @param user_directory The user directory to use, or 0 to use the one from the preferences + * @return The path for the provided location, or an empty string if the location is the base path. + * If there is an actual path, it is guaranteeed to end with "/". + * The return value is a pointer to a static buffer, so it should be copied if needed. + */ +const char *platform_file_manager_get_directory_for_location(int location, const char *user_directory); + +/** + * Checks if a directory is writeable + * @param directory The directory to check + * @return 1 if the directory is writeable, 0 otherwise + */ +int platform_file_manager_is_directory_writeable(const char *directory); + /** * Gets the contents of a directory by the specified extension * @param dir The directory to search on, or null if base directory @@ -101,10 +118,12 @@ int platform_file_manager_remove_file(const char *filename); /** * Creates a directory * @param path The full path to the new directory + * @param location The base location to create the directory. The game won't attempt to create the directories in location. + * If location and name are similar, the game will create the directory in the location. Can be 0. * @param overwrite Whether to return error if overwriting the directory * @return 1 if creation was successful, 0 otherwise */ -int platform_file_manager_create_directory(const char *name, int overwrite); +int platform_file_manager_create_directory(const char *name, const char *location, int overwrite); /** * Copies a file @@ -118,9 +137,10 @@ int platform_file_manager_copy_file(const char *src, const char *dst); * Copies a directory recursively * @param src The source directory * @param dst The destination directory + * @param overwrite_files Whether to overwrite existing files * @return 1 if copying was successful, 0 otherwise */ -int platform_file_manager_copy_directory(const char *src, const char *dst); +int platform_file_manager_copy_directory(const char *src, const char *dst, int overwrite_files); /** * Removes a directory recursively - use with care!!! diff --git a/src/platform/platform.c b/src/platform/platform.c index 6f812f9664..c8c51b231c 100644 --- a/src/platform/platform.c +++ b/src/platform/platform.c @@ -136,6 +136,16 @@ int platform_sdl_version_at_least(int major, int minor, int patch) return SDL_VERSIONNUM(version.major, version.minor, version.patch) >= SDL_VERSIONNUM(major, minor, patch); } +char *platform_get_pref_path(void) +{ +#if SDL_VERSION_ATLEAST(2, 0, 1) + if (platform_sdl_version_at_least(2, 0, 1)) { + return SDL_GetPrefPath("augustus", "augustus"); + } +#endif + return 0; +} + void exit_with_status(int status) { #ifdef __EMSCRIPTEN__ diff --git a/src/platform/platform.h b/src/platform/platform.h index b44441cac7..575894bfb4 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -2,6 +2,7 @@ #define PLATFORM_PLATFORM_H int platform_sdl_version_at_least(int major, int minor, int patch); +char *platform_get_pref_path(void); void exit_with_status(int status); #endif // PLATFORM_PLATFORM_H diff --git a/src/platform/prefs.c b/src/platform/prefs.c index 3c9c7ffd63..06496e5c0f 100644 --- a/src/platform/prefs.c +++ b/src/platform/prefs.c @@ -1,6 +1,7 @@ #include "platform/prefs.h" #include "core/log.h" +#include "core/file.h" #include "platform/platform.h" #include "SDL.h" @@ -9,52 +10,82 @@ #include #include +static char stored_user_dir[FILE_NAME_MAX]; +static char stored_data_dir[FILE_NAME_MAX]; + static FILE *open_pref_file(const char *filename, const char *mode) { - #if SDL_VERSION_ATLEAST(2, 0, 1) - if (platform_sdl_version_at_least(2, 0, 1)) { - char *pref_dir = SDL_GetPrefPath("augustus", "augustus"); - log_info("Location:", pref_dir, 0); - if (!pref_dir) { - return NULL; - } - size_t file_len = strlen(filename) + strlen(pref_dir) + 1; - char *pref_file = malloc(file_len * sizeof(char)); - if (!pref_file) { - SDL_free(pref_dir); - return NULL; - } - snprintf(pref_file, file_len, "%s%s", pref_dir, filename); + char *pref_dir = platform_get_pref_path(); + if (!pref_dir) { + return NULL; + } + log_info("Location:", pref_dir, 0); + size_t file_len = strlen(filename) + strlen(pref_dir) + 1; + char *pref_file = malloc(file_len * sizeof(char)); + if (!pref_file) { SDL_free(pref_dir); - - FILE *fp = fopen(pref_file, mode); - free(pref_file); - return fp; + return NULL; } - #endif - return NULL; + snprintf(pref_file, file_len, "%s%s", pref_dir, filename); + SDL_free(pref_dir); + + FILE *fp = fopen(pref_file, mode); + free(pref_file); + return fp; } const char *pref_data_dir(void) { - static char data_dir[1000]; + if (*stored_data_dir) { + return stored_data_dir; + } + char data_dir[FILE_NAME_MAX] = { 0 }; FILE *fp = open_pref_file("data_dir.txt", "r"); if (fp) { - size_t length = fread(data_dir, 1, 1000, fp); + size_t length = fread(data_dir, 1, FILE_NAME_MAX - 1, fp); fclose(fp); if (length > 0) { data_dir[length] = 0; - return data_dir; } } - return NULL; + snprintf(stored_data_dir, FILE_NAME_MAX, "%s", data_dir); + return stored_data_dir; } void pref_save_data_dir(const char *data_dir) { + snprintf(stored_data_dir, FILE_NAME_MAX, "%s", data_dir); FILE *fp = open_pref_file("data_dir.txt", "w"); if (fp) { fwrite(data_dir, 1, strlen(data_dir), fp); fclose(fp); } } + +const char *pref_user_dir(void) +{ + if (*stored_user_dir) { + return stored_user_dir; + } + char user_dir[FILE_NAME_MAX] = { 0 }; + FILE *fp = open_pref_file("user_dir.txt", "r"); + if (fp) { + size_t length = fread(user_dir, 1, FILE_NAME_MAX - 1, fp); + fclose(fp); + if (length > 0) { + user_dir[length] = 0; + } + } + snprintf(stored_user_dir, FILE_NAME_MAX, "%s", user_dir); + return stored_user_dir; +} + +void pref_save_user_dir(const char *user_dir) +{ + snprintf(stored_user_dir, FILE_NAME_MAX, "%s", user_dir); + FILE *fp = open_pref_file("user_dir.txt", "w"); + if (fp) { + fwrite(user_dir, 1, strlen(user_dir), fp); + fclose(fp); + } +} diff --git a/src/platform/prefs.h b/src/platform/prefs.h index cb13ee70d7..adf2f99e07 100644 --- a/src/platform/prefs.h +++ b/src/platform/prefs.h @@ -5,4 +5,8 @@ const char *pref_data_dir(void); void pref_save_data_dir(const char *data_dir); +const char *pref_user_dir(void); + +void pref_save_user_dir(const char *user_dir); + #endif // PLATFORM_PREFS_H diff --git a/src/platform/switch/switch.h b/src/platform/switch/switch.h index e4b6a4e2eb..74848b382f 100644 --- a/src/platform/switch/switch.h +++ b/src/platform/switch/switch.h @@ -22,5 +22,7 @@ void platform_hide_virtual_keyboard(void); #define PLATFORM_USE_SOFTWARE_CURSOR +#define PLATFORM_NO_USER_DIRECTORIES + #endif // __SWITCH__ #endif // PLATFORM_SWITCH_H diff --git a/src/platform/user_path.c b/src/platform/user_path.c new file mode 100644 index 0000000000..27c650f4f7 --- /dev/null +++ b/src/platform/user_path.c @@ -0,0 +1,128 @@ +#include "user_path.h" + +#include "core/file.h" +#include "platform/file_manager.h" +#include "platform/platform.h" +#include "platform/prefs.h" + +#include + +const char *platform_user_path_recommend(void) +{ +#if !defined(_WIN32) + return 0; +#else + static const char *user_path; + if (!user_path) { + user_path = platform_get_pref_path(); + } + return user_path; +#endif +} + +void platform_user_path_create_subdirectories(void) +{ + for (int i = 0; i < PATH_LOCATION_MAX; i++) { + if (i == PATH_LOCATION_ROOT || i == PATH_LOCATION_ASSET) { + continue; + } + const char *new_directory = platform_file_manager_get_directory_for_location(i, 0); + if (*new_directory) { + platform_file_manager_create_directory(new_directory, pref_user_dir(), 0); + } + } +} + +void platform_user_path_copy_files(const char *original_user_path, int overwrite) +{ + for (int location = 0; location < PATH_LOCATION_MAX; location++) { + if (location == PATH_LOCATION_ROOT || location == PATH_LOCATION_ASSET) { + continue; + } + char original_directory[FILE_NAME_MAX]; + char new_directory[FILE_NAME_MAX]; + snprintf(original_directory, FILE_NAME_MAX, "%s", + platform_file_manager_get_directory_for_location(location, original_user_path)); + snprintf(new_directory, FILE_NAME_MAX, "%s", + platform_file_manager_get_directory_for_location(location, 0)); + if (strcmp(new_directory, original_directory) == 0) { + continue; + } + const dir_listing *listing = 0; + int has_subdirectories = 0; + switch (location) { + case PATH_LOCATION_CONFIG: + listing = dir_find_files_with_extension(original_directory, "inf"); + listing = dir_append_files_with_extension("ini"); + break; + case PATH_LOCATION_SAVEGAME: + listing = dir_find_files_with_extension(original_directory, "sav"); + listing = dir_append_files_with_extension("svx"); + break; + case PATH_LOCATION_SCENARIO: + listing = dir_find_files_with_extension(original_directory, "map"); + listing = dir_append_files_with_extension("mapx"); + break; + case PATH_LOCATION_CAMPAIGN: + listing = dir_find_files_with_extension(original_directory, "campaign"); + has_subdirectories = 1; + break; + case PATH_LOCATION_SCREENSHOT: + listing = dir_find_files_with_extension(original_directory, "png"); + listing = dir_append_files_with_extension("bmp"); + break; + case PATH_LOCATION_COMMUNITY: + has_subdirectories = 1; + break; + case PATH_LOCATION_EDITOR_CUSTOM_EMPIRES: + case PATH_LOCATION_EDITOR_CUSTOM_MESSAGES: + case PATH_LOCATION_EDITOR_CUSTOM_EVENTS: + listing = dir_find_files_with_extension(original_directory, "xml"); + break; + default: + continue; + } + if (listing) { + char original_name[FILE_NAME_MAX]; + char new_name[FILE_NAME_MAX]; + for (int i = 0; i < listing->num_files; i++) { + snprintf(original_name, FILE_NAME_MAX, "%s%s", original_directory, listing->files[i].name); + snprintf(new_name, FILE_NAME_MAX, "%s%s", new_directory, listing->files[i].name); + if (overwrite || !dir_get_file_at_location(listing->files[i].name, location)) { + platform_file_manager_copy_file(original_name, new_name); + } + } + } + if (has_subdirectories) { + listing = dir_find_all_subdirectories(original_directory); + char original_name[FILE_NAME_MAX]; + char new_name[FILE_NAME_MAX]; + for (int i = 0; i < listing->num_files; i++) { + snprintf(original_name, FILE_NAME_MAX, "%s%s", original_directory, listing->files[i].name); + snprintf(new_name, FILE_NAME_MAX, "%s%s", new_directory, listing->files[i].name); + platform_file_manager_copy_directory(original_name, new_name, overwrite); + } + } + } +} + +void platform_user_path_copy_campaigns_and_custom_empires(void) +{ + if (*pref_user_dir()) { + return; + } + const dir_listing *listing = dir_find_files_with_extension(0, "campaign"); + if (listing->num_files > 0) { + char new_name[FILE_NAME_MAX]; + const char *campaign_directory = platform_file_manager_get_directory_for_location(PATH_LOCATION_CAMPAIGN, 0); + platform_file_manager_create_directory(campaign_directory, pref_user_dir(), 0); + for (int i = 0; i < listing->num_files; i++) { + snprintf(new_name, FILE_NAME_MAX, "%s%s", campaign_directory, listing->files[i].name); + if (!dir_get_file_at_location(listing->files[i].name, PATH_LOCATION_CAMPAIGN)) { + platform_file_manager_copy_file(listing->files[i].name, new_name); + } + } + } + platform_file_manager_copy_directory("custom_empires", + platform_file_manager_get_directory_for_location(PATH_LOCATION_EDITOR_CUSTOM_EMPIRES, 0), 0); +} diff --git a/src/platform/user_path.h b/src/platform/user_path.h new file mode 100644 index 0000000000..1ae8a00ead --- /dev/null +++ b/src/platform/user_path.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_USER_PATH_H +#define PLATFORM_USER_PATH_H + +/** + * Recommends a user path + * Only works on Windows right now + * + * @return The recommended user path, or 0 if unsupported + */ +const char *platform_user_path_recommend(void); + +/** + * Creates subdirectories for a user path + */ +void platform_user_path_create_subdirectories(void); + +/** + * Copies files from the default user path to the new user path + * + * @param original_user_path The user path to copy files from + * @param overwrite Whether to overwrite existing files + */ +void platform_user_path_copy_files(const char *original_user_path, int overwrite); + +/** + * Copies campaigns and custom empires from the default place to the new subdirectories + */ +void platform_user_path_copy_campaigns_and_custom_empires(void); + +#endif // PLATFORM_USER_PATH_H + diff --git a/src/platform/vita/vita.h b/src/platform/vita/vita.h index 5f133445be..20c01bfcdb 100644 --- a/src/platform/vita/vita.h +++ b/src/platform/vita/vita.h @@ -21,5 +21,7 @@ void platform_hide_virtual_keyboard(void); #define PLATFORM_USE_SOFTWARE_CURSOR +#define PLATFORM_NO_USER_DIRECTORIES + #endif // __vita__ #endif // PLATFORM_VITA_H diff --git a/src/scenario/custom_messages.c b/src/scenario/custom_messages.c index 4cc181d472..85451919ca 100644 --- a/src/scenario/custom_messages.c +++ b/src/scenario/custom_messages.c @@ -10,18 +10,18 @@ #define CUSTOM_MESSAGES_ARRAY_SIZE_STEP 100 static const char *AUDIO_FILE_PATHS[] = { - CAMPAIGNS_DIRECTORY "/audio/", - "community/audio/", - "mp3/", - "wavs/", + CAMPAIGNS_DIRECTORY "/audio", + "community/audio", + "mp3", + "wavs", 0 }; static const char *VIDEO_FILE_PATHS[] = { - CAMPAIGNS_DIRECTORY "/video/", - "community/video/", - "smk/", - "mpg/", + CAMPAIGNS_DIRECTORY "/video", + "community/video", + "smk", + "mpg", 0 }; @@ -233,8 +233,13 @@ uint8_t *custom_messages_get_text(custom_message_t *message) static const char *check_for_file_in_dir(const char *filename, const char *directory) { static char filepath[FILE_NAME_MAX]; - snprintf(filepath, FILE_NAME_MAX, "%s%s", directory, filename); - return campaign_has_file(filepath) || file_exists(filepath, MAY_BE_LOCALIZED) ? filepath : 0; + int location = PATH_LOCATION_ROOT; + if (strncmp(directory, "community/", 10) == 0) { + directory += 10; + location = PATH_LOCATION_COMMUNITY; + } + snprintf(filepath, FILE_NAME_MAX, "%s/%s", directory, filename); + return campaign_has_file(filepath) || dir_get_file_at_location(filepath, location) ? filepath : 0; } static const char *search_for_file(const uint8_t *filename, const char *paths[]) diff --git a/src/translation/english.c b/src/translation/english.c index 661bb9e7f1..61ec68bb93 100644 --- a/src/translation/english.c +++ b/src/translation/english.c @@ -864,6 +864,8 @@ static translation_string all_strings[] = { {TR_BUILDING_ACADEMY_UPGRADE_DESC, "This academy is operational. Local development allowed your citizens to expand the building, providing more halls for orations and research to take place."}, {TR_BUILDING_PALISADE_GATE, "Palisade gate"}, {TR_BUILDING_PALISADE_GATE_DESC, "This narrow wooden gateway controls the movement of people in and out of your city's defenses."}, + {TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TITLE, "File does not exist"}, + {TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TEXT, "The selected file does not exist.\nPlease select a different file."}, {TR_SAVE_DIALOG_INVALID_FILE, "Invalid file"}, {TR_SAVE_DIALOG_INVALID_FILE_DESC, "The savegame you're trying to load is invalid.\n\nThe save may have been corrupted due to a bug.\n\nIf you're sure this should be a valid save, please report the bug to:\n\nhttps://github.com/Keriew/augustus/issues/new\n\nPlease attach the save as well."}, {TR_SAVE_DIALOG_INCOMPATIBLE_VERSION, "Incompatible version"}, @@ -1469,6 +1471,23 @@ static translation_string all_strings[] = { {TR_BUILDING_FORT_JAVELIN, "Auxiliaries - Javelins" }, {TR_WINDOW_ADVISOR_JAVELIN, "Javelins" }, {TR_WARNING_NO_ARMOURY, "Build an armory to deliver weapons to barracks" }, + {TR_CONFIG_USER_PATH_DEFAULT, "Default (Caesar III installation path, no subdirectories)" }, + {TR_CONFIG_USER_PATH_WITH_SUBDIRECTORIES, "Caesar III installation path, using subdirectories" }, + {TR_CONFIG_USER_PATH_RECOMMENDED, "Recommended ("}, + {TR_CONFIG_USER_PATH_CUSTOM, "Set custom directory..."}, + {TR_USER_DIRECTORIES_NOT_SET_UP_TITLE, "Custom user directories not setup"}, + {TR_USER_DIRECTORIES_NOT_SET_UP_TEXT, "You have not setup your user directory, where the savegames, scenarios and maps will be stored.\n" \ + "Do you wish to do it now?"}, + {TR_USER_DIRECTORIES_CANCELLED_TITLE, "Directory selection canceled"}, + {TR_USER_DIRECTORIES_CANCELLED_TEXT, "You have decided not to set a user directory. The C3 install directory will be used.\n" + "You can change your settings later in the configuration window."}, + {TR_USER_DIRECTORIES_NOT_WRITEABLE_TITLE, "Directory not writeable"}, + {TR_USER_DIRECTORIES_NOT_WRITEABLE_TEXT, "The selected directory is not writeable.\n\nPlease select a different directory."}, + {TR_USER_DIRECTORIES_WINDOW_TITLE, "Set user directory"}, + {TR_USER_DIRETORIES_WINDOW_USER_PATH, "User directory:" }, + {TR_USER_DIRECTORIES_USER_PATH_CHANGED_TITLE, "User path changed"}, + {TR_USER_DIRECTORIES_USER_PATH_CHANGED_TEXT, "The user path was successfully changed.\nDo you want to copy over your files?"}, + {TR_USER_DIRECTORIES_USER_PATH_CHANGED_OVERWRITE, "Overwrite existing files"} }; diff --git a/src/translation/translation.h b/src/translation/translation.h index ba3ac53b35..916d5eed57 100644 --- a/src/translation/translation.h +++ b/src/translation/translation.h @@ -858,6 +858,8 @@ typedef enum { TR_CITY_MESSAGE_TEXT_EMPERORS_WRATH, TR_BUILDING_PALISADE_GATE, TR_BUILDING_PALISADE_GATE_DESC, + TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TITLE, + TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TEXT, TR_SAVE_DIALOG_INVALID_FILE, TR_SAVE_DIALOG_INVALID_FILE_DESC, TR_SAVE_DIALOG_INCOMPATIBLE_VERSION, @@ -1463,6 +1465,21 @@ typedef enum { TR_BUILDING_FORT_JAVELIN, TR_WINDOW_ADVISOR_JAVELIN, TR_WARNING_NO_ARMOURY, + TR_CONFIG_USER_PATH_DEFAULT, + TR_CONFIG_USER_PATH_WITH_SUBDIRECTORIES, + TR_CONFIG_USER_PATH_RECOMMENDED, + TR_CONFIG_USER_PATH_CUSTOM, + TR_USER_DIRECTORIES_NOT_SET_UP_TITLE, + TR_USER_DIRECTORIES_NOT_SET_UP_TEXT, + TR_USER_DIRECTORIES_CANCELLED_TITLE, + TR_USER_DIRECTORIES_CANCELLED_TEXT, + TR_USER_DIRECTORIES_NOT_WRITEABLE_TITLE, + TR_USER_DIRECTORIES_NOT_WRITEABLE_TEXT, + TR_USER_DIRECTORIES_WINDOW_TITLE, + TR_USER_DIRETORIES_WINDOW_USER_PATH, + TR_USER_DIRECTORIES_USER_PATH_CHANGED_TITLE, + TR_USER_DIRECTORIES_USER_PATH_CHANGED_TEXT, + TR_USER_DIRECTORIES_USER_PATH_CHANGED_OVERWRITE, TRANSLATION_MAX_KEY } translation_key; diff --git a/src/window/cck_selection.c b/src/window/cck_selection.c index 8b60727a5d..51126ed1e9 100644 --- a/src/window/cck_selection.c +++ b/src/window/cck_selection.c @@ -72,7 +72,7 @@ static struct { static void init(void) { - data.scenarios = dir_find_files_with_extension(".", "map"); + data.scenarios = dir_find_files_with_extension_at_location(PATH_LOCATION_SCENARIO, "map"); data.scenarios = dir_append_files_with_extension("mapx"); data.focus_toggle_button = 0; data.show_minimap = 0; @@ -276,7 +276,10 @@ static void select_scenario(int index, int is_double_click) { if (strcmp(data.selected_scenario_filename, data.scenarios->files[index].name) != 0) { snprintf(data.selected_scenario_filename, FILE_NAME_MAX, "%s", data.scenarios->files[index].name); - game_file_io_read_scenario_info(data.selected_scenario_filename, &data.info); + const char *filename = dir_get_file_at_location(data.selected_scenario_filename, PATH_LOCATION_SCENARIO); + if (filename) { + game_file_io_read_scenario_info(filename, &data.info); + } encoding_from_utf8(data.selected_scenario_filename, data.selected_scenario_display, FILE_NAME_MAX); file_remove_extension((char *) data.selected_scenario_display); window_invalidate(); diff --git a/src/window/file_dialog.c b/src/window/file_dialog.c index 8de0489c28..a21c7bcfdd 100644 --- a/src/window/file_dialog.c +++ b/src/window/file_dialog.c @@ -9,7 +9,6 @@ #include "core/lang.h" #include "core/log.h" #include "core/string.h" -#include "core/time.h" #include "editor/empire.h" #include "empire/xml.h" #include "game/file.h" @@ -49,8 +48,6 @@ #define FILTER_TEXT_SIZE 16 #define MIN_FILTER_SIZE 2 -static const time_millis NOT_EXIST_MESSAGE_TIMEOUT = 500; - static void button_toggle_sort_type(int param1, int param2); static void button_ok_cancel(int is_ok, int param2); static void input_box_changed(int is_addition_at_end); @@ -69,12 +66,11 @@ static generic_button sort_by_button[] = { typedef struct { const char *extension; - const char *path; + int location; char last_loaded_file[FILE_NAME_MAX]; } file_type_data; static struct { - time_millis message_not_exist_start_time; file_type type; file_dialog_type dialog_type; @@ -124,13 +120,13 @@ static const int MISSION_ID_TO_CITY_ID[] = { 0, 3, 2, 1, 7, 10, 18, 4, 30, 6, 12, 14, 16, 27, 31, 23, 36, 38, 28, 25 }; -static file_type_data saved_game_data = { "sav", "." }; -static file_type_data saved_game_data_expanded = { "svx", "." }; -static file_type_data scenario_data = { "map", "." }; -static file_type_data scenario_data_expanded = { "mapx", "." }; -static file_type_data empire_data = { "xml", "custom_empires" }; -static file_type_data scenario_event_data = { "xml", "editor/events" }; -static file_type_data custom_messages_data = { "xml", "editor/messages" }; +static file_type_data saved_game_data = { "sav", PATH_LOCATION_SAVEGAME }; +static file_type_data saved_game_data_expanded = { "svx", PATH_LOCATION_SAVEGAME }; +static file_type_data scenario_data = { "map", PATH_LOCATION_SCENARIO }; +static file_type_data scenario_data_expanded = { "mapx", PATH_LOCATION_SCENARIO}; +static file_type_data empire_data = { "xml", PATH_LOCATION_EDITOR_CUSTOM_EMPIRES }; +static file_type_data scenario_event_data = { "xml", PATH_LOCATION_EDITOR_CUSTOM_EVENTS }; +static file_type_data custom_messages_data = { "xml", PATH_LOCATION_EDITOR_CUSTOM_MESSAGES }; static int compare_name(const void *va, const void *vb) { @@ -220,8 +216,6 @@ static void init(file_type type, file_dialog_type dialog_type) } data.dialog_type = dialog_type; - data.message_not_exist_start_time = 0; - if (strlen(data.file_data->last_loaded_file) > 0) { snprintf(data.selected_file, FILE_NAME_MAX, "%s", data.file_data->last_loaded_file); if (data.dialog_type == FILE_DIALOG_SAVE) { @@ -229,13 +223,13 @@ static void init(file_type type, file_dialog_type dialog_type) } encoding_from_utf8(data.selected_file, data.typed_name, FILE_NAME_MAX); if (data.dialog_type == FILE_DIALOG_SAVE) { - file_append_extension(data.selected_file, data.file_data->extension); + file_append_extension(data.selected_file, data.file_data->extension, FILE_NAME_MAX); } } else if (dialog_type == FILE_DIALOG_SAVE) { // Suggest default filename string_copy(lang_get_string(9, type == FILE_TYPE_SCENARIO ? 7 : 6), data.typed_name, FILE_NAME_MAX); encoding_to_utf8(data.typed_name, data.selected_file, FILE_NAME_MAX, encoding_system_uses_decomposed()); - file_append_extension(data.selected_file, data.file_data->extension); + file_append_extension(data.selected_file, data.file_data->extension, FILE_NAME_MAX); } else { // Use empty string data.typed_name[0] = 0; @@ -244,20 +238,20 @@ static void init(file_type type, file_dialog_type dialog_type) if (data.dialog_type != FILE_DIALOG_SAVE) { if (type == FILE_TYPE_SCENARIO) { - data.file_list = dir_find_files_with_extension(scenario_data.path, scenario_data.extension); + data.file_list = dir_find_files_with_extension_at_location(scenario_data.location, scenario_data.extension); data.file_list = dir_append_files_with_extension(scenario_data_expanded.extension); } else if (type == FILE_TYPE_EMPIRE) { - data.file_list = dir_find_files_with_extension(empire_data.path, empire_data.extension); + data.file_list = dir_find_files_with_extension_at_location(empire_data.location, empire_data.extension); } else if (type == FILE_TYPE_SCENARIO_EVENTS) { - data.file_list = dir_find_files_with_extension(scenario_event_data.path, scenario_event_data.extension); + data.file_list = dir_find_files_with_extension_at_location(scenario_event_data.location, scenario_event_data.extension); } else if (type == FILE_TYPE_CUSTOM_MESSAGES) { - data.file_list = dir_find_files_with_extension(custom_messages_data.path, custom_messages_data.extension); + data.file_list = dir_find_files_with_extension_at_location(custom_messages_data.location, custom_messages_data.extension); } else { - data.file_list = dir_find_files_with_extension(saved_game_data.path, saved_game_data.extension); + data.file_list = dir_find_files_with_extension_at_location(saved_game_data.location, saved_game_data.extension); data.file_list = dir_append_files_with_extension(saved_game_data_expanded.extension); } } else { - data.file_list = dir_find_files_with_extension(data.file_data->path, data.file_data->extension); + data.file_list = dir_find_files_with_extension_at_location(data.file_data->location, data.file_data->extension); } init_filtered_file_list(); list_box_init(&list_box, data.filtered_file_list.num_files); @@ -309,10 +303,15 @@ static void draw_background(void) { window_draw_underlying_window(); if (*data.selected_file) { - if (data.type == FILE_TYPE_SAVED_GAME) { - data.savegame_info_status = game_file_io_read_saved_game_info(data.selected_file, &data.info); + const char *filename = dir_get_file_at_location(data.selected_file, data.file_data->location); + if (filename) { + if (data.type == FILE_TYPE_SAVED_GAME) { + data.savegame_info_status = game_file_io_read_saved_game_info(filename, &data.info); + } else { + data.savegame_info_status = game_file_io_read_scenario_info(filename, &data.info); + } } else { - data.savegame_info_status = game_file_io_read_scenario_info(data.selected_file, &data.info); + data.savegame_info_status = SAVEGAME_STATUS_INVALID; } } data.redraw_full_window = 1; @@ -339,10 +338,7 @@ static void draw_foreground(void) list_box_request_refresh(&list_box); // title - if (data.message_not_exist_start_time - && time_get_millis() - data.message_not_exist_start_time < NOT_EXIST_MESSAGE_TIMEOUT) { - lang_text_draw_centered(43, 2, 32, 14, 554, FONT_LARGE_BLACK); - } else if (data.dialog_type == FILE_DIALOG_DELETE) { + if (data.dialog_type == FILE_DIALOG_DELETE) { lang_text_draw_centered(43, 6, 32, 14, 554, FONT_LARGE_BLACK); } else if (data.type == FILE_TYPE_EMPIRE) { lang_text_draw_centered(CUSTOM_TRANSLATION, TR_EDITOR_CUSTOM_EMPIRE_TITLE, 32, 14, 554, FONT_LARGE_BLACK); @@ -442,12 +438,6 @@ static void handle_input(const mouse *m, const hotkeys *h) return; } - if (data.message_not_exist_start_time && - time_get_millis() - data.message_not_exist_start_time >= NOT_EXIST_MESSAGE_TIMEOUT) { - data.redraw_full_window = 1; - data.message_not_exist_start_time = 0; - } - const mouse *m_dialog = mouse_in_dialog(m); if (input_box_handle_mouse(m_dialog, &main_input) || @@ -522,7 +512,7 @@ static void input_box_changed(int is_addition_at_end) if (data.file_list->num_files > NUM_FILES_IN_VIEW) { scroll_index = find_first_file_with_prefix(data.selected_file); } - file_append_extension(data.selected_file, data.file_data->extension); + file_append_extension(data.selected_file, data.file_data->extension, FILE_NAME_MAX); if (scroll_index >= 0 && platform_file_manager_compare_filename(data.selected_file, data.filtered_file_list.files[scroll_index].name) == 0) { @@ -545,31 +535,12 @@ static void input_box_changed(int is_addition_at_end) list_box_update_total_items(&list_box, data.filtered_file_list.num_files); } -static const char *prepare_filename(void) -{ - static char filename[FILE_NAME_MAX]; - int result = 0; - if (data.type == FILE_TYPE_EMPIRE) { - result = snprintf(filename, FILE_NAME_MAX, "custom_empires/%s", data.selected_file); - } else if (data.type == FILE_TYPE_SCENARIO_EVENTS) { - result = snprintf(filename, FILE_NAME_MAX, "editor/events/%s", data.selected_file); - } else if (data.type == FILE_TYPE_CUSTOM_MESSAGES) { - result = snprintf(filename, FILE_NAME_MAX, "editor/messages/%s", data.selected_file); - } else { - result = snprintf(filename, FILE_NAME_MAX, "%s", data.selected_file); - } - if (result > FILE_NAME_MAX) { - log_info("Output is truncated", data.selected_file, 0); - } - return filename; -} - static void confirm_save_file(int accepted, int checked) { if (!accepted) { return; } - const char *filename = prepare_filename(); + const char *filename = dir_append_location(data.selected_file, data.file_data->location); input_box_stop(&main_input); if (checked) { config_set(CONFIG_UI_ASK_CONFIRMATION_ON_FILE_OVERWRITE, 0); @@ -606,11 +577,17 @@ static void button_ok_cancel(int is_ok, int param2) return; } - const char *filename = prepare_filename(); + const char *filename; - if (data.dialog_type != FILE_DIALOG_SAVE && !file_exists(filename, NOT_LOCALIZED)) { - data.message_not_exist_start_time = time_get_millis(); - return; + if (data.dialog_type == FILE_DIALOG_SAVE) { + filename = dir_append_location(data.selected_file, data.file_data->location); + } else { + filename = dir_get_file_at_location(data.selected_file, data.file_data->location); + if (!filename) { + window_plain_message_dialog_show(TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TITLE, + TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TEXT, 1); + return; + } } if (data.dialog_type == FILE_DIALOG_LOAD) { @@ -619,7 +596,8 @@ static void button_ok_cancel(int is_ok, int param2) if (result == FILE_LOAD_SUCCESS) { window_city_show(); } else if (result == FILE_LOAD_DOES_NOT_EXIST) { - data.message_not_exist_start_time = time_get_millis(); + window_plain_message_dialog_show(TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TITLE, + TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TEXT, 1); return; } else if (result == FILE_LOAD_INCOMPATIBLE_VERSION) { window_plain_message_dialog_show(TR_SAVEGAME_LARGER_VERSION_TITLE, @@ -634,13 +612,14 @@ static void button_ok_cancel(int is_ok, int param2) if (game_file_editor_load_scenario(filename)) { window_editor_map_show(); } else { - data.message_not_exist_start_time = time_get_millis(); + window_plain_message_dialog_show(TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TITLE, + TR_SAVE_DIALOG_FILE_DOES_NOT_EXIST_TEXT, 1); return; } } else if (data.type == FILE_TYPE_EMPIRE) { int result = empire_xml_parse_file(filename); if (result) { - scenario_editor_set_custom_empire(filename); + scenario_editor_set_custom_empire(data.selected_file); window_editor_empire_show(); } else { window_plain_message_dialog_show(TR_EDITOR_UNABLE_TO_LOAD_EMPIRE_TITLE, @@ -668,7 +647,8 @@ static void button_ok_cancel(int is_ok, int param2) input_box_stop(&main_input); snprintf(data.file_data->last_loaded_file, FILE_NAME_MAX, "%s", data.selected_file); } else if (data.dialog_type == FILE_DIALOG_SAVE) { - if (config_get(CONFIG_UI_ASK_CONFIRMATION_ON_FILE_OVERWRITE) && file_exists(filename, NOT_LOCALIZED)) { + if (config_get(CONFIG_UI_ASK_CONFIRMATION_ON_FILE_OVERWRITE) && + dir_get_file_at_location(data.selected_file, data.file_data->location)) { window_popup_dialog_show_confirmation(lang_get_string(CUSTOM_TRANSLATION, TR_SAVE_DIALOG_OVERWRITE_FILE), lang_get_string(CUSTOM_TRANSLATION, TR_SAVE_DIALOG_OVERWRITE_FILE_DESC), lang_get_string(CUSTOM_TRANSLATION, TR_SAVE_DIALOG_OVERWRITE_FILE_DO_NOT_ASK_AGAIN), confirm_save_file); @@ -677,7 +657,7 @@ static void button_ok_cancel(int is_ok, int param2) } } else if (data.dialog_type == FILE_DIALOG_DELETE) { if (game_file_delete_saved_game(filename)) { - dir_find_files_with_extension(saved_game_data.path, saved_game_data.extension); + data.file_list = dir_find_files_with_extension_at_location(saved_game_data.location, saved_game_data.extension); dir_append_files_with_extension(saved_game_data_expanded.extension); init_filtered_file_list(); @@ -708,7 +688,6 @@ static void select_file(int index, int is_double_click) return; } if (strcmp(data.selected_file, data.filtered_file_list.files[index].name) != 0) { - data.message_not_exist_start_time = 0; snprintf(data.selected_file, FILE_NAME_MAX, "%s", data.filtered_file_list.files[index].name); if (data.dialog_type == FILE_DIALOG_SAVE) { file_remove_extension(data.selected_file); @@ -716,7 +695,7 @@ static void select_file(int index, int is_double_click) encoding_from_utf8(data.selected_file, data.typed_name, FILE_NAME_MAX); if (data.dialog_type == FILE_DIALOG_SAVE) { input_box_refresh_text(&main_input); - file_append_extension(data.selected_file, data.file_data->extension); + file_append_extension(data.selected_file, data.file_data->extension, FILE_NAME_MAX); } window_request_refresh(); } diff --git a/src/window/logo.c b/src/window/logo.c index 832267e836..ca3fe91b3e 100644 --- a/src/window/logo.c +++ b/src/window/logo.c @@ -9,14 +9,58 @@ #include "window/intro_video.h" #include "window/main_menu.h" #include "window/plain_message_dialog.h" +#include "window/popup_dialog.h" +#include "window/user_path_setup.h" -static void init(void) +static int pending_actions; + +static void init(int actions) { + pending_actions = actions; sound_music_play_intro(); } +static int process_pending_actions(void) +{ + if (pending_actions & ACTION_SHOW_MESSAGE_MISSING_PATCH) { + window_plain_message_dialog_show(TR_NO_PATCH_TITLE, TR_NO_PATCH_MESSAGE, 0); + pending_actions ^= ACTION_SHOW_MESSAGE_MISSING_PATCH; + return 1; + } + if (pending_actions & ACTION_SHOW_MESSAGE_MISSING_FONTS) { + window_plain_message_dialog_show(TR_MISSING_FONTS_TITLE, TR_MISSING_FONTS_MESSAGE, 0); + pending_actions ^= ACTION_SHOW_MESSAGE_MISSING_FONTS; + return 1; + } + if (pending_actions & ACTION_SHOW_MESSAGE_MISSING_EXTRA_ASSETS) { + window_plain_message_dialog_show(TR_NO_EXTRA_ASSETS_TITLE, TR_NO_EXTRA_ASSETS_MESSAGE, 0); + pending_actions ^= ACTION_SHOW_MESSAGE_MISSING_EXTRA_ASSETS; + return 1; + } + if (pending_actions & ACTION_SETUP_USER_DIR) { + window_user_path_setup_show(1); + pending_actions ^= ACTION_SETUP_USER_DIR; + return 1; + } + if (pending_actions & ACTION_SHOW_MESSAGE_USER_DIR_NOT_WRITABLE) { + window_plain_message_dialog_show(TR_USER_DIRECTORIES_NOT_WRITEABLE_TITLE, + TR_USER_DIRECTORIES_NOT_WRITEABLE_TEXT, 0); + pending_actions ^= ACTION_SHOW_MESSAGE_USER_DIR_NOT_WRITABLE; + return 1; + } + if (pending_actions & ACTION_SHOW_INTRO_VIDEOS) { + window_intro_video_show(); + pending_actions ^= ACTION_SHOW_INTRO_VIDEOS; + return 1; + } + return 0; +} + static void draw_background(void) { + if (process_pending_actions()) { + return; + } graphics_clear_screen(); graphics_in_dialog(); @@ -27,6 +71,9 @@ static void draw_background(void) static void handle_input(const mouse *m, const hotkeys *h) { + if (pending_actions) { + return; + } if (m->left.went_up || m->right.went_up) { window_main_menu_show(0); return; @@ -36,7 +83,7 @@ static void handle_input(const mouse *m, const hotkeys *h) } } -void window_logo_show(int show_patch_message) +void window_logo_show(int actions) { window_type window = { WINDOW_LOGO, @@ -44,16 +91,6 @@ void window_logo_show(int show_patch_message) 0, handle_input }; - init(); + init(actions); window_show(&window); - if (show_patch_message == MESSAGE_MISSING_PATCH) { - window_plain_message_dialog_show(TR_NO_PATCH_TITLE, TR_NO_PATCH_MESSAGE, 0); - } else if (show_patch_message == MESSAGE_MISSING_FONTS) { - window_plain_message_dialog_show(TR_MISSING_FONTS_TITLE, TR_MISSING_FONTS_MESSAGE, 0); - } else if (show_patch_message == MESSAGE_MISSING_EXTRA_ASSETS) { - window_plain_message_dialog_show(TR_NO_EXTRA_ASSETS_TITLE, TR_NO_EXTRA_ASSETS_MESSAGE, 0); - } - if (config_get(CONFIG_UI_SHOW_INTRO_VIDEO)) { - window_intro_video_show(); - } } diff --git a/src/window/logo.h b/src/window/logo.h index d4538fb2db..9821ecbc28 100644 --- a/src/window/logo.h +++ b/src/window/logo.h @@ -2,12 +2,15 @@ #define WINDOW_LOGO_H enum { - MESSAGE_NONE = 0, - MESSAGE_MISSING_PATCH = 1, - MESSAGE_MISSING_FONTS = 2, - MESSAGE_MISSING_EXTRA_ASSETS = 3, + ACTION_NONE = 0, + ACTION_SHOW_MESSAGE_MISSING_PATCH = 1 << 0, + ACTION_SHOW_MESSAGE_MISSING_FONTS = 1 << 1, + ACTION_SHOW_MESSAGE_MISSING_EXTRA_ASSETS = 1 << 2, + ACTION_SETUP_USER_DIR = 1 << 3, + ACTION_SHOW_MESSAGE_USER_DIR_NOT_WRITABLE = 1 << 4, + ACTION_SHOW_INTRO_VIDEOS = 1 << 5 }; -void window_logo_show(int show_patch_message); +void window_logo_show(int actions); #endif // WINDOW_LOGO_H diff --git a/src/window/new_campaign.c b/src/window/new_campaign.c index 1089464019..2fbe018d0f 100644 --- a/src/window/new_campaign.c +++ b/src/window/new_campaign.c @@ -94,7 +94,7 @@ static void init(void) if (string_equals(player_name_input.placeholder, data.player_name)) { *data.player_name = 0; } - data.campaign_list = dir_find_all_subdirectories(CAMPAIGNS_DIR_NAME); + data.campaign_list = dir_find_all_subdirectories_at_location(PATH_LOCATION_CAMPAIGN); data.campaign_list = dir_append_files_with_extension("campaign"); calculate_input_box_width(); input_box_start(&player_name_input); diff --git a/src/window/popup_dialog.c b/src/window/popup_dialog.c index 359f0e8b3a..ddb5e91bad 100644 --- a/src/window/popup_dialog.c +++ b/src/window/popup_dialog.c @@ -97,7 +97,7 @@ static void draw_foreground(void) button_border_draw(data.checkbox_start_width, 180, CHECKBOX_CHECK_SIZE, CHECKBOX_CHECK_SIZE, data.has_focus); } if (data.has_buttons) { - image_buttons_draw(80, data.checkbox_text ? 110 : 80, buttons, 2); + image_buttons_draw(80, data.checkbox_text ? 110 : 90, buttons, 2); } else { lang_text_draw_centered(13, 1, 80, 208, 480, FONT_NORMAL_BLACK); } @@ -110,12 +110,12 @@ static void handle_input(const mouse *m, const hotkeys *h) return; } if (data.has_buttons && image_buttons_handle_mouse(mouse_in_dialog(m), 80, - data.checkbox_text ? 110 : 80, buttons, 2, 0)) { + data.checkbox_text ? 110 : 90, buttons, 2, 0)) { return; } if (input_go_back_requested(m, h)) { - data.close_func(0, 0); window_go_back(); + data.close_func(0, 0); } if (h->enter_pressed) { confirm(); diff --git a/src/window/select_list.c b/src/window/select_list.c index 4fc9b461ab..5960ddf5b0 100644 --- a/src/window/select_list.c +++ b/src/window/select_list.c @@ -10,6 +10,8 @@ #include "input/input.h" #define MAX_ITEMS_PER_LIST 20 +#define BASE_LIST_WIDTH 200 +#define MAX_LIST_WIDTH 496 enum { MODE_TEXT, @@ -71,6 +73,7 @@ static struct { int group; const uint8_t **items; int num_items; + int width; void (*callback)(int); int focus_button_id; } data; @@ -81,8 +84,12 @@ static void init_group(int x, int y, int group, int num_items, void (*callback)( data.y = y; data.mode = MODE_GROUP; data.group = group; + data.width = BASE_LIST_WIDTH; data.num_items = num_items; data.callback = callback; + for (int i = 0; i < MAX_ITEMS_PER_LIST; i++) { + buttons_list1[i].width = data.width - 10; + } } static void init_text(int x, int y, const uint8_t **items, int num_items, void (*callback)(int)) @@ -93,6 +100,26 @@ static void init_text(int x, int y, const uint8_t **items, int num_items, void ( data.items = items; data.num_items = num_items; data.callback = callback; + data.width = BASE_LIST_WIDTH; + if (data.num_items <= MAX_ITEMS_PER_LIST) { + for (int i = 0; i < num_items; i++) { + int width = text_get_width(data.items[i], FONT_NORMAL_PLAIN) + 10; + if (width > data.width) { + data.width = width; + data.width += BLOCK_SIZE - (data.width % BLOCK_SIZE); + if (width > MAX_LIST_WIDTH) { + data.width = MAX_LIST_WIDTH; + } + } + } + for (int i = 0; i < num_items; i++) { + buttons_list1[i].width = data.width; + } + } else { + for (int i = 0; i < MAX_ITEMS_PER_LIST; i++) { + buttons_list1[i].width = data.width - 10; + } + } } static int items_in_first_list(void) @@ -104,9 +131,15 @@ static void draw_item(int item_id, int x, int y, int selected) { color_t color = selected ? COLOR_FONT_BLUE : COLOR_BLACK; if (data.mode == MODE_GROUP) { - lang_text_draw_centered_colored(data.group, item_id, data.x + x, data.y + y, 190, FONT_NORMAL_PLAIN, color); + lang_text_draw_centered_colored(data.group, item_id, data.x + x, data.y + y, data.width - 10, + FONT_NORMAL_PLAIN, color); } else { - text_draw_centered(data.items[item_id], data.x + x, data.y + y, 190, FONT_NORMAL_PLAIN, color); + if (data.width == BASE_LIST_WIDTH) { + text_draw_centered(data.items[item_id], data.x + x, data.y + y, BASE_LIST_WIDTH - 10, FONT_NORMAL_PLAIN, color); + } else { + text_draw_ellipsized(data.items[item_id], data.x + x + 5, data.y + y, + data.width - 10, FONT_NORMAL_PLAIN, color); + } } } @@ -122,7 +155,8 @@ static void draw_foreground(void) draw_item(i + max_first, 205, 11 + 20 * i, MAX_ITEMS_PER_LIST + i + 1 == data.focus_button_id); } } else { - outer_panel_draw(data.x, data.y, 13, (20 * data.num_items + 24) / BLOCK_SIZE); + int width_blocks = (data.width + BLOCK_SIZE - 1) / BLOCK_SIZE; + outer_panel_draw(data.x, data.y, width_blocks, (20 * data.num_items + 24) / BLOCK_SIZE); for (int i = 0; i < data.num_items; i++) { draw_item(i, 5, 11 + 20 * i, i + 1 == data.focus_button_id); } @@ -137,7 +171,7 @@ static int click_outside_window(const mouse *m) width = 26 * BLOCK_SIZE; height = 20 * items_in_first_list() + 24; } else { - width = 13 * BLOCK_SIZE; + width = data.width + BLOCK_SIZE - 1; height = 20 * data.num_items + 24; } return m->left.went_up && (m->x < data.x || m->x >= data.x + width || m->y < data.y || m->y >= data.y + height); diff --git a/src/window/user_path_setup.c b/src/window/user_path_setup.c new file mode 100644 index 0000000000..431c66c69d --- /dev/null +++ b/src/window/user_path_setup.c @@ -0,0 +1,279 @@ +#include "user_path_setup.h" + +#include "core/encoding.h" +#include "core/file.h" +#include "core/image_group.h" +#include "core/string.h" +#include "game/system.h" +#include "graphics/image_button.h" +#include "graphics/generic_button.h" +#include "graphics/panel.h" +#include "graphics/graphics.h" +#include "graphics/lang_text.h" +#include "graphics/screen.h" +#include "graphics/text.h" +#include "graphics/window.h" +#include "platform/file_manager.h" +#include "platform/prefs.h" +#include "platform/user_path.h" +#include "window/plain_message_dialog.h" +#include "window/popup_dialog.h" +#include "window/select_list.h" + +#include + +static struct { + int show_window; + int window_status; + int button_in_focus; + int first_time; + char new_path[FILE_NAME_MAX]; +} data; + +static void button_pick_option(int param1, int param2); +static void button_ok_cancel(int is_ok, int param2); + +static generic_button path_button = { + 150, 55, 458, 30, button_pick_option, button_none +}; + +static image_button ok_cancel_buttons[] = { + {270, 100, 39, 26, IB_NORMAL, GROUP_OK_CANCEL_SCROLL_BUTTONS, 0, button_ok_cancel, button_none, 1, 0, 1}, + {330, 100, 39, 26, IB_NORMAL, GROUP_OK_CANCEL_SCROLL_BUTTONS, 4, button_ok_cancel, button_none, 0, 0, 1}, +}; + +static void init(int first_time) +{ + data.show_window = !first_time; + data.new_path[0] = 0; + data.button_in_focus = 0; + data.first_time = first_time; + if (data.first_time) { + platform_user_path_copy_campaigns_and_custom_empires(); + } +} + +static void cancel(void) +{ + window_go_back(); + window_plain_message_dialog_show(TR_USER_DIRECTORIES_CANCELLED_TITLE, TR_USER_DIRECTORIES_CANCELLED_TEXT, 0); +} + +static void show_window(int accepted, int checked) +{ + if (!accepted) { + cancel(); + return; + } + data.show_window = 1; +} + +static int encode_path(uint8_t *output, const char *input, int output_size) +{ + encoding_from_utf8(input, output, output_size); + int length = 0; + while (output[length]) { + if (output[length] == '\\') { + output[length] = '/'; + } + length++; + } + return length; +} + +static const uint8_t *get_path_text(void) +{ + if (!*data.new_path) { + snprintf(data.new_path, FILE_NAME_MAX, "%s", pref_user_dir()); + } + static uint8_t text[FILE_NAME_MAX]; + const uint8_t *path_text; + if (*data.new_path) { + if (strcmp(data.new_path, "./") == 0) { + path_text = translation_for(TR_CONFIG_USER_PATH_WITH_SUBDIRECTORIES); + } else { + encode_path(text, data.new_path, FILE_NAME_MAX); + path_text = text; + } + } else { + path_text = translation_for(TR_CONFIG_USER_PATH_DEFAULT); + } + return path_text; +} + +static void draw_background(void) +{ + if (!data.show_window) { + return; + } + graphics_clear_screen(); + graphics_in_dialog_with_size(640, 144); + + outer_panel_draw(0, 0, 40, 9); + lang_text_draw_centered(CUSTOM_TRANSLATION, TR_USER_DIRECTORIES_WINDOW_TITLE, 0, 20, 640, FONT_LARGE_BLACK); + + lang_text_draw(CUSTOM_TRANSLATION, TR_USER_DIRETORIES_WINDOW_USER_PATH, 16, 64, FONT_NORMAL_BLACK); + + text_draw_ellipsized(get_path_text(), path_button.x + 10, path_button.y + 9, path_button.width - 20, + FONT_NORMAL_BLACK, 0); + + graphics_reset_dialog(); +} + +static void draw_foreground(void) +{ + if (!data.show_window) { + return; + } + + graphics_in_dialog_with_size(640, 144); + + button_border_draw(path_button.x, path_button.y, path_button.width, path_button.height, data.button_in_focus); + image_buttons_draw(0, 0, ok_cancel_buttons, 2); + + graphics_reset_dialog(); +} + +static void handle_input(const mouse *m, const hotkeys *h) +{ + const mouse *m_dialog = mouse_in_dialog_with_size(m, 640, 144); + if (m->right.went_up) { + window_go_back(); + return; + } + if (generic_buttons_handle_mouse(m_dialog, 0, 0, &path_button, 1, &data.button_in_focus) || + image_buttons_handle_mouse(m_dialog, 0, 0, ok_cancel_buttons, 2, 0)) { + return; + } + if (h->escape_pressed) { + hotkey_handle_escape(); + } +} + +static const char *get_path_from_dialog(void) +{ + const char *user_path = data.new_path; + if (!*user_path) { + user_path = pref_user_dir(); + } + if (!*user_path || strcmp(user_path, "./") == 0) { + user_path = pref_data_dir(); + } + const char *path = system_show_select_folder_dialog("Please select your user path folder:", user_path); + if (!path) { + return 0; + } + return path; +} + +static void set_paths(int index) +{ + const char *path = ""; + if (index == 0) { + path = ""; + } else if (index == 1) { + path = "./"; + } else if (index == 2) { + const char *recommended = platform_user_path_recommend(); + if (recommended) { + path = recommended; + } else { + path = get_path_from_dialog(); + } + } else if (index == 3) { + path = get_path_from_dialog(); + } + if (!path) { + return; + } + if (strcmp(path, pref_data_dir()) == 0) { + snprintf(data.new_path, FILE_NAME_MAX, "./"); + } else { + snprintf(data.new_path, FILE_NAME_MAX, "%s", path); + } +} + +static void button_pick_option(int param1, int param2) +{ + static const uint8_t *texts[4]; + static uint8_t recommended_text[FILE_NAME_MAX]; + const char *recommended = platform_user_path_recommend(); + static int total_options = 3; + if (!texts[0]) { + texts[0] = translation_for(TR_CONFIG_USER_PATH_DEFAULT); + texts[1] = translation_for(TR_CONFIG_USER_PATH_WITH_SUBDIRECTORIES); + if (recommended) { + texts[2] = recommended_text; + uint8_t *cursor = string_copy(translation_for(TR_CONFIG_USER_PATH_RECOMMENDED), + recommended_text, FILE_NAME_MAX); + cursor += encode_path(cursor, recommended, FILE_NAME_MAX - (cursor - recommended_text)); + string_copy(string_from_ascii(")"), cursor, FILE_NAME_MAX - (cursor - recommended_text)); + total_options++; + } + texts[total_options - 1] = translation_for(TR_CONFIG_USER_PATH_CUSTOM); + }; + + window_select_list_show_text(screen_dialog_offset_x() + 150, + screen_dialog_offset_y() + path_button.y + path_button.height, texts, total_options, set_paths); +} + +static void copy_user_files(int accepted, int overwrite) +{ + window_go_back(); + char original_path[FILE_NAME_MAX]; + snprintf(original_path, FILE_NAME_MAX, "%s", pref_user_dir()); + pref_save_user_dir(data.new_path); + platform_user_path_create_subdirectories(); + if (!accepted) { + return; + } + platform_user_path_copy_files(original_path, overwrite); +} + +static void button_ok_cancel(int is_ok, int param2) +{ + if (!is_ok) { + cancel(); + return; + } + if (strcmp(data.new_path, pref_user_dir()) == 0) { + window_go_back(); + return; + } + if (!platform_file_manager_is_directory_writeable(data.new_path)) { + window_plain_message_dialog_show(TR_USER_DIRECTORIES_NOT_WRITEABLE_TITLE, + TR_USER_DIRECTORIES_NOT_WRITEABLE_TEXT, 0); + return; + } + window_popup_dialog_show_confirmation(translation_for(TR_USER_DIRECTORIES_USER_PATH_CHANGED_TITLE), + translation_for(TR_USER_DIRECTORIES_USER_PATH_CHANGED_TEXT), + translation_for(TR_USER_DIRECTORIES_USER_PATH_CHANGED_OVERWRITE), copy_user_files); +} + +static void get_tooltip(tooltip_context *c) +{ + if (data.button_in_focus) { + const uint8_t *path_text = get_path_text(); + if (text_get_width(path_text, FONT_NORMAL_BLACK) > path_button.width - 20) { + c->precomposed_text = path_text; + c->type = TOOLTIP_BUTTON; + } + } +} + +void window_user_path_setup_show(int first_time) +{ + window_type window = { + WINDOW_USER_PATH_SETUP, + draw_background, + draw_foreground, + handle_input, + get_tooltip + }; + init(first_time); + window_show(&window); + if (data.first_time) { + window_popup_dialog_show_confirmation(translation_for(TR_USER_DIRECTORIES_NOT_SET_UP_TITLE), + translation_for(TR_USER_DIRECTORIES_NOT_SET_UP_TEXT), 0, show_window); + } +} diff --git a/src/window/user_path_setup.h b/src/window/user_path_setup.h new file mode 100644 index 0000000000..9676d2862a --- /dev/null +++ b/src/window/user_path_setup.h @@ -0,0 +1,6 @@ +#ifndef WINDOW_USER_PATH_SETUP_H +#define WINDOW_USER_PATH_SETUP_H + +void window_user_path_setup_show(int first_time); + +#endif // WINDOW_USER_PATH_SETUP_H