1 /*________
2  /         \ This is a custom D port of tinyfiledialogs v3.3.9 [Apr 14, 2019]
3  |tiny file| http://tinyfiledialogs.sourceforge.net
4  | dialogs | Copyright (c) 2014-2019 Guillaume Vareille
5  \____  ___/ Copyright (c) 2019 dayllenger
6       \|
7 
8 Native dialog library for Windows, macOS, GTK+, Qt, console & more.
9 SSH supported via automatic switch to console mode or X11 forwarding.
10 
11 8 functions:
12 - beep
13 - notify popup (tray)
14 - message & question
15 - input & password
16 - save file
17 - open file(s)
18 - select folder
19 - color picker
20 
21 Each function is documented with examples.
22 The dialogs can be forced into console mode.
23 Supports UTF-8 (except Windows console).
24 
25 Windows XP to 10:
26 - native code & vbs create the graphic dialogs
27 - enhanced console mode can use dialog.exe from
28 http://andrear.altervista.org/home/cdialog.php
29 - basic console input
30 
31 Unix (using command line calls):
32 - applescript, kdialog, zenity
33 - python (2 or 3) + tkinter + python-dbus (optional)
34 - dialog (opens a console if needed)
35 - basic console input
36 The same executable can run across desktops & distributions.
37 
38 *** Notes ***
39 
40 - Avoid using " and ' in titles and messages.
41 - String memory is preallocated statically for all the returned values.
42 - Use platform-specific path separators: \ on Windows, / on Unix.
43 - If you pass only a path instead of path + filename,
44   make sure it ends with a separator.
45 - File and path names are tested before return, they are valid.
46 - There's one file filter only, it may contain several patterns.
47 - If no filter description is provided,
48   the list of patterns becomes the description.
49 - You can query the type of dialog that will be used, see `tinyfd_response`.
50 
51 - This is not for android nor ios.
52 - The code is betterC compatible, originally pure C89.
53 - Windows is fully supported from XP to 10 (maybe even older versions).
54 - OSX supported from 10.4 to latest (maybe even older versions).
55 - On Windows, it links against Comdlg32.lib and Ole32.lib.
56 - Set TINYFD_NOLIB version if you don't want to include the code creating
57   graphic dialogs. Then you won't need to link against those libs.
58 - On Unix, it tries command line calls.
59   They are already available on most (if not all) desktops.
60 - In the absence of those it will use gdialog, gxmessage or whiptail
61   with a textinputbox.
62 - If nothing is found, it switches to basic console input,
63   and opens a console if needed (requires xterm + bash).
64 
65 - On Windows, console mode only make sense for console applications.
66 - The package dialog must be installed to run in enhanced console mode.
67   It is already installed on most Unix systems.
68 - On OSX, the package dialog can be installed via
69   http://macappstore.org/dialog or http://macports.org
70 - On Windows, for enhanced console mode,
71   dialog.exe should be copied somewhere on your executable path.
72   It can be found at the bottom of the following page:
73   http://andrear.altervista.org/home/cdialog.php
74 - If dialog is missing, it will switch to basic console input.
75 - Mutiple selects are not allowed in console mode.
76 
77 Thanks for contributions, bug corrections & thorough testing to:
78 - Don Heyse http://ldglite.sf.net for bug corrections & thorough testing!
79 - Paul Rouget
80 
81 *** License ***
82 
83 This software is provided 'as-is', without any express or implied
84 warranty.  In no event will the authors be held liable for any damages
85 arising from the use of this software.
86 
87 Permission is granted to anyone to use this software for any purpose,
88 including commercial applications, and to alter it and redistribute it
89 freely, subject to the following restrictions:
90 
91 1. The origin of this software must not be misrepresented; you must not
92 claim that you wrote the original software.  If you use this software
93 in a product, an acknowledgment in the product documentation would be
94 appreciated but is not required.
95 2. Altered source versions must be plainly marked as such, and must not be
96 misrepresented as being the original software.
97 3. This notice may not be removed or altered from any source distribution.
98 */
99 module tinyfiledialogs;
100 
101 // version = TINYFD_NOLIB;
102 // version = TINYFD_NOSELECTFOLDERWIN;
103 version = TINYFD_NOCCSUNICODE;
104 
105 extern (C) nothrow @nogc:
106 
107 alias c_str = const(char*);
108 
109 /// No params, no return value, just beep
110 void tinyfd_beep()
111 {
112     _beep();
113 }
114 
115 /** Params:
116         title = C-string or null
117         message = C-string or null, can be multiline
118         iconType = "info" "warning" "error"
119 
120     Returns:
121         Return has only meaning for "tinyfd_query".
122 
123     Example:
124     ---
125     tinyfd_notifyPopup("the title", "the message from outer-space", "info");
126     ---
127 */
128 int tinyfd_notifyPopup(c_str title, c_str message, c_str iconType)
129 {
130     return _notifyPopup(title, message, iconType);
131 }
132 
133 /**	Params:
134         title = C-string or null
135         message = C-string or null, can be multiline
136         dialogType = "ok" "okcancel" "yesno" "yesnocancel"
137         iconType = "info" "warning" "error" "question"
138         defaultButton = 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel
139 
140     Returns:
141         0 for cancel/no, 1 for ok/yes, 2 for no in yesnocancel.
142 
143     Example:
144     ---
145     const int ret = tinyfd_messageBox("Hello World",
146         "graphic dialogs [yes] / console mode [no]?",
147         "yesno", "question", 1);
148     tinyfd_forceConsole = (ret == 0);
149     ---
150 */
151 int tinyfd_messageBox(
152     c_str title,
153     c_str message,
154     c_str dialogType,
155     c_str iconType,
156     int defaultButton,
157 )
158 {
159     return _messageBox(title, message, dialogType, iconType, defaultButton);
160 }
161 
162 /** Params:
163         title = C-string or null
164         message = C-string or null, may NOT contain \ n or \ t on windows
165         defaultInput = C-string, if null it's a password box
166 
167     Returns:
168         Entered text, `null` on cancel.
169 
170     Example:
171     ---
172     c_str passwd = tinyfd_inputBox("a password box",
173         "your password will be revealed", null);
174     if (passwd)
175         printf("your password is: %s\ n", passwd);
176     ---
177 */
178 c_str tinyfd_inputBox(c_str title, c_str message, c_str defaultInput)
179 {
180     return _inputBox(title, message, defaultInput);
181 }
182 
183 /** Params:
184         title = C-string or null
185         defaultPathAndFile = C-string or null
186         numOfFilterPatterns = 0
187         filterPatterns = `["*.jpg", "*.png"]`, or (sometimes) MIME type `["audio/mp3"]`, or null
188         singleFilterDescription = "Text files" or null
189 
190     Returns:
191         Selected file name, `null` on cancel.
192 
193     Example:
194     ---
195     const c_str[] patterns = ["application/x-dsrc"];
196     c_str filename = tinyfd_saveFileDialog(
197         "Save D source file", "mod.d",
198         cast(int)patterns.length, patterns.ptr, null);
199     if (filename)
200         tinyfd_messageBox("Chosen file is", filename, "ok", "info", 1);
201     ---
202 */
203 c_str tinyfd_saveFileDialog(
204     c_str title,
205     c_str defaultPathAndFile,
206     int numOfFilterPatterns,
207     c_str* filterPatterns,
208     c_str singleFilterDescription,
209 )
210 {
211     return _saveFileDialog(title, defaultPathAndFile, numOfFilterPatterns,
212         filterPatterns, singleFilterDescription);
213 }
214 
215 /** Params:
216         title = C-string or null
217         defaultPathAndFile = C-string or null
218         numOfFilterPatterns = 0
219         filterPatterns = `["*.jpg", "*.png"]`, or (on unix) MIME type `["audio/mp3"]`, or null
220         singleFilterDescription = "Image files" or null
221         allowMultipleSelects = does not work on console
222 
223     Returns:
224         Selected file name or `null` on cancel. In case of multiple files, the separator is |.
225 
226     Example:
227     ---
228     const c_str[] patterns = ["*.cpp *.cc *.C *.cxx *.c++"];
229     c_str filename = tinyfd_openFileDialog(
230         "Open a C++ File", null,
231         cast(int)patterns.length, patterns.ptr,
232         "C++ source code", false);
233     if (filename)
234         tinyfd_messageBox("Chosen file is", filename, "ok", "info", 1);
235     ---
236 */
237 c_str tinyfd_openFileDialog(
238     c_str title,
239     c_str defaultPathAndFile,
240     int numOfFilterPatterns,
241     c_str* filterPatterns,
242     c_str singleFilterDescription,
243     bool allowMultipleSelects,
244 )
245 {
246     return _openFileDialog(title, defaultPathAndFile, numOfFilterPatterns,
247         filterPatterns, singleFilterDescription, allowMultipleSelects);
248 }
249 
250 /** Params:
251         title = C-string or null
252         defaultPath = C-string or null
253 
254     Returns:
255         Selected folder path, `null` on cancel
256 
257     Example:
258     ---
259     c_str name = tinyfd_selectFolderDialog("Let us just select a directory", null);
260     if (name)
261         tinyfd_messageBox("The selected folder is", name, "ok", "info", 1);
262     ---
263 */
264 c_str tinyfd_selectFolderDialog(c_str title, c_str defaultPath)
265 {
266     return _selectFolderDialog(title, defaultPath);
267 }
268 
269 /** Params:
270         title = C-string or null
271         defaultHexRGB = default color, C-string or null
272         defaultRGB = used only if the previous parameter is null
273         resultRGB = also contains the result
274 
275     Returns:
276         The hexcolor as a string like "#FF0000" or `null` on cancel.
277 
278     Example:
279     ---
280     ubyte[3] rgbColor;
281     c_str hexColor = tinyfd_colorChooser("Choose a nice color", "#FF0077",
282         rgbColor, rgbColor);
283     if (hexColor)
284         tinyfd_messageBox("The selected hexcolor is", hexColor, "ok", "info", 1);
285     ---
286 */
287 c_str tinyfd_colorChooser(
288     c_str title,
289     c_str defaultHexRGB,
290     ref const ubyte[3] defaultRGB,
291     ref ubyte[3] resultRGB,
292 )
293 {
294     return _colorChooser(title, defaultHexRGB, defaultRGB, resultRGB);
295 }
296 
297 /**** Constants and variables ****/
298 __gshared:
299 
300 /// Contains tinyfd current version number
301 immutable char[8] tinyfd_version = "3.3.9";
302 
303 /// Info about requirements for a platform
304 version (Windows)
305 immutable char[] tinyfd_needs =
306 ` ___________
307 /           \
308 | tiny file |
309 |  dialogs  |
310 \_____  ____/
311       \|
312 tiny file dialogs on Windows needs:
313    a graphic display
314 or dialog.exe (enhanced console mode)
315 or a console for basic input`;
316 else
317 immutable char[] tinyfd_needs =
318 ` ___________
319 /           \
320 | tiny file |
321 |  dialogs  |
322 \_____  ____/
323       \|
324 tiny file dialogs on UNIX needs:
325    applescript
326 or kdialog
327 or zenity (or matedialog or qarma)
328 or python (2 or 3)
329  + tkinter + python-dbus (optional)
330 or dialog (opens console if needed)
331 or xterm + bash
332    (opens console for basic input)
333 or existing console for basic input`;
334 
335 /// On unix, prints the command line calls; default is `false`
336 bool tinyfd_verbose;
337 /// On unix, hide errors and warnings from called dialog; default is `true
338 bool tinyfd_silent = true;
339 
340 /** For unix & windows: 0 (graphic mode, default) or 1 (console mode).
341 
342     `false` - try to use a graphic solution, if it fails then it uses console mode.
343     `true` - forces all dialogs into console mode even when an X server is present,
344     if the package dialog (and a console is present) or dialog.exe is installed.
345     On windows it only make sense for console applications.
346 */
347 version (Windows)
348 {
349     version (TINYFD_NOLIB)
350         bool tinyfd_forceConsole = true;
351     else
352         bool tinyfd_forceConsole;
353 }
354 else
355     bool tinyfd_forceConsole;
356 
357 /** If you pass "tinyfd_query" as `title`, the functions will not display
358     the dialogs but will return 0 for console mode, 1 for graphic mode.
359     `tinyfd_response` is then filled with the retain solution.
360     possible values for `tinyfd_response` are (all lowercase),
361     for graphic mode: `windows
362         applescript kdialog zenity zenity3 matedialog qarma
363         python2-tkinter python3-tkinter python-dbus perl-dbus
364         gxmessage gmessage xmessage xdialog gdialog`,
365     for console mode: `dialog whiptail basicinput no_solution`
366 
367     Example:
368     ---
369     import core.stdc.string;
370 
371     char[1024] buf = '\0';
372     c_str mode = tinyfd_inputBox("tinyfd_query", null, null);
373 
374     strcpy(buf.ptr, "v");
375     strcat(buf.ptr, tinyfd_version.ptr);
376     strcat(buf.ptr, "\ n");
377     if (mode)
378         strcat(buf.ptr, "graphic mode: ");
379     else
380         strcat(buf.ptr, "console mode: ");
381     strcat(buf.ptr, tinyfd_response.ptr);
382     strcat(buf.ptr, "\ n");
383     strcat(buf.ptr, tinyfd_needs.ptr + 78);
384     tinyfd_messageBox("Hello", buf.ptr, "ok", "info", 0);
385     ---
386 */
387 char[1024] tinyfd_response = '\0';
388 
389 /**************************** IMPLEMENTATION ****************************/
390 
391 private:
392 import core.stdc.ctype;
393 import core.stdc.stdlib;
394 import core.stdc.string;
395 
396 version (Windows)
397 {
398     import core.stdc.stdio;
399     import core.stdc.wchar_;
400     import core.sys.windows.commdlg;
401     import core.sys.windows.w32api;
402     import core.sys.windows.winbase;
403     import core.sys.windows.wincon : GetConsoleMode, SetConsoleMode, GetConsoleWindow, ENABLE_ECHO_INPUT;
404     import core.sys.windows.windef;
405     import core.sys.windows.winnls;
406 
407     static assert(_WIN32_WINNT >= 0x0500);
408 
409     enum SLASH = "\\";
410 
411     int _getch();
412     FILE* _popen(const char* command, const char* mode);
413     int _pclose(FILE* stream);
414 
415     version (TINYFD_NOLIB)
416     {
417         bool gWarningDisplayed = true;
418     }
419     else
420     {
421         version = TINYFD_LIB;
422         import core.sys.windows.com : COINIT_APARTMENTTHREADED;
423         import core.sys.windows.wingdi : RGB, GetRValue, GetGValue, GetBValue;
424         import core.sys.windows.winuser;
425 
426         pragma(lib, "comdlg32.lib");
427         pragma(lib, "ole32.lib");
428         pragma(lib, "user32.lib");
429 
430         FILE* _wfopen(const wchar* filename, const wchar* mode);
431         int _wremove(const wchar* path);
432         wchar* _wgetenv(const wchar* varname);
433         // header versions are not nothrow @nogc
434         extern(Windows) HRESULT CoInitializeEx(LPVOID, DWORD);
435         extern(Windows) void CoUninitialize();
436 
437         bool gWarningDisplayed;
438     }
439 
440     version (TINYFD_NOSELECTFOLDERWIN) {}
441     else
442     {
443         version = TINYFD_SELECTFOLDERWIN;
444         import core.sys.windows.shlobj : BFFM_INITIALIZED, BFFM_SETSELECTIONW, BIF_USENEWUI,
445             BROWSEINFOW, LPCITEMIDLIST, LPITEMIDLIST, PBROWSEINFOW;
446 
447         extern(Windows) LPITEMIDLIST SHBrowseForFolderW(PBROWSEINFOW);
448         extern(Windows) BOOL SHGetPathFromIDListW(LPCITEMIDLIST, LPWSTR);
449     }
450 }
451 else
452 {
453     import core.stdc.limits;
454     import core.sys.posix.dirent;
455     import core.sys.posix.signal;
456     import core.sys.posix.stdio;
457     import core.sys.posix.sys.stat;
458     import core.sys.posix.sys.utsname;
459     import core.sys.posix.termios;
460     import core.sys.posix.unistd;
461 
462     enum SLASH = "/";
463     bool gWarningDisplayed;
464 }
465 
466 enum int MAX_PATH_OR_CMD = 1024; /* _MAX_PATH or MAX_PATH */
467 enum int MAX_MULTIPLE_FILES = 32;
468 
469 immutable char[] gTitle = "missing software! (we will try basic console input)";
470 
471 bool some(const char* str)
472 {
473     return str && str[0] != '\0';
474 }
475 
476 bool wsome(const wchar* str)
477 {
478     return str && str[0] != '\0';
479 }
480 
481 bool eq(const char* s1, const char* s2)
482 {
483     if (s1 is s2)
484         return true;
485     if (s1 is null || s2 is null)
486         return false;
487     return strcmp(s1, s2) == 0;
488 }
489 
490 char lastch(const char* str)
491 {
492     if (str)
493     {
494         size_t len = strlen(str);
495         if (len > 0)
496             return str[len - 1];
497     }
498     return '\0';
499 }
500 
501 void removeLastNL(char* str)
502 {
503     size_t len = strlen(str);
504     if (len > 0 && str[len - 1] == '\n')
505     {
506         str[len - 1] = '\0';
507     }
508 }
509 
510 void response(const char* message)
511 {
512     strcpy(tinyfd_response.ptr, message);
513 }
514 
515 char* getPathWithoutFinalSlash(
516     char* aoDestination, /* make sure it is allocated, use _MAX_PATH */
517     const char* aSource) /* aoDestination and aSource can be the same */
518 {
519     const(char)* lTmp;
520     if (aSource)
521     {
522         lTmp = strrchr(aSource, '/');
523         if (!lTmp)
524         {
525             lTmp = strrchr(aSource, '\\');
526         }
527         if (lTmp)
528         {
529             strncpy(aoDestination, aSource, lTmp - aSource);
530             aoDestination[lTmp - aSource] = '\0';
531         }
532         else
533         {
534             *aoDestination = '\0';
535         }
536     }
537     else
538     {
539         *aoDestination = '\0';
540     }
541     return aoDestination;
542 }
543 
544 char* getLastName(
545     char* aoDestination, /* make sure it is allocated */
546     const char* aSource)
547 {
548     /* copy the last name after '/' or '\' */
549     const(char)* lTmp;
550     if (aSource)
551     {
552         lTmp = strrchr(aSource, '/');
553         if (!lTmp)
554         {
555             lTmp = strrchr(aSource, '\\');
556         }
557         if (lTmp)
558         {
559             strcpy(aoDestination, lTmp + 1);
560         }
561         else
562         {
563             strcpy(aoDestination, aSource);
564         }
565     }
566     else
567     {
568         *aoDestination = '\0';
569     }
570     return aoDestination;
571 }
572 
573 void ensureFinalSlash(char* aioString)
574 {
575     if (some(aioString))
576     {
577         char* lastcar = aioString + strlen(aioString) - 1;
578         if (strncmp(lastcar, SLASH, 1))
579         {
580             strcat(lastcar, SLASH);
581         }
582     }
583 }
584 
585 void Hex2RGB(const char* aHexRGB, ref ubyte[3] aoResultRGB)
586 {
587     char[8] lColorChannel = '\0';
588     if (aHexRGB)
589     {
590         strcpy(lColorChannel.ptr, aHexRGB);
591         aoResultRGB[2] = cast(ubyte)strtoul(lColorChannel.ptr + 5, null, 16);
592         lColorChannel[5] = '\0';
593         aoResultRGB[1] = cast(ubyte)strtoul(lColorChannel.ptr + 3, null, 16);
594         lColorChannel[3] = '\0';
595         aoResultRGB[0] = cast(ubyte)strtoul(lColorChannel.ptr + 1, null, 16);
596         /* printf("%d %d %d\n", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]); */
597     }
598     else
599     {
600         aoResultRGB[0] = 0;
601         aoResultRGB[1] = 0;
602         aoResultRGB[2] = 0;
603     }
604 }
605 
606 void RGB2Hex(const ubyte[3] aRGB, char* aoResultHexRGB)
607 {
608     if (aoResultHexRGB)
609     {
610         // NOTE: here was compiler ifdef
611         sprintf(aoResultHexRGB, "#%02hhx%02hhx%02hhx", aRGB[0], aRGB[1], aRGB[2]);
612         /* printf("aoResultHexRGB %s\n", aoResultHexRGB); */
613     }
614 }
615 
616 void replaceSubStr(const char* aSource,
617                    const char* aOldSubStr,
618                    const char* aNewSubStr,
619                    char* aoDestination)
620 {
621     const(char)* pOccurence;
622     const(char)* p;
623     const(char)* lNewSubStr = "";
624     size_t lOldSubLen = strlen(aOldSubStr);
625 
626     if (!aSource)
627     {
628         *aoDestination = '\0';
629         return;
630     }
631     if (!aOldSubStr)
632     {
633         strcpy(aoDestination, aSource);
634         return;
635     }
636     if (aNewSubStr)
637     {
638         lNewSubStr = aNewSubStr;
639     }
640     p = aSource;
641     *aoDestination = '\0';
642     while ((pOccurence = strstr(p, aOldSubStr)) !is null)
643     {
644         strncat(aoDestination, p, pOccurence - p);
645         strcat(aoDestination, lNewSubStr);
646         p = pOccurence + lOldSubLen;
647     }
648     strcat(aoDestination, p);
649 }
650 
651 bool filenameValid(const char* aFileNameWithoutPath)
652 {
653     return some(aFileNameWithoutPath) && !strpbrk(aFileNameWithoutPath, "\\/:*?\"<>|");
654 }
655 
656 void wipefile(const char* aFilename)
657 {
658     // file must exist beforehand
659     FILE* lIn = fopen(aFilename, "w");
660     if (lIn)
661     {
662         fseek(lIn, 0, SEEK_END);
663         const size = ftell(lIn);
664         rewind(lIn);
665         foreach (i; 0 .. size)
666         {
667             fputc('A', lIn);
668         }
669         fclose(lIn);
670     }
671 }
672 
673 /* source and destination can be the same or ovelap*/
674 const(char)* ensureFilesExist(char* aDestination, const char* aSourcePathsAndNames)
675 {
676     if (!some(aSourcePathsAndNames))
677         return null;
678 
679     char* lDestination = aDestination;
680     const(char)* p;
681     const(char)* p2;
682     size_t lLen = strlen(aSourcePathsAndNames);
683 
684     p = aSourcePathsAndNames;
685     while ((p2 = strchr(p, '|')) !is null)
686     {
687         lLen = p2 - p;
688         memmove(lDestination, p, lLen);
689         lDestination[lLen] = '\0';
690         if (fileExists(lDestination))
691         {
692             lDestination += lLen;
693             *lDestination = '|';
694             lDestination++;
695         }
696         p = p2 + 1;
697     }
698     if (fileExists(p))
699     {
700         lLen = strlen(p);
701         memmove(lDestination, p, lLen);
702         lDestination[lLen] = '\0';
703     }
704     else
705     {
706         *(lDestination - 1) = '\0';
707     }
708     return aDestination;
709 }
710 
711 version (Windows)
712 {
713     bool fileExists(const char* aFilePathAndName)
714     {
715         if (!some(aFilePathAndName))
716             return false;
717 
718         wchar* lTmpWChar = utf8to16(aFilePathAndName);
719         DWORD attribs = GetFileAttributes(lTmpWChar);
720         free(lTmpWChar);
721         return !(attribs & FILE_ATTRIBUTE_DEVICE) && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
722     }
723 }
724 else // unix
725 {
726     bool fileExists(const char* aFilePathAndName)
727     {
728         if (!some(aFilePathAndName))
729             return false;
730 
731         FILE* lIn = fopen(aFilePathAndName, "r");
732         if (!lIn)
733             return false;
734         fclose(lIn);
735         return true;
736     }
737 }
738 
739 version (Windows) {
740 
741 bool replaceChr(char* aString, const char aOldChr, const char aNewChr)
742 {
743     if (!aString)
744         return false;
745     if (aOldChr == aNewChr)
746         return false;
747 
748     bool ret;
749     char* p = aString;
750     while ((p = strchr(p, aOldChr)) !is null)
751     {
752         *p = aNewChr;
753         p++;
754         ret = true;
755     }
756     return ret;
757 }
758 
759 int sizeUtf16(const char* aUtf8string)
760 {
761     return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
762                                aUtf8string, -1, null, 0);
763 }
764 
765 wchar* utf8to16(const char* str)
766 {
767     if (!some(str))
768         return null;
769 
770     int lSize = sizeUtf16(str);
771     wchar* ret = cast(wchar*)malloc(lSize * wchar.sizeof);
772     lSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
773                                 str, -1, ret, lSize);
774     if (lSize == 0)
775     {
776         free(ret);
777         return null;
778     }
779     return ret;
780 }
781 
782 bool dirExists(const char* aDirPath)
783 {
784     if (!some(aDirPath))
785         return false;
786     size_t lDirLen = strlen(aDirPath);
787     if (lDirLen == 2 && aDirPath[1] == ':')
788         return true;
789 
790     wchar* lTmpWChar = utf8to16(aDirPath);
791     DWORD attribs = GetFileAttributes(lTmpWChar);
792     free(lTmpWChar);
793     return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
794 }
795 
796 version (TINYFD_NOLIB) {
797 
798 void _beep()
799 {
800     printf("\a");
801 }
802 
803 } else { // TINYFD_NOLIB
804 
805 void _beep()
806 {
807     Beep(440, 300);
808 }
809 
810 void wipefileW(const wchar* aFilename)
811 {
812     // file must exist beforehand
813     FILE* lIn = _wfopen(aFilename, "w");
814     if (lIn)
815     {
816         fseek(lIn, 0, SEEK_END);
817         const size = ftell(lIn);
818         rewind(lIn);
819         foreach (i; 0 .. size)
820         {
821             fputc('A', lIn);
822         }
823         fclose(lIn);
824     }
825 }
826 
827 wchar* getPathWithoutFinalSlashW(
828     wchar* aoDestination, /* make sure it is allocated, use _MAX_PATH */
829     const wchar* aSource) /* aoDestination and aSource can be the same */
830 {
831     const(wchar)* lTmp;
832     if (aSource)
833     {
834         lTmp = wcsrchr(aSource, '/');
835         if (!lTmp)
836         {
837             lTmp = wcsrchr(aSource, '\\');
838         }
839         if (lTmp)
840         {
841             wcsncpy(aoDestination, aSource, lTmp - aSource);
842             aoDestination[lTmp - aSource] = '\0';
843         }
844         else
845         {
846             *aoDestination = '\0';
847         }
848     }
849     else
850     {
851         *aoDestination = '\0';
852     }
853     return aoDestination;
854 }
855 
856 wchar* getLastNameW(
857     wchar* aoDestination, /* make sure it is allocated */
858     const wchar* aSource)
859 {
860     /* copy the last name after '/' or '\' */
861     const(wchar)* lTmp;
862     if (aSource)
863     {
864         lTmp = wcsrchr(aSource, '/');
865         if (!lTmp)
866         {
867             lTmp = wcsrchr(aSource, '\\');
868         }
869         if (lTmp)
870         {
871             wcscpy(aoDestination, lTmp + 1);
872         }
873         else
874         {
875             wcscpy(aoDestination, aSource);
876         }
877     }
878     else
879     {
880         *aoDestination = '\0';
881     }
882     return aoDestination;
883 }
884 
885 static if (!is(WC_ERR_INVALID_CHARS))
886 /* undefined prior to Vista, so not yet in MINGW header file */
887 enum DWORD WC_ERR_INVALID_CHARS = 0x00000080;
888 
889 int sizeUtf8(const wchar* aUtf16string)
890 {
891     return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
892                                aUtf16string, -1, null, 0, null, null);
893 }
894 
895 char* utf16to8(const wchar* str)
896 {
897     if (!wsome(str))
898         return null;
899 
900     int lSize = sizeUtf8(str);
901     char* ret = cast(char*)malloc(lSize);
902     lSize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
903                                 str, -1, ret, lSize, null, null);
904     if (lSize == 0)
905     {
906         free(ret);
907         return null;
908     }
909     return ret;
910 }
911 
912 bool replaceWchar(wchar* aString, const wchar aOldChr, const wchar aNewChr)
913 {
914     if (!aString)
915         return false;
916     if (aOldChr == aNewChr)
917         return false;
918 
919     bool ret;
920     wchar* p = aString;
921     while ((p = wcsrchr(p, aOldChr)) !is null)
922     {
923         *p = aNewChr;
924         version (TINYFD_NOCCSUNICODE)
925             p++;
926         p++;
927         ret = true;
928     }
929     return ret;
930 }
931 
932 bool willBeGui()
933 {
934     return (!tinyfd_forceConsole || !(GetConsoleWindow() || dialogPresent())) &&
935            (!getenv("SSH_CLIENT") || getenv("DISPLAY"));
936 }
937 
938 extern (Windows) int EnumThreadWndProc(HWND hwnd, LPARAM lParam)
939 {
940     wchar[MAX_PATH] lTitleName = '\0';
941     GetWindowTextW(hwnd, lTitleName.ptr, MAX_PATH);
942     /* wprintf("lTitleName %ls \n", lTitleName.ptr);  */
943     if (wcscmp("tinyfiledialogsTopWindow", lTitleName.ptr) == 0)
944     {
945         SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
946         return 0;
947     }
948     return 1;
949 }
950 
951 void hiddenConsoleW(const wchar* aString, const wchar* aDialogTitle, const int aInFront)
952 {
953     if (!wsome(aString))
954         return;
955 
956     STARTUPINFOW StartupInfo;
957     PROCESS_INFORMATION ProcessInfo;
958 
959     StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
960     StartupInfo.wShowWindow = SW_HIDE;
961 
962     if (!CreateProcessW(null, cast(LPWSTR)aString, null, null, FALSE,
963                         CREATE_NEW_CONSOLE, null, null,
964                         &StartupInfo, &ProcessInfo))
965     {
966         return; /* GetLastError(); */
967     }
968 
969     WaitForInputIdle(ProcessInfo.hProcess, INFINITE);
970     if (aInFront)
971     {
972         while (EnumWindows(&EnumThreadWndProc, 0))
973         {
974         }
975         SetWindowTextW(GetForegroundWindow(), aDialogTitle);
976     }
977     WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
978     CloseHandle(ProcessInfo.hThread);
979     CloseHandle(ProcessInfo.hProcess);
980 }
981 
982 int messageBoxWinGui(
983     const char* aTitle,
984     const char* aMessage,
985     const char* aDialogType,
986     const char* aIconType,
987     const int aDefaultButton)
988 {
989     wchar* lTitle = utf8to16(aTitle);
990     wchar* lMessage = utf8to16(aMessage);
991 
992     UINT aCode;
993 
994     if (eq("warning", aIconType))
995     {
996         aCode = MB_ICONWARNING;
997     }
998     else if (eq("error", aIconType))
999     {
1000         aCode = MB_ICONERROR;
1001     }
1002     else if (eq("question", aIconType))
1003     {
1004         aCode = MB_ICONQUESTION;
1005     }
1006     else
1007     {
1008         aCode = MB_ICONINFORMATION;
1009     }
1010 
1011     if (eq("okcancel", aDialogType))
1012     {
1013         aCode += MB_OKCANCEL;
1014         if (!aDefaultButton)
1015         {
1016             aCode += MB_DEFBUTTON2;
1017         }
1018     }
1019     else if (eq("yesno", aDialogType))
1020     {
1021         aCode += MB_YESNO;
1022         if (!aDefaultButton)
1023         {
1024             aCode += MB_DEFBUTTON2;
1025         }
1026     }
1027     else
1028     {
1029         aCode += MB_OK;
1030     }
1031 
1032     aCode += MB_TOPMOST;
1033 
1034     int lBoxReturnValue = MessageBoxW(GetForegroundWindow(), lMessage, lTitle, aCode);
1035     free(lTitle);
1036     free(lMessage);
1037 
1038     if (eq("ok", aDialogType) || lBoxReturnValue == IDOK || lBoxReturnValue == IDYES)
1039         return 1;
1040     else
1041         return 0;
1042 }
1043 
1044 int notifyWinGui(
1045     const char* aTitle,
1046     const char* aMessage,
1047     const char* aIconType)
1048 {
1049     wchar* lTitle = utf8to16(aTitle);
1050     wchar* lMessage = utf8to16(aMessage);
1051     wchar* lIconType = utf8to16(aIconType);
1052 
1053     size_t lTitleLen = lTitle ? wcslen(lTitle) : 0;
1054     size_t lMessageLen = lMessage ? wcslen(lMessage) : 0;
1055     size_t lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen;
1056     wchar* str = cast(wchar*)malloc(lDialogStringLen * wchar.sizeof);
1057 
1058     wcscpy(str, "powershell.exe -command \"" ~
1059 "function Show-BalloonTip {" ~
1060 "[cmdletbinding()] " ~
1061 "param( " ~
1062 "[string]$Title = ' ', " ~
1063 "[string]$Message = ' ', " ~
1064 "[ValidateSet('info', 'warning', 'error')] " ~
1065 "[string]$IconType = 'info');" ~
1066 "[system.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null ; " ~
1067 "$balloon = New-Object System.Windows.Forms.NotifyIcon ; " ~
1068 "$path = Get-Process -id $pid | Select-Object -ExpandProperty Path ; " ~
1069 "$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) ;");
1070 
1071     wcscat(str,
1072 "$balloon.Icon = $icon ; " ~
1073 "$balloon.BalloonTipIcon = $IconType ; " ~
1074 "$balloon.BalloonTipText = $Message ; " ~
1075 "$balloon.BalloonTipTitle = $Title ; " ~
1076 "$balloon.Text = 'lalala' ; " ~
1077 "$balloon.Visible = $true ; " ~
1078 "$balloon.ShowBalloonTip(5000)};" ~
1079 "Show-BalloonTip");
1080 
1081     if (wsome(lTitle))
1082     {
1083         wcscat(str, " -Title '");
1084         wcscat(str, lTitle);
1085         wcscat(str, "'");
1086     }
1087     if (wsome(lMessage))
1088     {
1089         wcscat(str, " -Message '");
1090         wcscat(str, lMessage);
1091         wcscat(str, "'");
1092     }
1093     if (wsome(lIconType))
1094     {
1095         wcscat(str, " -IconType '");
1096         wcscat(str, lIconType);
1097         wcscat(str, "'");
1098     }
1099     wcscat(str, "\"");
1100 
1101     /* wprintf ( "str: %ls\n" , str ) ; */
1102 
1103     hiddenConsoleW(str, lTitle, 0);
1104 
1105     free(str);
1106     free(lTitle);
1107     free(lMessage);
1108     free(lIconType);
1109     return 1;
1110 }
1111 
1112 const(wchar)* _inputBoxW(
1113     const wchar* aTitle,
1114     const wchar* aMessage,
1115     const wchar* aDefaultInput)
1116 {
1117     static wchar[MAX_PATH_OR_CMD] lBuff = '\0';
1118 
1119     size_t lTitleLen = aTitle ? wcslen(aTitle) : 0;
1120     size_t lMessageLen = aMessage ? wcslen(aMessage) : 0;
1121     size_t lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen;
1122     wchar* str = cast(wchar*)malloc(lDialogStringLen * wchar.sizeof);
1123 
1124     wcscpy(str, _wgetenv("USERPROFILE"));
1125     wcscat(str, "\\AppData\\Local\\Temp\\tinyfd.");
1126     wcscat(str, aDefaultInput ? "vbs" : "hta");
1127 
1128     FILE* lIn = _wfopen(str, "w");
1129     if (!lIn)
1130     {
1131         free(str);
1132         return null;
1133     }
1134 
1135     if (aDefaultInput)
1136     {
1137         wcscpy(str, "Dim result:result=InputBox(\"");
1138         if (wsome(aMessage))
1139         {
1140             wcscpy(lBuff.ptr, aMessage);
1141             replaceWchar(lBuff.ptr, '\n', ' ');
1142             wcscat(str, lBuff.ptr);
1143         }
1144         wcscat(str, "\",\"tinyfiledialogsTopWindow\",\"");
1145         if (wsome(aDefaultInput))
1146         {
1147             wcscpy(lBuff.ptr, aDefaultInput);
1148             replaceWchar(lBuff.ptr, '\n', ' ');
1149             wcscat(str, lBuff.ptr);
1150         }
1151         wcscat(str, "\"):If IsEmpty(result) then:WScript.Echo 0");
1152         wcscat(str, ":Else: WScript.Echo \"1\" & result : End If");
1153     }
1154     else
1155     {
1156         wcscpy(str, `
1157 <html>
1158 <head>
1159 <title>`);
1160 
1161         wcscat(str, "tinyfiledialogsTopWindow");
1162         wcscat(str, `</title>
1163 <HTA:APPLICATION
1164 ID = 'tinyfdHTA'
1165 APPLICATIONNAME = 'tinyfd_inputBox'
1166 MINIMIZEBUTTON = 'no'
1167 MAXIMIZEBUTTON = 'no'
1168 BORDER = 'dialog'
1169 SCROLL = 'no'
1170 SINGLEINSTANCE = 'yes'
1171 WINDOWSTATE = 'hidden'>
1172 
1173 <script language = 'VBScript'>
1174 
1175 intWidth = Screen.Width/4
1176 intHeight = Screen.Height/6
1177 ResizeTo intWidth, intHeight
1178 MoveTo((Screen.Width/2)-(intWidth/2)),((Screen.Height/2)-(intHeight/2))
1179 result = 0
1180 
1181 Sub Window_onLoad
1182 txt_input.Focus
1183 End Sub
1184 `);
1185 
1186         wcscat(str,
1187 `Sub Window_onUnload
1188 Set objFSO = CreateObject("Scripting.FileSystemObject")
1189 Set oShell = CreateObject("WScript.Shell")
1190 strHomeFolder = oShell.ExpandEnvironmentStrings("%USERPROFILE%")
1191 Set objFile = objFSO.CreateTextFile(strHomeFolder & "\AppData\Local\Temp\tinyfd.txt",True,True)
1192 If result = 1 Then
1193 objFile.Write 1 & txt_input.Value
1194 Else
1195 objFile.Write 0
1196 End If
1197 objFile.Close
1198 End Sub
1199 
1200 Sub Run_ProgramOK
1201 result = 1
1202 window.Close
1203 End Sub
1204 
1205 Sub Run_ProgramCancel
1206 window.Close
1207 End Sub
1208 `);
1209 
1210         wcscat(str, `Sub Default_Buttons
1211 If Window.Event.KeyCode = 13 Then
1212 btn_OK.Click
1213 ElseIf Window.Event.KeyCode = 27 Then
1214 btn_Cancel.Click
1215 End If
1216 End Sub
1217 
1218 </script>
1219 </head>
1220 <body style = 'background-color:#EEEEEE' onkeypress = 'vbs:Default_Buttons' align = 'top'>
1221 <table width = '100%' height = '80%' align = 'center' border = '0'>
1222 <tr border = '0'>
1223 <td align = 'left' valign = 'middle' style='Font-Family:Arial'>
1224 `);
1225 
1226         wcscat(str, aMessage ? aMessage : "");
1227 
1228         wcscat(str, `
1229 </td>
1230 <td align = 'right' valign = 'middle' style = 'margin-top: 0em'>
1231 <table  align = 'right' style = 'margin-right: 0em;'>
1232 <tr align = 'right' style = 'margin-top: 5em;'>
1233 <input type = 'button' value = 'OK' name = 'btn_OK' onClick = 'vbs:Run_ProgramOK' style = 'width: 5em; margin-top: 2em;'><br>
1234 <input type = 'button' value = 'Cancel' name = 'btn_Cancel' onClick = 'vbs:Run_ProgramCancel' style = 'width: 5em;'><br><br>
1235 </tr>
1236 </table>
1237 </td>
1238 </tr>
1239 </table>
1240 `);
1241 
1242         wcscat(str, `<table width = '100%' height = '100%' align = 'center' border = '0'>
1243 <tr>
1244 <td align = 'left' valign = 'top'>
1245 <input type = 'password' id = 'txt_input'
1246 name = 'txt_input' value = '' style = 'float:left;width:100%' ><BR>
1247 </td>
1248 </tr>
1249 </table>
1250 </body>
1251 </html>
1252 `);
1253     }
1254     fputws(str, lIn);
1255     fclose(lIn);
1256 
1257     if (aDefaultInput)
1258     {
1259         wcscpy(str, _wgetenv("USERPROFILE"));
1260         wcscat(str, "\\AppData\\Local\\Temp\\tinyfd.txt");
1261 
1262 version (TINYFD_NOCCSUNICODE) {
1263         FILE* lFile = _wfopen(str, "w");
1264         fputc(0xFF, lFile);
1265         fputc(0xFE, lFile);
1266 } else {
1267         FILE* lFile = _wfopen(str, "wt, ccs=UNICODE");  /*or ccs=UTF-16LE*/
1268 }
1269         fclose(lFile);
1270 
1271         wcscpy(str, "cmd.exe /c cscript.exe //U //Nologo ");
1272         wcscat(str, "\"%USERPROFILE%\\AppData\\Local\\Temp\\tinyfd.vbs\" ");
1273         wcscat(str, ">> \"%USERPROFILE%\\AppData\\Local\\Temp\\tinyfd.txt\"");
1274     }
1275     else
1276     {
1277         wcscpy(str, "cmd.exe /c mshta.exe \"%USERPROFILE%\\AppData\\Local\\Temp\\tinyfd.hta\"");
1278     }
1279 
1280     /* wprintf ( "str: %ls\n" , str ) ; */
1281 
1282     hiddenConsoleW(str, aTitle, 1);
1283 
1284     wcscpy(str, _wgetenv("USERPROFILE"));
1285     wcscat(str, "\\AppData\\Local\\Temp\\tinyfd.txt");
1286     /* wprintf("str: %ls\n", str); */
1287     version (TINYFD_NOCCSUNICODE)
1288         const wchar* mode = "r";
1289     else
1290         const wchar* mode = "rt, ccs=UNICODE"; /*or ccs=UTF-16LE*/
1291     lIn = _wfopen(str, mode);
1292     if (!lIn)
1293     {
1294         _wremove(str);
1295         free(str);
1296         return null;
1297     }
1298 
1299     lBuff = '\0';
1300 
1301 version (TINYFD_NOCCSUNICODE) {
1302     fgets(cast(char*)lBuff.ptr, 2 * MAX_PATH_OR_CMD, lIn);
1303 } else {
1304     fgetws(lBuff.ptr, MAX_PATH_OR_CMD, lIn);
1305 }
1306     fclose(lIn);
1307     wipefileW(str);
1308     _wremove(str);
1309 
1310     wcscpy(str, _wgetenv("USERPROFILE"));
1311     wcscat(str, "\\AppData\\Local\\Temp\\tinyfd.");
1312     wcscat(str, aDefaultInput ? "vbs" : "hta");
1313     _wremove(str);
1314     free(str);
1315     /* wprintf( "lBuff: %ls\n" , lBuff ) ; */
1316 version (TINYFD_NOCCSUNICODE) {
1317     int lResult = !wcsncmp(lBuff.ptr + 1, "1", 1);
1318 } else {
1319     int lResult = !wcsncmp(lBuff.ptr, "1", 1);
1320 }
1321 
1322     /* printf( "lResult: %d \n" , lResult ) ; */
1323     if (!lResult)
1324         return null;
1325 
1326     /* wprintf( "lBuff+1: %ls\n" , lBuff+1 ) ; */
1327 
1328 version (TINYFD_NOCCSUNICODE) {
1329 
1330     if (aDefaultInput)
1331     {
1332         lDialogStringLen = wcslen(lBuff.ptr);
1333         assert(lDialogStringLen >= 2);
1334         lBuff[lDialogStringLen - 1] = '\0';
1335         lBuff[lDialogStringLen - 2] = '\0';
1336     }
1337     return lBuff.ptr + 2;
1338 
1339 } else {
1340 
1341     if (aDefaultInput)
1342     {
1343         lDialogStringLen = wcslen(lBuff.ptr);
1344         assert(lDialogStringLen > 0);
1345         lBuff[lDialogStringLen - 1] = '\0';
1346     }
1347     return lBuff.ptr + 1;
1348 }
1349 }
1350 
1351 const(char)* inputBoxWinGui(
1352     char* aoBuff,
1353     const char* aTitle,
1354     const char* aMessage,
1355     const char* aDefaultInput)
1356 {
1357     wchar* lTitle = utf8to16(aTitle);
1358     wchar* lMessage = utf8to16(aMessage);
1359     wchar* lDefaultInput = utf8to16(aDefaultInput);
1360 
1361     const(wchar)* lTmpWChar = _inputBoxW(lTitle, lMessage, lDefaultInput);
1362 
1363     free(lTitle);
1364     free(lMessage);
1365     free(lDefaultInput);
1366 
1367     if (!lTmpWChar)
1368         return null;
1369 
1370     char* lTmpChar = utf16to8(lTmpWChar);
1371     strcpy(aoBuff, lTmpChar);
1372     free(lTmpChar);
1373 
1374     return aoBuff;
1375 }
1376 
1377 const(wchar)* _saveFileDialogW(
1378     const wchar* aTitle,
1379     const wchar* aDefaultPathAndFile,
1380     const int aNumOfFilterPatterns,
1381     const wchar** aFilterPatterns,
1382     const wchar* aSingleFilterDescription)
1383 {
1384     static wchar[MAX_PATH_OR_CMD] lBuff = '\0';
1385     wchar[MAX_PATH_OR_CMD] lDirname = '\0';
1386     wchar[MAX_PATH_OR_CMD] str = '\0';
1387     wchar[MAX_PATH_OR_CMD] lFilterPatterns_buf = '\0';
1388     wchar* lFilterPatterns = lFilterPatterns_buf.ptr;
1389 
1390     HRESULT lHResult = CoInitializeEx(null, 0);
1391 
1392     getPathWithoutFinalSlashW(lDirname.ptr, aDefaultPathAndFile);
1393     getLastNameW(lBuff.ptr, aDefaultPathAndFile);
1394 
1395     if (aNumOfFilterPatterns > 0)
1396     {
1397         if (wsome(aSingleFilterDescription))
1398         {
1399             wcscpy(lFilterPatterns, aSingleFilterDescription);
1400             wcscat(lFilterPatterns, "\n");
1401         }
1402         wcscat(lFilterPatterns, aFilterPatterns[0]);
1403         foreach (i; 1 .. aNumOfFilterPatterns)
1404         {
1405             wcscat(lFilterPatterns, ";");
1406             wcscat(lFilterPatterns, aFilterPatterns[i]);
1407         }
1408         wcscat(lFilterPatterns, "\n");
1409         if (!wsome(aSingleFilterDescription))
1410         {
1411             wcscpy(str.ptr, lFilterPatterns);
1412             wcscat(lFilterPatterns, str.ptr);
1413         }
1414         wcscat(lFilterPatterns, "All Files\n*.*\n");
1415         wchar* p = lFilterPatterns;
1416         while ((p = wcschr(p, '\n')) !is null)
1417         {
1418             *p = '\0';
1419             p++;
1420         }
1421     }
1422 
1423     OPENFILENAMEW ofn = {0};
1424     ofn.lStructSize = OPENFILENAMEW.sizeof;
1425     ofn.hwndOwner = GetForegroundWindow();
1426     ofn.hInstance = null;
1427     ofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : null;
1428     ofn.lpstrCustomFilter = null;
1429     ofn.nMaxCustFilter = 0;
1430     ofn.nFilterIndex = 1;
1431     ofn.lpstrFile = lBuff.ptr;
1432 
1433     ofn.nMaxFile = MAX_PATH_OR_CMD;
1434     ofn.lpstrFileTitle = null;
1435     ofn.nMaxFileTitle = MAX_PATH_OR_CMD / 2;
1436     ofn.lpstrInitialDir = wcslen(lDirname.ptr) ? lDirname.ptr : null;
1437     ofn.lpstrTitle = wsome(aTitle) ? aTitle : null;
1438     ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST;
1439     ofn.nFileOffset = 0;
1440     ofn.nFileExtension = 0;
1441     ofn.lpstrDefExt = null;
1442     ofn.lCustData = 0L;
1443     ofn.lpfnHook = null;
1444     ofn.lpTemplateName = null;
1445 
1446     wchar* lRetval;
1447     if (GetSaveFileNameW(&ofn) == 0)
1448     {
1449         lRetval = null;
1450     }
1451     else
1452     {
1453         lRetval = lBuff.ptr;
1454     }
1455 
1456     if (lHResult == S_OK || lHResult == S_FALSE)
1457     {
1458         CoUninitialize();
1459     }
1460     return lRetval;
1461 }
1462 
1463 const(char)* saveFileDialogWinGui(
1464     char* aoBuff,
1465     const char* aTitle,
1466     const char* aDefaultPathAndFile,
1467     const int aNumOfFilterPatterns,
1468     const char** aFilterPatterns,
1469     const char* aSingleFilterDescription)
1470 {
1471     wchar** lFilterPatterns = cast(wchar**)malloc(aNumOfFilterPatterns * (wchar*).sizeof);
1472     foreach (i; 0 .. aNumOfFilterPatterns)
1473     {
1474         lFilterPatterns[i] = utf8to16(aFilterPatterns[i]);
1475     }
1476 
1477     wchar* lTitle = utf8to16(aTitle);
1478     wchar* lDefaultPathAndFile = utf8to16(aDefaultPathAndFile);
1479     wchar* lSingleFilterDescription = utf8to16(aSingleFilterDescription);
1480 
1481     const(wchar)* lTmpWChar = _saveFileDialogW(
1482         lTitle,
1483         lDefaultPathAndFile,
1484         aNumOfFilterPatterns,
1485         lFilterPatterns,
1486         lSingleFilterDescription);
1487 
1488     free(lTitle);
1489     free(lDefaultPathAndFile);
1490     free(lSingleFilterDescription);
1491     foreach (i; 0 .. aNumOfFilterPatterns)
1492     {
1493         free(lFilterPatterns[i]);
1494     }
1495     free(lFilterPatterns);
1496 
1497     if (!lTmpWChar)
1498         return null;
1499 
1500     char* lTmpChar = utf16to8(lTmpWChar);
1501     strcpy(aoBuff, lTmpChar);
1502     free(lTmpChar);
1503 
1504     return aoBuff;
1505 }
1506 
1507 const(wchar)* _openFileDialogW(
1508     const wchar* aTitle,
1509     const wchar* aDefaultPathAndFile,
1510     const int aNumOfFilterPatterns,
1511     const wchar** aFilterPatterns,
1512     const wchar* aSingleFilterDescription,
1513     const bool aAllowMultipleSelects)
1514 {
1515     static wchar[MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD] lBuff = '\0';
1516 
1517     wchar[MAX_PATH_OR_CMD] lDirname = '\0';
1518     wchar[MAX_PATH_OR_CMD] str = '\0';
1519     wchar[MAX_PATH_OR_CMD] lFilterPatterns_buf = '\0';
1520     wchar* lFilterPatterns = lFilterPatterns_buf.ptr;
1521 
1522     HRESULT lHResult = CoInitializeEx(null, 0);
1523 
1524     getPathWithoutFinalSlashW(lDirname.ptr, aDefaultPathAndFile);
1525     getLastNameW(lBuff.ptr, aDefaultPathAndFile);
1526 
1527     if (aNumOfFilterPatterns > 0)
1528     {
1529         if (wsome(aSingleFilterDescription))
1530         {
1531             wcscpy(lFilterPatterns, aSingleFilterDescription);
1532             wcscat(lFilterPatterns, "\n");
1533         }
1534         wcscat(lFilterPatterns, aFilterPatterns[0]);
1535         foreach (i; 1 .. aNumOfFilterPatterns)
1536         {
1537             wcscat(lFilterPatterns, ";");
1538             wcscat(lFilterPatterns, aFilterPatterns[i]);
1539         }
1540         wcscat(lFilterPatterns, "\n");
1541         if (!wsome(aSingleFilterDescription))
1542         {
1543             wcscpy(str.ptr, lFilterPatterns);
1544             wcscat(lFilterPatterns, str.ptr);
1545         }
1546         wcscat(lFilterPatterns, "All Files\n*.*\n");
1547         wchar* p = lFilterPatterns;
1548         while ((p = wcschr(p, '\n')) !is null)
1549         {
1550             *p = '\0';
1551             p++;
1552         }
1553     }
1554 
1555     OPENFILENAMEW ofn = {0};
1556     ofn.lStructSize = OPENFILENAME.sizeof;
1557     ofn.hwndOwner = GetForegroundWindow();
1558     ofn.hInstance = null;
1559     ofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : null;
1560     ofn.lpstrCustomFilter = null;
1561     ofn.nMaxCustFilter = 0;
1562     ofn.nFilterIndex = 1;
1563     ofn.lpstrFile = lBuff.ptr;
1564     ofn.nMaxFile = MAX_PATH_OR_CMD;
1565     ofn.lpstrFileTitle = null;
1566     ofn.nMaxFileTitle = MAX_PATH_OR_CMD / 2;
1567     ofn.lpstrInitialDir = wcslen(lDirname.ptr) ? lDirname.ptr : null;
1568     ofn.lpstrTitle = wsome(aTitle) ? aTitle : null;
1569     ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
1570     ofn.nFileOffset = 0;
1571     ofn.nFileExtension = 0;
1572     ofn.lpstrDefExt = null;
1573     ofn.lCustData = 0L;
1574     ofn.lpfnHook = null;
1575     ofn.lpTemplateName = null;
1576 
1577     if (aAllowMultipleSelects)
1578     {
1579         ofn.Flags |= OFN_ALLOWMULTISELECT;
1580     }
1581 
1582     size_t[MAX_MULTIPLE_FILES] lLengths;
1583     wchar*[MAX_MULTIPLE_FILES] lPointers;
1584     wchar* lRetval, p;
1585     if (GetOpenFileNameW(&ofn) == 0)
1586     {
1587         lRetval = null;
1588     }
1589     else
1590     {
1591         size_t lBuffLen = wcslen(lBuff.ptr);
1592         lPointers[0] = lBuff.ptr + lBuffLen + 1;
1593         if (!aAllowMultipleSelects || (lPointers[0][0] == '\0'))
1594         {
1595             lRetval = lBuff.ptr;
1596         }
1597         else
1598         {
1599             int i;
1600             do
1601             {
1602                 lLengths[i] = wcslen(lPointers[i]);
1603                 lPointers[i + 1] = lPointers[i] + lLengths[i] + 1;
1604                 i++;
1605             } while (lPointers[i][0] != '\0');
1606             i--;
1607             p = lBuff.ptr + MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD - 1;
1608             *p = '\0';
1609             for (int j = i; j >= 0; j--)
1610             {
1611                 p -= lLengths[j];
1612                 memmove(p, lPointers[j], lLengths[j] * wchar.sizeof);
1613                 p--;
1614                 *p = '\\';
1615                 p -= lBuffLen;
1616                 memmove(p, lBuff.ptr, lBuffLen * wchar.sizeof);
1617                 p--;
1618                 *p = '|';
1619             }
1620             p++;
1621             lRetval = p;
1622         }
1623     }
1624 
1625     if (lHResult == S_OK || lHResult == S_FALSE)
1626     {
1627         CoUninitialize();
1628     }
1629     return lRetval;
1630 }
1631 
1632 const(char)* openFileDialogWinGui(
1633     char* aoBuff,
1634     const char* aTitle,
1635     const char* aDefaultPathAndFile,
1636     const int aNumOfFilterPatterns,
1637     const char** aFilterPatterns,
1638     const char* aSingleFilterDescription,
1639     const bool aAllowMultipleSelects)
1640 {
1641     wchar** lFilterPatterns = cast(wchar**)malloc(aNumOfFilterPatterns * (wchar*).sizeof);
1642     foreach (i; 0 .. aNumOfFilterPatterns)
1643     {
1644         lFilterPatterns[i] = utf8to16(aFilterPatterns[i]);
1645     }
1646 
1647     wchar* lTitle = utf8to16(aTitle);
1648     wchar* lDefaultPathAndFile = utf8to16(aDefaultPathAndFile);
1649     wchar* lSingleFilterDescription = utf8to16(aSingleFilterDescription);
1650 
1651     const(wchar)* lTmpWChar = _openFileDialogW(
1652         lTitle,
1653         lDefaultPathAndFile,
1654         aNumOfFilterPatterns,
1655         lFilterPatterns,
1656         lSingleFilterDescription,
1657         aAllowMultipleSelects);
1658 
1659     free(lTitle);
1660     free(lDefaultPathAndFile);
1661     free(lSingleFilterDescription);
1662     foreach (i; 0 .. aNumOfFilterPatterns)
1663     {
1664         free(lFilterPatterns[i]);
1665     }
1666     free(lFilterPatterns);
1667 
1668     if (!lTmpWChar)
1669         return null;
1670 
1671     char* lTmpChar = utf16to8(lTmpWChar);
1672     strcpy(aoBuff, lTmpChar);
1673     free(lTmpChar);
1674 
1675     return aoBuff;
1676 }
1677 
1678 version (TINYFD_SELECTFOLDERWIN) {
1679 
1680 extern (Windows) int BrowseCallbackProcW(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
1681 {
1682     if (uMsg == BFFM_INITIALIZED)
1683     {
1684         SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, cast(LPARAM)pData);
1685     }
1686     return 0;
1687 }
1688 
1689 const(char)* selectFolderDialogWinGui(
1690     char* aoBuff,
1691     const char* aTitle,
1692     const char* aDefaultPath)
1693 {
1694     static wchar[MAX_PATH_OR_CMD] lBuff = '\0';
1695 
1696     wchar* lTitle = utf8to16(aTitle);
1697     wchar* lDefaultPath = utf8to16(aDefaultPath);
1698 
1699     HRESULT lHResult = CoInitializeEx(null, COINIT_APARTMENTTHREADED);
1700 
1701     BROWSEINFOW bInfo;
1702     bInfo.hwndOwner = GetForegroundWindow();
1703     bInfo.pidlRoot = null;
1704     bInfo.pszDisplayName = lBuff.ptr;
1705     bInfo.lpszTitle = wsome(lTitle) ? lTitle : null;
1706     if (lHResult == S_OK || lHResult == S_FALSE)
1707     {
1708         bInfo.ulFlags = BIF_USENEWUI;
1709     }
1710     bInfo.lpfn = &BrowseCallbackProcW;
1711     bInfo.lParam = cast(LPARAM)lDefaultPath;
1712     bInfo.iImage = -1;
1713 
1714     LPITEMIDLIST lpItem = SHBrowseForFolderW(&bInfo);
1715     if (lpItem)
1716     {
1717         SHGetPathFromIDListW(lpItem, lBuff.ptr);
1718     }
1719 
1720     if (lHResult == S_OK || lHResult == S_FALSE)
1721     {
1722         CoUninitialize();
1723     }
1724 
1725     char* lTmpChar = utf16to8(lBuff.ptr);
1726     if (lTmpChar)
1727     {
1728         strcpy(aoBuff, lTmpChar);
1729         free(lTmpChar);
1730     }
1731     free(lTitle);
1732     free(lDefaultPath);
1733     return aoBuff;
1734 }
1735 
1736 } // TINYFD_SELECTFOLDERWIN
1737 
1738 const(char)* colorChooserWinGui(
1739     const char* aTitle,
1740     const char* aDefaultHexRGB,
1741     ref const ubyte[3] aDefaultRGB,
1742     ref ubyte[3] aoResultRGB)
1743 {
1744     static char[8] lResultHexRGB = '\0';
1745     ubyte[3] lDefaultRGB = aDefaultRGB;
1746 
1747     HRESULT lHResult = CoInitializeEx(null, 0);
1748 
1749     if (aDefaultHexRGB)
1750     {
1751         Hex2RGB(aDefaultHexRGB, lDefaultRGB);
1752     }
1753 
1754     CHOOSECOLORW cc;
1755     COLORREF[16] crCustColors;
1756     /* we can't use aTitle */
1757     cc.lStructSize = CHOOSECOLOR.sizeof;
1758     cc.hwndOwner = GetForegroundWindow();
1759     cc.hInstance = null;
1760     cc.rgbResult = RGB(lDefaultRGB[0], lDefaultRGB[1], lDefaultRGB[2]);
1761     cc.lpCustColors = crCustColors.ptr;
1762     cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR;
1763     cc.lCustData = 0;
1764     cc.lpfnHook = null;
1765     cc.lpTemplateName = null;
1766 
1767     const(char)* ret;
1768     if (ChooseColorW(&cc))
1769     {
1770         aoResultRGB[0] = GetRValue(cc.rgbResult);
1771         aoResultRGB[1] = GetGValue(cc.rgbResult);
1772         aoResultRGB[2] = GetBValue(cc.rgbResult);
1773         RGB2Hex(aoResultRGB, lResultHexRGB.ptr);
1774         ret = lResultHexRGB.ptr;
1775     }
1776 
1777     if (lHResult == S_OK || lHResult == S_FALSE)
1778     {
1779         CoUninitialize();
1780     }
1781     return ret;
1782 }
1783 
1784 } // TINYFD_NOLIB
1785 
1786 int dialogPresent()
1787 {
1788     static int ret = -1;
1789     if (ret < 0)
1790     {
1791         FILE* lIn = _popen("where dialog.exe", "r");
1792         if (!lIn)
1793         {
1794             ret = 0;
1795             return 0;
1796         }
1797 
1798         char[MAX_PATH_OR_CMD] lBuff = '\0';
1799         const char* lString = "dialog.exe";
1800         while (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
1801         {
1802         }
1803         _pclose(lIn);
1804         removeLastNL(lBuff.ptr);
1805         if (strcmp(lBuff.ptr + strlen(lBuff.ptr) - strlen(lString), lString))
1806             ret = 0;
1807         else
1808             ret = 1;
1809     }
1810     return ret;
1811 }
1812 
1813 int messageBoxWinConsole(
1814     const char* aTitle,
1815     const char* aMessage,
1816     const char* aDialogType,
1817     const char* aIconType,
1818     const int aDefaultButton)
1819 {
1820     char[MAX_PATH_OR_CMD] str_buf = '\0';
1821     char[MAX_PATH_OR_CMD] lDialogFile_buf = '\0';
1822     char* str = str_buf.ptr;
1823     char* lDialogFile = lDialogFile_buf.ptr;
1824     FILE* lIn;
1825     char[MAX_PATH_OR_CMD] lBuff = '\0';
1826 
1827     strcpy(str, "dialog ");
1828     if (some(aTitle))
1829     {
1830         strcat(str, "--title \"");
1831         strcat(str, aTitle);
1832         strcat(str, "\" ");
1833     }
1834 
1835     if (eq("okcancel", aDialogType) || eq("yesno", aDialogType) || eq("yesnocancel", aDialogType))
1836     {
1837         strcat(str, "--backtitle \"");
1838         strcat(str, "tab: move focus");
1839         strcat(str, "\" ");
1840     }
1841 
1842     if (eq("okcancel", aDialogType))
1843     {
1844         if (!aDefaultButton)
1845         {
1846             strcat(str, "--defaultno ");
1847         }
1848         strcat(str,
1849                "--yes-label \"Ok\" --no-label \"Cancel\" --yesno ");
1850     }
1851     else if (eq("yesno", aDialogType))
1852     {
1853         if (!aDefaultButton)
1854         {
1855             strcat(str, "--defaultno ");
1856         }
1857         strcat(str, "--yesno ");
1858     }
1859     else if (eq("yesnocancel", aDialogType))
1860     {
1861         if (!aDefaultButton)
1862         {
1863             strcat(str, "--defaultno ");
1864         }
1865         strcat(str, "--menu ");
1866     }
1867     else
1868     {
1869         strcat(str, "--msgbox ");
1870     }
1871 
1872     strcat(str, "\"");
1873     if (some(aMessage))
1874     {
1875         replaceSubStr(aMessage, "\n", "\\n", lBuff.ptr);
1876         strcat(str, lBuff.ptr);
1877         lBuff[0] = '\0';
1878     }
1879     strcat(str, "\" ");
1880 
1881     if (eq("yesnocancel", aDialogType))
1882     {
1883         strcat(str, "0 60 0 Yes \"\" No \"\"");
1884         strcat(str, "2>>");
1885     }
1886     else
1887     {
1888         strcat(str, "10 60");
1889         strcat(str, " && echo 1 > ");
1890     }
1891 
1892     strcpy(lDialogFile, getenv("USERPROFILE"));
1893     strcat(lDialogFile, "\\AppData\\Local\\Temp\\tinyfd.txt");
1894     strcat(str, lDialogFile);
1895 
1896     /*if (tinyfd_verbose) printf( "str: %s\n" , str ) ;*/
1897     system(str);
1898 
1899     lIn = fopen(lDialogFile, "r");
1900     if (!lIn)
1901     {
1902         remove(lDialogFile);
1903         return 0;
1904     }
1905     while (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
1906     {
1907     }
1908     fclose(lIn);
1909     remove(lDialogFile);
1910     removeLastNL(lBuff.ptr);
1911 
1912     /* if (tinyfd_verbose) printf("lBuff: %s\n", lBuff.ptr); */
1913     if (!some(lBuff.ptr))
1914     {
1915         return 0;
1916     }
1917 
1918     if (eq("yesnocancel", aDialogType))
1919     {
1920         if (lBuff[0] == 'Y')
1921             return 1;
1922         else
1923             return 2;
1924     }
1925 
1926     return 1;
1927 }
1928 
1929 const(char)* inputBoxWinConsole(
1930     char* aoBuff,
1931     const char* aTitle,
1932     const char* aMessage,
1933     const char* aDefaultInput)
1934 {
1935     char[MAX_PATH_OR_CMD] str_buf = '\0';
1936     char[MAX_PATH_OR_CMD] lDialogFile_buf = '\0';
1937     char* str = str_buf.ptr;
1938     char* lDialogFile = lDialogFile_buf.ptr;
1939     FILE* lIn;
1940     int lResult;
1941 
1942     strcpy(lDialogFile, getenv("USERPROFILE"));
1943     strcat(lDialogFile, "\\AppData\\Local\\Temp\\tinyfd.txt");
1944     strcpy(str, "echo|set /p=1 >");
1945     strcat(str, lDialogFile);
1946     strcat(str, " & ");
1947 
1948     strcat(str, "dialog ");
1949     if (some(aTitle))
1950     {
1951         strcat(str, "--title \"");
1952         strcat(str, aTitle);
1953         strcat(str, "\" ");
1954     }
1955 
1956     strcat(str, "--backtitle \"");
1957     strcat(str, "tab: move focus");
1958     if (!aDefaultInput)
1959     {
1960         strcat(str, " (sometimes nothing, no blink nor star, is shown in text field)");
1961     }
1962 
1963     strcat(str, "\" ");
1964 
1965     if (!aDefaultInput)
1966     {
1967         strcat(str, "--insecure --passwordbox");
1968     }
1969     else
1970     {
1971         strcat(str, "--inputbox");
1972     }
1973     strcat(str, " \"");
1974     if (some(aMessage))
1975     {
1976         strcat(str, aMessage);
1977     }
1978     strcat(str, "\" 10 60 ");
1979     if (some(aDefaultInput))
1980     {
1981         strcat(str, "\"");
1982         strcat(str, aDefaultInput);
1983         strcat(str, "\" ");
1984     }
1985 
1986     strcat(str, "2>>");
1987     strcpy(lDialogFile, getenv("USERPROFILE"));
1988     strcat(lDialogFile, "\\AppData\\Local\\Temp\\tinyfd.txt");
1989     strcat(str, lDialogFile);
1990     strcat(str, " || echo 0 > ");
1991     strcat(str, lDialogFile);
1992 
1993     /* printf( "str: %s\n" , str ) ; */
1994     system(str);
1995 
1996     lIn = fopen(lDialogFile, "r");
1997     if (!lIn)
1998     {
1999         remove(lDialogFile);
2000         return null;
2001     }
2002     while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) !is null)
2003     {
2004     }
2005     fclose(lIn);
2006 
2007     wipefile(lDialogFile);
2008     remove(lDialogFile);
2009     removeLastNL(aoBuff);
2010     /* printf( "aoBuff: %s\n" , aoBuff ) ; */
2011 
2012     /* printf( "aoBuff: %s len: %lu \n" , aoBuff , strlen(aoBuff) ) ; */
2013     lResult = strncmp(aoBuff, "1", 1) ? 0 : 1;
2014     /* printf( "lResult: %d \n" , lResult ) ; */
2015     if (!lResult)
2016         return null;
2017     /* printf( "aoBuff+1: %s\n" , aoBuff+1 ) ; */
2018     return aoBuff + 3;
2019 }
2020 
2021 const(char)* saveFileDialogWinConsole(
2022     char* aoBuff,
2023     const char* aTitle,
2024     const char* aDefaultPathAndFile)
2025 {
2026     char[MAX_PATH_OR_CMD] str_buf = '\0';
2027     char[MAX_PATH_OR_CMD] lPathAndFile_buf = '\0';
2028     char* str = str_buf.ptr;
2029     char* lPathAndFile = lPathAndFile_buf.ptr;
2030     FILE* lIn;
2031 
2032     strcpy(str, "dialog ");
2033     if (some(aTitle))
2034     {
2035         strcat(str, "--title \"");
2036         strcat(str, aTitle);
2037         strcat(str, "\" ");
2038     }
2039 
2040     strcat(str, "--backtitle \"");
2041     strcat(str,
2042            "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY");
2043     strcat(str, "\" ");
2044 
2045     strcat(str, "--fselect \"");
2046     if (some(aDefaultPathAndFile))
2047     {
2048         /* dialog.exe uses unix separators even on windows */
2049         strcpy(lPathAndFile, aDefaultPathAndFile);
2050         replaceChr(lPathAndFile, '\\', '/');
2051     }
2052 
2053     /* dialog.exe needs at least one separator */
2054     if (!strchr(lPathAndFile, '/'))
2055     {
2056         strcat(str, "./");
2057     }
2058     strcat(str, lPathAndFile);
2059     strcat(str, "\" 0 60 2>");
2060     strcpy(lPathAndFile, getenv("USERPROFILE"));
2061     strcat(lPathAndFile, "\\AppData\\Local\\Temp\\tinyfd.txt");
2062     strcat(str, lPathAndFile);
2063 
2064     /* printf( "str: %s\n" , str ) ; */
2065     system(str);
2066 
2067     lIn = fopen(lPathAndFile, "r");
2068     if (!lIn)
2069     {
2070         remove(lPathAndFile);
2071         return null;
2072     }
2073     while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) !is null)
2074     {
2075     }
2076     fclose(lIn);
2077     remove(lPathAndFile);
2078     replaceChr(aoBuff, '/', '\\');
2079     /* printf( "aoBuff: %s\n" , aoBuff ) ; */
2080     getLastName(str, aoBuff);
2081     if (!some(str))
2082         return null;
2083     return aoBuff;
2084 }
2085 
2086 const(char)* openFileDialogWinConsole(
2087     char* aoBuff,
2088     const char* aTitle,
2089     const char* aDefaultPathAndFile,
2090     const bool aAllowMultipleSelects)
2091 {
2092     char[MAX_PATH_OR_CMD] lFilterPatterns = '\0';
2093     char[MAX_PATH_OR_CMD] str_buf = '\0';
2094     char* str = str_buf.ptr;
2095     FILE* lIn;
2096 
2097     strcpy(str, "dialog ");
2098     if (some(aTitle))
2099     {
2100         strcat(str, "--title \"");
2101         strcat(str, aTitle);
2102         strcat(str, "\" ");
2103     }
2104 
2105     strcat(str, "--backtitle \"");
2106     strcat(str,
2107            "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY");
2108     strcat(str, "\" ");
2109 
2110     strcat(str, "--fselect \"");
2111     if (some(aDefaultPathAndFile))
2112     {
2113         /* dialog.exe uses unix separators even on windows */
2114         strcpy(lFilterPatterns.ptr, aDefaultPathAndFile);
2115         replaceChr(lFilterPatterns.ptr, '\\', '/');
2116     }
2117 
2118     /* dialog.exe needs at least one separator */
2119     if (!strchr(lFilterPatterns.ptr, '/'))
2120     {
2121         strcat(str, "./");
2122     }
2123     strcat(str, lFilterPatterns.ptr);
2124     strcat(str, "\" 0 60 2>");
2125     strcpy(lFilterPatterns.ptr, getenv("USERPROFILE"));
2126     strcat(lFilterPatterns.ptr, "\\AppData\\Local\\Temp\\tinyfd.txt");
2127     strcat(str, lFilterPatterns.ptr);
2128 
2129     /* printf( "str: %s\n" , str ) ; */
2130     system(str);
2131 
2132     lIn = fopen(lFilterPatterns.ptr, "r");
2133     if (!lIn)
2134     {
2135         remove(lFilterPatterns.ptr);
2136         return null;
2137     }
2138     while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) !is null)
2139     {
2140     }
2141     fclose(lIn);
2142     remove(lFilterPatterns.ptr);
2143     replaceChr(aoBuff, '/', '\\');
2144     /* printf( "aoBuff: %s\n" , aoBuff ) ; */
2145     return aoBuff;
2146 }
2147 
2148 const(char)* selectFolderDialogWinConsole(
2149     char* aoBuff,
2150     const char* aTitle,
2151     const char* aDefaultPath)
2152 {
2153     char[MAX_PATH_OR_CMD] str_buf = '\0';
2154     char[MAX_PATH_OR_CMD] lString_buf = '\0';
2155     char* str = str_buf.ptr;
2156     char* lString = lString_buf.ptr;
2157     FILE* lIn;
2158 
2159     strcpy(str, "dialog ");
2160     if (some(aTitle))
2161     {
2162         strcat(str, "--title \"");
2163         strcat(str, aTitle);
2164         strcat(str, "\" ");
2165     }
2166 
2167     strcat(str, "--backtitle \"");
2168     strcat(str,
2169            "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY");
2170     strcat(str, "\" ");
2171 
2172     strcat(str, "--dselect \"");
2173     if (some(aDefaultPath))
2174     {
2175         /* dialog.exe uses unix separators even on windows */
2176         strcpy(lString, aDefaultPath);
2177         ensureFinalSlash(lString);
2178         replaceChr(lString, '\\', '/');
2179         strcat(str, lString);
2180     }
2181     else
2182     {
2183         /* dialog.exe needs at least one separator */
2184         strcat(str, "./");
2185     }
2186     strcat(str, "\" 0 60 2>");
2187     strcpy(lString, getenv("USERPROFILE"));
2188     strcat(lString, "\\AppData\\Local\\Temp\\tinyfd.txt");
2189     strcat(str, lString);
2190 
2191     /* printf( "str: %s\n" , str ) ; */
2192     system(str);
2193 
2194     lIn = fopen(lString, "r");
2195     if (!lIn)
2196     {
2197         remove(lString);
2198         return null;
2199     }
2200     while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) !is null)
2201     {
2202     }
2203     fclose(lIn);
2204     remove(lString);
2205     replaceChr(aoBuff, '/', '\\');
2206     /* printf( "aoBuff: %s\n" , aoBuff ) ; */
2207     return aoBuff;
2208 }
2209 
2210 int _messageBox(
2211     const char* aTitle,
2212     const char* aMessage,
2213     const char* aDialogType,
2214     const char* aIconType,
2215     int aDefaultButton)
2216 {
2217     char lChar;
2218     const bool lQuery = eq(aTitle, "tinyfd_query");
2219 
2220     version (TINYFD_LIB) {
2221 
2222     if (willBeGui())
2223     {
2224         if (lQuery)
2225         {
2226             response("windows");
2227             return 1;
2228         }
2229         return messageBoxWinGui(
2230             aTitle, aMessage, aDialogType, aIconType, aDefaultButton);
2231     }
2232 }
2233     if (dialogPresent())
2234     {
2235         if (lQuery)
2236         {
2237             response("dialog");
2238             return 0;
2239         }
2240         return messageBoxWinConsole(
2241             aTitle, aMessage, aDialogType, aIconType, aDefaultButton);
2242     }
2243     else
2244     {
2245         if (lQuery)
2246         {
2247             response("basicinput");
2248             return 0;
2249         }
2250         if (!gWarningDisplayed && !tinyfd_forceConsole)
2251         {
2252             gWarningDisplayed = true;
2253             printf("\n\n%s\n", gTitle.ptr);
2254             printf("%s\n\n", tinyfd_needs.ptr);
2255         }
2256         if (some(aTitle))
2257         {
2258             printf("\n%s\n\n", aTitle);
2259         }
2260         if (eq("yesno", aDialogType))
2261         {
2262             do
2263             {
2264                 if (some(aMessage))
2265                 {
2266                     printf("%s\n", aMessage);
2267                 }
2268                 printf("y/n: ");
2269                 lChar = cast(char)tolower(_getch());
2270                 printf("\n\n");
2271             } while (lChar != 'y' && lChar != 'n');
2272             return lChar == 'y' ? 1 : 0;
2273         }
2274         else if (eq("okcancel", aDialogType))
2275         {
2276             do
2277             {
2278                 if (some(aMessage))
2279                 {
2280                     printf("%s\n", aMessage);
2281                 }
2282                 printf("[O]kay/[C]ancel: ");
2283                 lChar = cast(char)tolower(_getch());
2284                 printf("\n\n");
2285             } while (lChar != 'o' && lChar != 'c');
2286             return lChar == 'o' ? 1 : 0;
2287         }
2288         else if (eq("yesnocancel", aDialogType))
2289         {
2290             do
2291             {
2292                 if (some(aMessage))
2293                 {
2294                     printf("%s\n", aMessage);
2295                 }
2296                 printf("[Y]es/[N]o/[C]ancel: ");
2297                 lChar = cast(char)tolower(_getch());
2298                 printf("\n\n");
2299             } while (lChar != 'y' && lChar != 'n' && lChar != 'c');
2300             return (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0;
2301         }
2302         else
2303         {
2304             if (some(aMessage))
2305             {
2306                 printf("%s\n\n", aMessage);
2307             }
2308             printf("press enter to continue ");
2309             lChar = cast(char)_getch();
2310             printf("\n\n");
2311             return 1;
2312         }
2313     }
2314 }
2315 
2316 int _notifyPopup(
2317     const char* aTitle,
2318     const char* aMessage,
2319     const char* aIconType)
2320 {
2321     const bool lQuery = eq(aTitle, "tinyfd_query");
2322 
2323     version (TINYFD_LIB) {
2324 
2325     if (willBeGui())
2326     {
2327         if (lQuery)
2328         {
2329             response("windows");
2330             return 1;
2331         }
2332         return notifyWinGui(aTitle, aMessage, aIconType);
2333     }
2334 }
2335     return _messageBox(aTitle, aMessage, "ok", aIconType, 0);
2336 }
2337 
2338 const(char*) _inputBox(
2339     const char* aTitle,
2340     const char* aMessage,
2341     const char* aDefaultInput)
2342 {
2343     static char[MAX_PATH_OR_CMD] lBuff = '\0';
2344     char* lEOF;
2345     const bool lQuery = eq(aTitle, "tinyfd_query");
2346 
2347     version (TINYFD_LIB) {
2348 
2349     DWORD mode = 0;
2350     HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
2351 
2352     if (willBeGui())
2353     {
2354         if (lQuery)
2355         {
2356             response("windows");
2357             return cast(const(char)*)1;
2358         }
2359         lBuff[0] = '\0';
2360         return inputBoxWinGui(lBuff.ptr, aTitle, aMessage, aDefaultInput);
2361     }
2362 }
2363     if (dialogPresent())
2364     {
2365         if (lQuery)
2366         {
2367             response("dialog");
2368             return cast(const(char)*)0;
2369         }
2370         lBuff[0] = '\0';
2371         return inputBoxWinConsole(lBuff.ptr, aTitle, aMessage, aDefaultInput);
2372     }
2373     else
2374     {
2375         if (lQuery)
2376         {
2377             response("basicinput");
2378             return cast(const(char)*)0;
2379         }
2380         lBuff[0] = '\0';
2381         if (!gWarningDisplayed && !tinyfd_forceConsole)
2382         {
2383             gWarningDisplayed = true;
2384             printf("\n\n%s\n", gTitle.ptr);
2385             printf("%s\n\n", tinyfd_needs.ptr);
2386         }
2387         if (some(aTitle))
2388         {
2389             printf("\n%s\n\n", aTitle);
2390         }
2391         if (some(aMessage))
2392         {
2393             printf("%s\n", aMessage);
2394         }
2395         printf("(ctrl-Z + enter to cancel): ");
2396         version (TINYFD_LIB)
2397         {
2398             if (!aDefaultInput)
2399             {
2400                 GetConsoleMode(hStdin, &mode);
2401                 SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT));
2402             }
2403         }
2404 
2405         lEOF = fgets(lBuff.ptr, lBuff.sizeof, stdin);
2406         if (!lEOF)
2407             return null;
2408 
2409         version (TINYFD_LIB)
2410         {
2411             if (!aDefaultInput)
2412             {
2413                 SetConsoleMode(hStdin, mode);
2414                 printf("\n");
2415             }
2416         }
2417         printf("\n");
2418         if (strchr(lBuff.ptr, 27))
2419             return null;
2420         removeLastNL(lBuff.ptr);
2421         return lBuff.ptr;
2422     }
2423 }
2424 
2425 const(char*) _saveFileDialog(
2426     const char* aTitle,
2427     const char* aDefaultPathAndFile,
2428     const int aNumOfFilterPatterns,
2429     const char** aFilterPatterns,
2430     const char* aSingleFilterDescription)
2431 {
2432     static char[MAX_PATH_OR_CMD] lBuff = '\0';
2433     const bool lQuery = eq(aTitle, "tinyfd_query");
2434     char[MAX_PATH_OR_CMD] lString = '\0';
2435     const(char)* p;
2436     lBuff[0] = '\0';
2437 
2438     version (TINYFD_LIB) {
2439 
2440     if (willBeGui())
2441     {
2442         if (lQuery)
2443         {
2444             response("windows");
2445             return cast(const(char)*)1;
2446         }
2447         p = saveFileDialogWinGui(lBuff.ptr, aTitle, aDefaultPathAndFile, aNumOfFilterPatterns,
2448                                  aFilterPatterns, aSingleFilterDescription);
2449         goto end;
2450     }
2451 }
2452     if (dialogPresent())
2453     {
2454         if (lQuery)
2455         {
2456             response("dialog");
2457             return cast(const(char)*)0;
2458         }
2459         p = saveFileDialogWinConsole(lBuff.ptr, aTitle, aDefaultPathAndFile);
2460     }
2461     else
2462     {
2463         if (lQuery)
2464         {
2465             response("basicinput");
2466             return cast(const(char)*)0;
2467         }
2468         p = _inputBox(aTitle, "Save file", "");
2469     }
2470 
2471     end:
2472 
2473     if (!some(p))
2474         return null;
2475     getPathWithoutFinalSlash(lString.ptr, p);
2476     if (!dirExists(lString.ptr))
2477         return null;
2478     getLastName(lString.ptr, p);
2479     if (!filenameValid(lString.ptr))
2480         return null;
2481     return p;
2482 }
2483 
2484 const(char*) _openFileDialog(
2485     const char* aTitle,
2486     const char* aDefaultPathAndFile,
2487     const int aNumOfFilterPatterns,
2488     const char** aFilterPatterns,
2489     const char* aSingleFilterDescription,
2490     const bool aAllowMultipleSelects)
2491 {
2492     static char[MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD] lBuff = '\0';
2493     const bool lQuery = eq(aTitle, "tinyfd_query");
2494     const(char)* p;
2495 
2496     version (TINYFD_LIB) {
2497 
2498     if (willBeGui())
2499     {
2500         if (lQuery)
2501         {
2502             response("windows");
2503             return cast(const(char)*)1;
2504         }
2505         p = openFileDialogWinGui(lBuff.ptr, aTitle, aDefaultPathAndFile, aNumOfFilterPatterns,
2506                                  aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);
2507         goto end;
2508     }
2509 }
2510     if (dialogPresent())
2511     {
2512         if (lQuery)
2513         {
2514             response("dialog");
2515             return cast(const(char)*)0;
2516         }
2517         p = openFileDialogWinConsole(lBuff.ptr,
2518                                      aTitle, aDefaultPathAndFile, aAllowMultipleSelects);
2519     }
2520     else
2521     {
2522         if (lQuery)
2523         {
2524             response("basicinput");
2525             return cast(const(char)*)0;
2526         }
2527         p = _inputBox(aTitle, "Open file", "");
2528     }
2529 
2530     end:
2531 
2532     if (!some(p))
2533         return null;
2534     if (aAllowMultipleSelects && strchr(p, '|'))
2535         p = ensureFilesExist(lBuff.ptr, p);
2536     else if (!fileExists(p))
2537         return null;
2538     return p;
2539 }
2540 
2541 const(char*) _selectFolderDialog(const char* aTitle, const char* aDefaultPath)
2542 {
2543     static char[MAX_PATH_OR_CMD] lBuff = '\0';
2544     const bool lQuery = eq(aTitle, "tinyfd_query");
2545     const(char)* p;
2546 
2547     version (TINYFD_LIB) {
2548 
2549     if (willBeGui())
2550     {
2551         if (lQuery)
2552         {
2553             response("windows");
2554             return cast(const(char)*)1;
2555         }
2556         version (TINYFD_SELECTFOLDERWIN)
2557         {
2558             p = selectFolderDialogWinGui(lBuff.ptr, aTitle, aDefaultPath);
2559             return dirExists(p) ? p : null;
2560         }
2561     }
2562 }
2563     if (dialogPresent())
2564     {
2565         if (lQuery)
2566         {
2567             response("dialog");
2568             return cast(const(char)*)0;
2569         }
2570         p = selectFolderDialogWinConsole(lBuff.ptr, aTitle, aDefaultPath);
2571     }
2572     else
2573     {
2574         if (lQuery)
2575         {
2576             response("basicinput");
2577             return cast(const(char)*)0;
2578         }
2579         p = _inputBox(aTitle, "Select folder", "");
2580     }
2581     return dirExists(p) ? p : null;
2582 }
2583 
2584 const(char*) _colorChooser(
2585     const char* aTitle,
2586     const char* aDefaultHexRGB,
2587     ref const ubyte[3] aDefaultRGB,
2588     ref ubyte[3] aoResultRGB)
2589 {
2590     const bool lQuery = eq(aTitle, "tinyfd_query");
2591     char[8] lDefaultHexRGB = '\0';
2592     char* lpDefaultHexRGB;
2593     const(char)* p;
2594 
2595     version (TINYFD_LIB) {
2596 
2597     if (willBeGui())
2598     {
2599         if (lQuery)
2600         {
2601             response("windows");
2602             return cast(const(char)*)1;
2603         }
2604         return colorChooserWinGui(aTitle, aDefaultHexRGB, aDefaultRGB, aoResultRGB);
2605     }
2606 }
2607     if (aDefaultHexRGB)
2608     {
2609         lpDefaultHexRGB = cast(char*)aDefaultHexRGB;
2610     }
2611     else
2612     {
2613         RGB2Hex(aDefaultRGB, lDefaultHexRGB.ptr);
2614         lpDefaultHexRGB = cast(char*)lDefaultHexRGB;
2615     }
2616     p = _inputBox(aTitle, "Enter hex rgb color (i.e. #f5ca20)", lpDefaultHexRGB);
2617     if (lQuery)
2618         return p;
2619 
2620     if (!p || strlen(p) != 7 || p[0] != '#')
2621         return null;
2622 
2623     foreach (i; 1 .. 7)
2624     {
2625         if (!isxdigit(p[i]))
2626             return null;
2627     }
2628     Hex2RGB(p, aoResultRGB);
2629     return p;
2630 }
2631 
2632 } else { // unix
2633 
2634 char[16] gPython2Name = '\0';
2635 char[16] gPython3Name = '\0';
2636 char[16] gPythonName = '\0';
2637 
2638 int isDarwin()
2639 {
2640     static int ret = -1;
2641     utsname lUtsname;
2642     if (ret < 0)
2643     {
2644         ret = !uname(&lUtsname) && eq(lUtsname.sysname.ptr, "Darwin");
2645     }
2646     return ret;
2647 }
2648 
2649 bool dirExists(const char* aDirPath)
2650 {
2651     if (!some(aDirPath))
2652         return false;
2653     DIR* lDir = opendir(aDirPath);
2654     if (!lDir)
2655         return false;
2656     closedir(lDir);
2657     return true;
2658 }
2659 
2660 bool detectPresence(const char* aExecutable)
2661 {
2662     char[MAX_PATH_OR_CMD] lBuff = '\0';
2663     char[MAX_PATH_OR_CMD] lTestedString = "which ";
2664 
2665     strcat(lTestedString.ptr, aExecutable);
2666     strcat(lTestedString.ptr, " 2>/dev/null ");
2667     FILE* lIn = popen(lTestedString.ptr, "r");
2668     if (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null && !strchr(lBuff.ptr, ':') && strncmp(lBuff.ptr, "no ", 3))
2669     {
2670         // present
2671         pclose(lIn);
2672         if (tinyfd_verbose)
2673             printf("detectPresence %s %d\n", aExecutable, 1);
2674         return true;
2675     }
2676     else
2677     {
2678         pclose(lIn);
2679         if (tinyfd_verbose)
2680             printf("detectPresence %s %d\n", aExecutable, 0);
2681         return false;
2682     }
2683 }
2684 
2685 const(char)* getVersion(const char* aExecutable) /*version must be first numeral*/
2686 {
2687     static char[MAX_PATH_OR_CMD] lBuff = '\0';
2688     char[MAX_PATH_OR_CMD] lTestedString = '\0';
2689     FILE* lIn;
2690     char* lTmp;
2691 
2692     strcpy(lTestedString.ptr, aExecutable);
2693     strcat(lTestedString.ptr, " --version");
2694 
2695     lIn = popen(lTestedString.ptr, "r");
2696     lTmp = fgets(lBuff.ptr, lBuff.sizeof, lIn);
2697     pclose(lIn);
2698 
2699     lTmp += strcspn(lTmp, "0123456789");
2700     /* printf("lTmp:%s\n", lTmp); */
2701     return lTmp;
2702 }
2703 
2704 const(int)* getMajorMinorPatch(const char* aExecutable)
2705 {
2706     static int[3] lArray;
2707     char* lTmp;
2708 
2709     lTmp = cast(char*)getVersion(aExecutable);
2710     lArray[0] = atoi(strtok(lTmp, " ,.-"));
2711     /* printf("lArray0 %d\n", lArray[0]); */
2712     lArray[1] = atoi(strtok(null, " ,.-"));
2713     /* printf("lArray1 %d\n", lArray[1]); */
2714     lArray[2] = atoi(strtok(null, " ,.-"));
2715     /* printf("lArray2 %d\n", lArray[2]); */
2716 
2717     if (!lArray[0] && !lArray[1] && !lArray[2])
2718         return null;
2719     return lArray.ptr;
2720 }
2721 
2722 bool tryCommand(const char* aCommand)
2723 {
2724     char[MAX_PATH_OR_CMD] lBuff = '\0';
2725     FILE* lIn = popen(aCommand, "r");
2726     const bool present = fgets(lBuff.ptr, lBuff.sizeof, lIn) is null;
2727     pclose(lIn);
2728     return present;
2729 }
2730 
2731 int isTerminalRunning()
2732 {
2733     static int ret = -1;
2734     if (ret < 0)
2735     {
2736         ret = isatty(1);
2737         if (tinyfd_verbose)
2738             printf("isTerminalRunning %d\n", ret);
2739     }
2740     return ret;
2741 }
2742 
2743 const(char)* dialogNameOnly()
2744 {
2745     static char[128] ret = "*";
2746     if (ret[0] == '*')
2747     {
2748         if (isDarwin() && strcpy(ret.ptr, "/opt/local/bin/dialog") && detectPresence(ret.ptr))
2749         {
2750         }
2751         else if (strcpy(ret.ptr, "dialog") && detectPresence(ret.ptr))
2752         {
2753         }
2754         else
2755         {
2756             strcpy(ret.ptr, "");
2757         }
2758     }
2759     return ret.ptr;
2760 }
2761 
2762 bool isDialogVersionBetter09b()
2763 {
2764     const(char)* lDialogName;
2765     char* lVersion;
2766     int lMajor;
2767     int lMinor;
2768     int lDate;
2769     int lResult;
2770     char* lMinorP;
2771     char* lLetter;
2772     char[128] lBuff = '\0';
2773 
2774     /*char[128] lTest = " 0.9b-20031126" ;*/
2775 
2776     lDialogName = dialogNameOnly();
2777     lVersion = cast(char*)getVersion(lDialogName);
2778     if (!some(lDialogName) || !lVersion)
2779         return false;
2780     /*lVersion = lTest ;*/
2781     /*printf("lVersion %s\n", lVersion);*/
2782     strcpy(lBuff.ptr, lVersion);
2783     lMajor = atoi(strtok(lVersion, " ,.-"));
2784     /*printf("lMajor %d\n", lMajor);*/
2785     lMinorP = strtok(null, " ,.-abcdefghijklmnopqrstuvxyz");
2786     lMinor = atoi(lMinorP);
2787     /*printf("lMinor %d\n", lMinor );*/
2788     lDate = atoi(strtok(null, " ,.-"));
2789     if (lDate < 0)
2790         lDate = -lDate;
2791     /*printf("lDate %d\n", lDate);*/
2792     lLetter = lMinorP + strlen(lMinorP);
2793     strcpy(lVersion, lBuff.ptr);
2794     strtok(lLetter, " ,.-");
2795     /*printf("lLetter %s\n", lLetter);*/
2796     return lMajor > 0 || (lMinor == 9 && *lLetter == 'b' && lDate >= 20031126);
2797 }
2798 
2799 int whiptailPresentOnly()
2800 {
2801     static int ret = -1;
2802     if (ret < 0)
2803     {
2804         ret = detectPresence("whiptail");
2805     }
2806     return ret;
2807 }
2808 
2809 const(char)* terminalName()
2810 {
2811     static char[128] ret_buf = "*";
2812     char[64] shellName_buf = "*";
2813     char* ret = ret_buf.ptr;
2814     char* lShellName = shellName_buf.ptr;
2815     const(int)* lArray;
2816 
2817     if (ret[0] == '*')
2818     {
2819         if (detectPresence("bash"))
2820         {
2821             strcpy(lShellName, "bash -c "); /*good for basic input*/
2822         }
2823         else if (some(dialogNameOnly()) || whiptailPresentOnly())
2824         {
2825             strcpy(lShellName, "sh -c "); /*good enough for dialog & whiptail*/
2826         }
2827         else
2828         {
2829             strcpy(ret, "");
2830             return null;
2831         }
2832 
2833         if (isDarwin())
2834         {
2835             if (strcpy(ret, "/opt/X11/bin/xterm") && detectPresence(ret))
2836             {
2837                 strcat(ret, " -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e ");
2838                 strcat(ret, lShellName);
2839             }
2840             else
2841             {
2842                 strcpy(ret, "");
2843             }
2844         }
2845         else if (strcpy(ret, "xterm") /*good (small without parameters)*/
2846                  && detectPresence(ret))
2847         {
2848             strcat(ret, " -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e ");
2849             strcat(ret, lShellName);
2850         }
2851         else if (strcpy(ret, "terminator") /*good*/
2852                  && detectPresence(ret))
2853         {
2854             strcat(ret, " -x ");
2855             strcat(ret, lShellName);
2856         }
2857         else if (strcpy(ret, "lxterminal") /*good*/
2858                  && detectPresence(ret))
2859         {
2860             strcat(ret, " -e ");
2861             strcat(ret, lShellName);
2862         }
2863         else if (strcpy(ret, "konsole") /*good*/
2864                  && detectPresence(ret))
2865         {
2866             strcat(ret, " -e ");
2867             strcat(ret, lShellName);
2868         }
2869         else if (strcpy(ret, "kterm") /*good*/
2870                  && detectPresence(ret))
2871         {
2872             strcat(ret, " -e ");
2873             strcat(ret, lShellName);
2874         }
2875         else if (strcpy(ret, "tilix") /*good*/
2876                  && detectPresence(ret))
2877         {
2878             strcat(ret, " -e ");
2879             strcat(ret, lShellName);
2880         }
2881         else if (strcpy(ret, "xfce4-terminal") /*good*/
2882                  && detectPresence(ret))
2883         {
2884             strcat(ret, " -x ");
2885             strcat(ret, lShellName);
2886         }
2887         else if (strcpy(ret, "mate-terminal") /*good*/
2888                  && detectPresence(ret))
2889         {
2890             strcat(ret, " -x ");
2891             strcat(ret, lShellName);
2892         }
2893         else if (strcpy(ret, "Eterm") /*good*/
2894                  && detectPresence(ret))
2895         {
2896             strcat(ret, " -e ");
2897             strcat(ret, lShellName);
2898         }
2899         else if (strcpy(ret, "evilvte") /*good*/
2900                  && detectPresence(ret))
2901         {
2902             strcat(ret, " -e ");
2903             strcat(ret, lShellName);
2904         }
2905         else if (strcpy(ret, "pterm") /*good (only letters)*/
2906                  && detectPresence(ret))
2907         {
2908             strcat(ret, " -e ");
2909             strcat(ret, lShellName);
2910         }
2911         else if (strcpy(ret, "gnome-terminal") && detectPresence(ret))
2912         {
2913             lArray = getMajorMinorPatch(ret);
2914             if (lArray[0] < 3 || lArray[0] == 3 && lArray[1] <= 6)
2915             {
2916                 strcat(ret, " --disable-factory -x ");
2917                 strcat(ret, lShellName);
2918             }
2919             else
2920             {
2921                 strcpy(ret, "");
2922             }
2923         }
2924         else
2925         {
2926             strcpy(ret, "");
2927         }
2928         /* bad: koi rxterm guake tilda vala-terminal qterminal
2929                 aterm Terminal terminology sakura lilyterm weston-terminal
2930                 roxterm termit xvt rxvt mrxvt urxvt */
2931     }
2932     return some(ret) ? ret : null;
2933 }
2934 
2935 const(char)* dialogName()
2936 {
2937     const(char)* ret;
2938     ret = dialogNameOnly();
2939     if (some(ret) && (isTerminalRunning() || terminalName()))
2940     {
2941         return ret;
2942     }
2943     else
2944         return null;
2945 }
2946 
2947 int whiptailPresent()
2948 {
2949     int ret;
2950     ret = whiptailPresentOnly();
2951     if (ret && (isTerminalRunning() || terminalName()))
2952     {
2953         return ret;
2954     }
2955     else
2956         return 0;
2957 }
2958 
2959 int graphicMode()
2960 {
2961     return !(tinyfd_forceConsole && (isTerminalRunning() || terminalName())) && (getenv("DISPLAY") || (isDarwin() && (!getenv("SSH_TTY") || getenv("DISPLAY"))));
2962 }
2963 
2964 int pactlPresent()
2965 {
2966     static int ret = -1;
2967     if (ret < 0)
2968     {
2969         ret = detectPresence("pactl");
2970     }
2971     return ret;
2972 }
2973 
2974 int speakertestPresent()
2975 {
2976     static int ret = -1;
2977     if (ret < 0)
2978     {
2979         ret = detectPresence("speaker-test");
2980     }
2981     return ret;
2982 }
2983 
2984 int beepexePresent()
2985 {
2986     static int ret = -1;
2987     if (ret < 0)
2988     {
2989         ret = detectPresence("beep.exe");
2990     }
2991     return ret;
2992 }
2993 
2994 int xmessagePresent()
2995 {
2996     static int ret = -1;
2997     if (ret < 0)
2998     {
2999         ret = detectPresence("xmessage"); /*if not tty,not on osxpath*/
3000     }
3001     return ret && graphicMode();
3002 }
3003 
3004 int gxmessagePresent()
3005 {
3006     static int ret = -1;
3007     if (ret < 0)
3008     {
3009         ret = detectPresence("gxmessage");
3010     }
3011     return ret && graphicMode();
3012 }
3013 
3014 int gmessagePresent()
3015 {
3016     static int ret = -1;
3017     if (ret < 0)
3018     {
3019         ret = detectPresence("gmessage");
3020     }
3021     return ret && graphicMode();
3022 }
3023 
3024 int notifysendPresent()
3025 {
3026     static int ret = -1;
3027     if (ret < 0)
3028     {
3029         ret = detectPresence("notify-send");
3030     }
3031     return ret && graphicMode();
3032 }
3033 
3034 int perlPresent()
3035 {
3036     static int ret = -1;
3037     char[MAX_PATH_OR_CMD] lBuff = 0;
3038     FILE* lIn;
3039 
3040     if (ret < 0)
3041     {
3042         ret = detectPresence("perl");
3043         if (ret)
3044         {
3045             lIn = popen("perl -MNet::DBus -e \"Net::DBus->session->get_service('org.freedesktop.Notifications')\" 2>&1", "r");
3046             if (fgets(lBuff.ptr, lBuff.sizeof, lIn) is null)
3047             {
3048                 ret = 2;
3049             }
3050             pclose(lIn);
3051             if (tinyfd_verbose)
3052                 printf("perl-dbus %d\n", ret);
3053         }
3054     }
3055     return graphicMode() ? ret : 0;
3056 }
3057 
3058 int afplayPresent()
3059 {
3060     static int ret = -1;
3061     char[MAX_PATH_OR_CMD] lBuff = '\0';
3062     FILE* lIn;
3063 
3064     if (ret < 0)
3065     {
3066         ret = detectPresence("afplay");
3067         if (ret)
3068         {
3069             lIn = popen("test -e /System/Library/Sounds/Ping.aiff || echo Ping", "r");
3070             if (fgets(lBuff.ptr, lBuff.sizeof, lIn) is null)
3071             {
3072                 ret = 2;
3073             }
3074             pclose(lIn);
3075             if (tinyfd_verbose)
3076                 printf("afplay %d\n", ret);
3077         }
3078     }
3079     return graphicMode() ? ret : 0;
3080 }
3081 
3082 int xdialogPresent()
3083 {
3084     static int ret = -1;
3085     if (ret < 0)
3086     {
3087         ret = detectPresence("Xdialog");
3088     }
3089     return ret && graphicMode();
3090 }
3091 
3092 int gdialogPresent()
3093 {
3094     static int ret = -1;
3095     if (ret < 0)
3096     {
3097         ret = detectPresence("gdialog");
3098     }
3099     return ret && graphicMode();
3100 }
3101 
3102 int osascriptPresent()
3103 {
3104     static int ret = -1;
3105     if (ret < 0)
3106     {
3107         gWarningDisplayed |= !!getenv("SSH_TTY");
3108         ret = detectPresence("osascript");
3109     }
3110     return ret && graphicMode() && !getenv("SSH_TTY");
3111 }
3112 
3113 int qarmaPresent()
3114 {
3115     static int ret = -1;
3116     if (ret < 0)
3117     {
3118         ret = detectPresence("qarma");
3119     }
3120     return ret && graphicMode();
3121 }
3122 
3123 int matedialogPresent()
3124 {
3125     static int ret = -1;
3126     if (ret < 0)
3127     {
3128         ret = detectPresence("matedialog");
3129     }
3130     return ret && graphicMode();
3131 }
3132 
3133 int shellementaryPresent()
3134 {
3135     static int ret = -1;
3136     if (ret < 0)
3137     {
3138         ret = 0; /*detectPresence("shellementary"); shellementary is not ready yet */
3139     }
3140     return ret && graphicMode();
3141 }
3142 
3143 int zenityPresent()
3144 {
3145     static int ret = -1;
3146     if (ret < 0)
3147     {
3148         ret = detectPresence("zenity");
3149     }
3150     return ret && graphicMode();
3151 }
3152 
3153 int zenity3Present()
3154 {
3155     static int ret = -1;
3156     char[MAX_PATH_OR_CMD] lBuff = '\0';
3157     FILE* lIn;
3158     int lIntTmp;
3159 
3160     if (ret < 0)
3161     {
3162         ret = 0;
3163         if (zenityPresent())
3164         {
3165             lIn = popen("zenity --version", "r");
3166             if (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
3167             {
3168                 if (atoi(lBuff.ptr) >= 3)
3169                 {
3170                     ret = 3;
3171                     lIntTmp = atoi(strtok(lBuff.ptr, ".") + 2);
3172                     if (lIntTmp >= 18)
3173                     {
3174                         ret = 5;
3175                     }
3176                     else if (lIntTmp >= 10)
3177                     {
3178                         ret = 4;
3179                     }
3180                 }
3181                 else if ((atoi(lBuff.ptr) == 2) && (atoi(strtok(lBuff.ptr, ".") + 2) >= 32))
3182                 {
3183                     ret = 2;
3184                 }
3185                 if (tinyfd_verbose)
3186                     printf("zenity %d\n", ret);
3187             }
3188             pclose(lIn);
3189         }
3190     }
3191     return graphicMode() ? ret : 0;
3192 }
3193 
3194 int kdialogPresent()
3195 {
3196     static int ret = -1;
3197     char[MAX_PATH_OR_CMD] lBuff = '\0';
3198     FILE* lIn;
3199     char* lDesktop;
3200 
3201     if (ret < 0)
3202     {
3203         if (zenityPresent())
3204         {
3205             lDesktop = getenv("XDG_SESSION_DESKTOP");
3206             if (!eq(lDesktop, "KDE") && !eq(lDesktop, "lxqt"))
3207             {
3208                 ret = 0;
3209                 return ret;
3210             }
3211         }
3212 
3213         ret = detectPresence("kdialog");
3214         if (ret && !getenv("SSH_TTY"))
3215         {
3216             lIn = popen("kdialog --attach 2>&1", "r");
3217             if (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
3218             {
3219                 if (!strstr("Unknown", lBuff.ptr))
3220                 {
3221                     ret = 2;
3222                     if (tinyfd_verbose)
3223                         printf("kdialog-attach %d\n", ret);
3224                 }
3225             }
3226             pclose(lIn);
3227 
3228             if (ret == 2)
3229             {
3230                 ret = 1;
3231                 lIn = popen("kdialog --passivepopup 2>&1", "r");
3232                 if (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
3233                 {
3234                     if (!strstr("Unknown", lBuff.ptr))
3235                     {
3236                         ret = 2;
3237                         if (tinyfd_verbose)
3238                             printf("kdialog-popup %d\n", ret);
3239                     }
3240                 }
3241                 pclose(lIn);
3242             }
3243         }
3244     }
3245     return graphicMode() ? ret : 0;
3246 }
3247 
3248 int osx9orBetter()
3249 {
3250     static int ret = -1;
3251     char[MAX_PATH_OR_CMD] lBuff = '\0';
3252     FILE* lIn;
3253     int V, v;
3254 
3255     if (ret < 0)
3256     {
3257         ret = 0;
3258         lIn = popen("osascript -e 'set osver to system version of (system info)'", "r");
3259         if ((fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null) && (2 == sscanf(lBuff.ptr, "%d.%d", &V, &v)))
3260         {
3261             V = V * 100 + v;
3262             if (V >= 1009)
3263             {
3264                 ret = 1;
3265             }
3266         }
3267         pclose(lIn);
3268         if (tinyfd_verbose)
3269             printf("Osx10 = %d, %d = %s\n", ret, V, lBuff.ptr);
3270     }
3271     return ret;
3272 }
3273 
3274 int python2Present()
3275 {
3276     static int ret = -1;
3277 
3278     if (ret < 0)
3279     {
3280         ret = 0;
3281         strcpy(gPython2Name.ptr, "python2");
3282         if (detectPresence(gPython2Name.ptr))
3283             ret = 1;
3284         else
3285         {
3286             for (int i = 9; i >= 0; i--)
3287             {
3288                 sprintf(gPython2Name.ptr, "python2.%d", i);
3289                 if (detectPresence(gPython2Name.ptr))
3290                 {
3291                     ret = 1;
3292                     break;
3293                 }
3294             }
3295             /*if ( ! ret )
3296             {
3297                 strcpy(gPython2Name , "python" ) ;
3298                 if ( detectPresence(gPython2Name.ptr) ) ret = 1;
3299             }*/
3300         }
3301         if (tinyfd_verbose)
3302             printf("python2Present %d\n", ret);
3303         if (tinyfd_verbose)
3304             printf("gPython2Name %s\n", gPython2Name.ptr);
3305     }
3306     return ret;
3307 }
3308 
3309 int python3Present()
3310 {
3311     static int ret = -1;
3312 
3313     if (ret < 0)
3314     {
3315         ret = 0;
3316         strcpy(gPython3Name.ptr, "python3");
3317         if (detectPresence(gPython3Name.ptr))
3318             ret = 1;
3319         else
3320         {
3321             for (int i = 9; i >= 0; i--)
3322             {
3323                 sprintf(gPython3Name.ptr, "python3.%d", i);
3324                 if (detectPresence(gPython3Name.ptr))
3325                 {
3326                     ret = 1;
3327                     break;
3328                 }
3329             }
3330             /*if ( ! ret )
3331             {
3332                 strcpy(gPython3Name , "python" ) ;
3333                 if ( detectPresence(gPython3Name.ptr) ) ret = 1;
3334             }*/
3335         }
3336         if (tinyfd_verbose)
3337             printf("python3Present %d\n", ret);
3338         if (tinyfd_verbose)
3339             printf("gPython3Name %s\n", gPython3Name.ptr);
3340     }
3341     return ret;
3342 }
3343 
3344 int tkinter2Present()
3345 {
3346     static int ret = -1;
3347     char[256] lPythonCommand = '\0';
3348     char[256] lPythonParams =
3349         "-S -c \"try:\n\timport Tkinter;\nexcept:\n\tprint 0;\"";
3350 
3351     if (ret < 0)
3352     {
3353         ret = 0;
3354         if (python2Present())
3355         {
3356             sprintf(lPythonCommand.ptr, "%s %s", gPython2Name.ptr, lPythonParams.ptr);
3357             ret = tryCommand(lPythonCommand.ptr);
3358         }
3359         if (tinyfd_verbose)
3360             printf("tkinter2Present %d\n", ret);
3361     }
3362     return ret && graphicMode() && !(isDarwin() && getenv("SSH_TTY"));
3363 }
3364 
3365 int tkinter3Present()
3366 {
3367     static int ret = -1;
3368     char[256] lPythonCommand = '\0';
3369     char[256] lPythonParams =
3370         "-S -c \"try:\n\timport tkinter;\nexcept:\n\tprint(0);\"";
3371 
3372     if (ret < 0)
3373     {
3374         ret = 0;
3375         if (python3Present())
3376         {
3377             sprintf(lPythonCommand.ptr, "%s %s", gPython3Name.ptr, lPythonParams.ptr);
3378             ret = tryCommand(lPythonCommand.ptr);
3379         }
3380         if (tinyfd_verbose)
3381             printf("tkinter3Present %d\n", ret);
3382     }
3383     return ret && graphicMode() && !(isDarwin() && getenv("SSH_TTY"));
3384 }
3385 
3386 int pythonDbusPresent()
3387 {
3388     static int ret = -1;
3389     char[256] lPythonCommand = '\0';
3390     char[256] lPythonParams =
3391 `-c "try:
3392     import dbus
3393     bus=dbus.SessionBus()
3394     notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications')
3395     notify=dbus.Interface(notif,'org.freedesktop.Notifications')
3396 except:
3397     print(0)"`;
3398 
3399     if (ret < 0)
3400     {
3401         ret = 0;
3402         if (python2Present())
3403         {
3404             strcpy(gPythonName.ptr, gPython2Name.ptr);
3405             sprintf(lPythonCommand.ptr, "%s %s", gPythonName.ptr, lPythonParams.ptr);
3406             ret = tryCommand(lPythonCommand.ptr);
3407         }
3408 
3409         if (!ret && python3Present())
3410         {
3411             strcpy(gPythonName.ptr, gPython3Name.ptr);
3412             sprintf(lPythonCommand.ptr, "%s %s", gPythonName.ptr, lPythonParams.ptr);
3413             ret = tryCommand(lPythonCommand.ptr);
3414         }
3415 
3416         if (tinyfd_verbose)
3417             printf("dbusPresent %d\n", ret);
3418         if (tinyfd_verbose)
3419             printf("gPythonName %s\n", gPythonName.ptr);
3420     }
3421     return ret && graphicMode() && !(isDarwin() && getenv("SSH_TTY"));
3422 }
3423 
3424 void sigHandler(int sig)
3425 {
3426     FILE* lIn = popen("pactl unload-module module-sine", "r");
3427     if (lIn)
3428     {
3429         pclose(lIn);
3430     }
3431 }
3432 
3433 void _beep()
3434 {
3435     char[256] str_buf = '\0';
3436     char* str = str_buf.ptr;
3437     FILE* lIn;
3438 
3439     if (osascriptPresent())
3440     {
3441         if (afplayPresent() >= 2)
3442         {
3443             strcpy(str, "afplay /System/Library/Sounds/Ping.aiff");
3444         }
3445         else
3446         {
3447             strcpy(str, "osascript -e 'tell application \"System Events\" to beep'");
3448         }
3449     }
3450     else if (pactlPresent())
3451     {
3452         signal(SIGINT, &sigHandler);
3453         /*strcpy( str , "pactl load-module module-sine frequency=440;sleep .3;pactl unload-module module-sine" ) ;*/
3454         strcpy(str, "thnum=$(pactl load-module module-sine frequency=440);sleep .3;pactl unload-module $thnum");
3455     }
3456     else if (speakertestPresent())
3457     {
3458         /*strcpy( str , "timeout -k .3 .3 speaker-test --frequency 440 --test sine > /dev/tty" ) ;*/
3459         strcpy(str, "( speaker-test -t sine -f 440 > /dev/tty )& pid=$!;sleep .3; kill -9 $pid");
3460     }
3461     else if (beepexePresent())
3462     {
3463         strcpy(str, "beep.exe 440 300");
3464     }
3465     else
3466     {
3467         strcpy(str, "printf '\a' > /dev/tty");
3468     }
3469 
3470     if (tinyfd_verbose)
3471         printf("str: %s\n", str);
3472 
3473     lIn = popen(str, "r");
3474     if (lIn)
3475     {
3476         pclose(lIn);
3477     }
3478 
3479     if (pactlPresent())
3480     {
3481         signal(SIGINT, SIG_DFL);
3482     }
3483 }
3484 
3485 int _messageBox(
3486     const char* aTitle,
3487     const char* aMessage,
3488     const char* aDialogType,
3489     const char* aIconType,
3490     int aDefaultButton)
3491 {
3492     char[MAX_PATH_OR_CMD] lBuff = '\0';
3493     const bool lQuery = eq(aTitle, "tinyfd_query");
3494     char* str;
3495     char* lpDialogString;
3496     FILE* lIn;
3497     bool lWasGraphicDialog;
3498     bool lWasXterm;
3499     int lResult;
3500     char lChar;
3501     termios infoOri;
3502     termios info;
3503     size_t lTitleLen;
3504     size_t lMessageLen;
3505 
3506     lBuff[0] = '\0';
3507 
3508     lTitleLen = aTitle ? strlen(aTitle) : 0;
3509     lMessageLen = aMessage ? strlen(aMessage) : 0;
3510     if (!aTitle || !lQuery)
3511     {
3512         str = cast(char*)malloc(MAX_PATH_OR_CMD + lTitleLen + lMessageLen);
3513     }
3514 
3515     if (osascriptPresent())
3516     {
3517         if (lQuery)
3518         {
3519             response("applescript");
3520             return 1;
3521         }
3522 
3523         strcpy(str, "osascript ");
3524         if (!osx9orBetter())
3525             strcat(str, " -e 'tell application \"System Events\"' -e 'Activate'");
3526         strcat(str, " -e 'try' -e 'set {vButton} to {button returned} of ( display dialog \"");
3527         if (some(aMessage))
3528         {
3529             strcat(str, aMessage);
3530         }
3531         strcat(str, "\" ");
3532         if (some(aTitle))
3533         {
3534             strcat(str, "with title \"");
3535             strcat(str, aTitle);
3536             strcat(str, "\" ");
3537         }
3538         strcat(str, "with icon ");
3539         if (eq("error", aIconType))
3540         {
3541             strcat(str, "stop ");
3542         }
3543         else if (eq("warning", aIconType))
3544         {
3545             strcat(str, "caution ");
3546         }
3547         else /* question or info */
3548         {
3549             strcat(str, "note ");
3550         }
3551         if (eq("okcancel", aDialogType))
3552         {
3553             if (!aDefaultButton)
3554             {
3555                 strcat(str, "default button \"Cancel\" ");
3556             }
3557         }
3558         else if (eq("yesno", aDialogType))
3559         {
3560             strcat(str, "buttons {\"No\", \"Yes\"} ");
3561             if (aDefaultButton)
3562             {
3563                 strcat(str, "default button \"Yes\" ");
3564             }
3565             else
3566             {
3567                 strcat(str, "default button \"No\" ");
3568             }
3569             strcat(str, "cancel button \"No\"");
3570         }
3571         else if (eq("yesnocancel", aDialogType))
3572         {
3573             strcat(str, "buttons {\"No\", \"Yes\", \"Cancel\"} ");
3574             switch (aDefaultButton)
3575             {
3576             case 1:
3577                 strcat(str, "default button \"Yes\" ");
3578                 break;
3579             case 2:
3580                 strcat(str, "default button \"No\" ");
3581                 break;
3582             case 0:
3583                 strcat(str, "default button \"Cancel\" ");
3584                 break;
3585             default:
3586                 break;
3587             }
3588             strcat(str, "cancel button \"Cancel\"");
3589         }
3590         else
3591         {
3592             strcat(str, "buttons {\"OK\"} ");
3593             strcat(str, "default button \"OK\" ");
3594         }
3595         strcat(str, ")' ");
3596 
3597         strcat(str,
3598                " -e 'if vButton is \"Yes\" then' -e 'return 1'" ~
3599                " -e 'else if vButton is \"OK\" then' -e 'return 1'" ~
3600                " -e 'else if vButton is \"No\" then' -e 'return 2'" ~
3601                " -e 'else' -e 'return 0' -e 'end if' ");
3602 
3603         strcat(str, "-e 'on error number -128' ");
3604         strcat(str, "-e '0' ");
3605 
3606         strcat(str, "-e 'end try'");
3607         if (!osx9orBetter())
3608             strcat(str, " -e 'end tell'");
3609     }
3610     else if (kdialogPresent())
3611     {
3612         if (lQuery)
3613         {
3614             response("kdialog");
3615             return 1;
3616         }
3617 
3618         strcpy(str, "kdialog");
3619         if (kdialogPresent() == 2)
3620         {
3621             strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
3622         }
3623 
3624         strcat(str, " --");
3625         if (eq("okcancel", aDialogType) || eq("yesno", aDialogType) || eq("yesnocancel", aDialogType))
3626         {
3627             if (eq("warning", aIconType) || eq("error", aIconType))
3628             {
3629                 strcat(str, "warning");
3630             }
3631             if (eq("yesnocancel", aDialogType))
3632             {
3633                 strcat(str, "yesnocancel");
3634             }
3635             else
3636             {
3637                 strcat(str, "yesno");
3638             }
3639         }
3640         else if (eq("error", aIconType))
3641         {
3642             strcat(str, "error");
3643         }
3644         else if (eq("warning", aIconType))
3645         {
3646             strcat(str, "sorry");
3647         }
3648         else
3649         {
3650             strcat(str, "msgbox");
3651         }
3652         strcat(str, " \"");
3653         if (aMessage)
3654         {
3655             strcat(str, aMessage);
3656         }
3657         strcat(str, "\"");
3658         if (eq("okcancel", aDialogType))
3659         {
3660             strcat(str,
3661                    " --yes-label Ok --no-label Cancel");
3662         }
3663         if (some(aTitle))
3664         {
3665             strcat(str, " --title \"");
3666             strcat(str, aTitle);
3667             strcat(str, "\"");
3668         }
3669 
3670         if (eq("yesnocancel", aDialogType))
3671         {
3672             strcat(str, "; x=$? ;if [ $x = 0 ] ;then echo 1;elif [ $x = 1 ] ;then echo 2;else echo 0;fi");
3673         }
3674         else
3675         {
3676             strcat(str, ";if [ $? = 0 ];then echo 1;else echo 0;fi");
3677         }
3678     }
3679     else if (zenityPresent() || matedialogPresent() || shellementaryPresent() || qarmaPresent())
3680     {
3681         if (zenityPresent())
3682         {
3683             if (lQuery)
3684             {
3685                 response("zenity");
3686                 return 1;
3687             }
3688             strcpy(str, "szAnswer=$(zenity");
3689             if (zenity3Present() >= 4 && !getenv("SSH_TTY"))
3690             {
3691                 strcat(str, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
3692             }
3693         }
3694         else if (matedialogPresent())
3695         {
3696             if (lQuery)
3697             {
3698                 response("matedialog");
3699                 return 1;
3700             }
3701             strcpy(str, "szAnswer=$(matedialog");
3702         }
3703         else if (shellementaryPresent())
3704         {
3705             if (lQuery)
3706             {
3707                 response("shellementary");
3708                 return 1;
3709             }
3710             strcpy(str, "szAnswer=$(shellementary");
3711         }
3712         else
3713         {
3714             if (lQuery)
3715             {
3716                 response("qarma");
3717                 return 1;
3718             }
3719             strcpy(str, "szAnswer=$(qarma");
3720             if (!getenv("SSH_TTY"))
3721             {
3722                 strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
3723             }
3724         }
3725         strcat(str, " --");
3726 
3727         if (eq("okcancel", aDialogType))
3728         {
3729             strcat(str, "question --ok-label=Ok --cancel-label=Cancel");
3730         }
3731         else if (eq("yesno", aDialogType))
3732         {
3733             strcat(str, "question");
3734         }
3735         else if (eq("yesnocancel", aDialogType))
3736         {
3737             strcat(str, "list --column \"\" --hide-header \"Yes\" \"No\"");
3738         }
3739         else if (eq("error", aIconType))
3740         {
3741             strcat(str, "error");
3742         }
3743         else if (eq("warning", aIconType))
3744         {
3745             strcat(str, "warning");
3746         }
3747         else
3748         {
3749             strcat(str, "info");
3750         }
3751         if (some(aTitle))
3752         {
3753             strcat(str, " --title=\"");
3754             strcat(str, aTitle);
3755             strcat(str, "\"");
3756         }
3757         if (some(aMessage))
3758         {
3759             strcat(str, " --no-wrap --text=\"");
3760             strcat(str, aMessage);
3761             strcat(str, "\"");
3762         }
3763         if ((zenity3Present() >= 3) || (!zenityPresent() && (shellementaryPresent() || qarmaPresent())))
3764         {
3765             strcat(str, " --icon-name=dialog-");
3766             if (eq("question", aIconType) || eq("error", aIconType) || eq("warning", aIconType))
3767             {
3768                 strcat(str, aIconType);
3769             }
3770             else
3771             {
3772                 strcat(str, "information");
3773             }
3774         }
3775 
3776         if (tinyfd_silent)
3777             strcat(str, " 2>/dev/null ");
3778 
3779         if (eq("yesnocancel", aDialogType))
3780         {
3781             strcat(str,
3782                    ");if [ $? = 1 ];then echo 0;elif [ $szAnswer = \"No\" ];then echo 2;else echo 1;fi");
3783         }
3784         else
3785         {
3786             strcat(str, ");if [ $? = 0 ];then echo 1;else echo 0;fi");
3787         }
3788     }
3789     else if (!gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter2Present())
3790     {
3791         if (lQuery)
3792         {
3793             response("python2-tkinter");
3794             return 1;
3795         }
3796 
3797         strcpy(str, gPython2Name.ptr);
3798         if (!isTerminalRunning() && isDarwin())
3799         {
3800             strcat(str, " -i"); /* for osx without console */
3801         }
3802 
3803         strcat(str,
3804                " -S -c \"import Tkinter,tkMessageBox;root=Tkinter.Tk();root.withdraw();");
3805 
3806         if (isDarwin())
3807         {
3808             strcat(str,
3809                    "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set " ~
3810                    "frontmost of process \\\"Python\\\" to true' ''');");
3811         }
3812 
3813         strcat(str, "res=tkMessageBox.");
3814         if (eq("okcancel", aDialogType))
3815         {
3816             strcat(str, "askokcancel(");
3817             if (aDefaultButton)
3818             {
3819                 strcat(str, "default=tkMessageBox.OK,");
3820             }
3821             else
3822             {
3823                 strcat(str, "default=tkMessageBox.CANCEL,");
3824             }
3825         }
3826         else if (eq("yesno", aDialogType))
3827         {
3828             strcat(str, "askyesno(");
3829             if (aDefaultButton)
3830             {
3831                 strcat(str, "default=tkMessageBox.YES,");
3832             }
3833             else
3834             {
3835                 strcat(str, "default=tkMessageBox.NO,");
3836             }
3837         }
3838         else if (eq("yesnocancel", aDialogType))
3839         {
3840             strcat(str, "askyesnocancel(");
3841             switch (aDefaultButton)
3842             {
3843             case 1:
3844                 strcat(str, "default=tkMessageBox.YES,");
3845                 break;
3846             case 2:
3847                 strcat(str, "default=tkMessageBox.NO,");
3848                 break;
3849             case 0:
3850                 strcat(str, "default=tkMessageBox.CANCEL,");
3851                 break;
3852             default:
3853                 break;
3854             }
3855         }
3856         else
3857         {
3858             strcat(str, "showinfo(");
3859         }
3860 
3861         strcat(str, "icon='");
3862         if (eq("question", aIconType) || eq("error", aIconType) || eq("warning", aIconType))
3863         {
3864             strcat(str, aIconType);
3865         }
3866         else
3867         {
3868             strcat(str, "info");
3869         }
3870 
3871         strcat(str, "',");
3872         if (some(aTitle))
3873         {
3874             strcat(str, "title='");
3875             strcat(str, aTitle);
3876             strcat(str, "',");
3877         }
3878         if (some(aMessage))
3879         {
3880             strcat(str, "message='");
3881             lpDialogString = str + strlen(str);
3882             replaceSubStr(aMessage, "\n", "\\n", lpDialogString);
3883             strcat(str, "'");
3884         }
3885 
3886         if (eq("yesnocancel", aDialogType))
3887         {
3888             strcat(str, `);
3889 if res is None:
3890     print 0
3891 elif res is False:
3892     print 2
3893 else:
3894     print 1
3895 "`);
3896         }
3897         else
3898         {
3899             strcat(str, `);
3900 if res is False:
3901     print 0
3902 else:
3903     print 1
3904 "`);
3905         }
3906     }
3907     else if (!gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter3Present())
3908     {
3909         if (lQuery)
3910         {
3911             response("python3-tkinter");
3912             return 1;
3913         }
3914 
3915         strcpy(str, gPython3Name.ptr);
3916         strcat(str,
3917                " -S -c \"import tkinter;from tkinter import messagebox;root=tkinter.Tk();root.withdraw();");
3918 
3919         strcat(str, "res=messagebox.");
3920         if (eq("okcancel", aDialogType))
3921         {
3922             strcat(str, "askokcancel(");
3923             if (aDefaultButton)
3924             {
3925                 strcat(str, "default=messagebox.OK,");
3926             }
3927             else
3928             {
3929                 strcat(str, "default=messagebox.CANCEL,");
3930             }
3931         }
3932         else if (eq("yesno", aDialogType))
3933         {
3934             strcat(str, "askyesno(");
3935             if (aDefaultButton)
3936             {
3937                 strcat(str, "default=messagebox.YES,");
3938             }
3939             else
3940             {
3941                 strcat(str, "default=messagebox.NO,");
3942             }
3943         }
3944         else if (eq("yesnocancel", aDialogType))
3945         {
3946             strcat(str, "askyesnocancel(");
3947             switch (aDefaultButton)
3948             {
3949             case 1:
3950                 strcat(str, "default=messagebox.YES,");
3951                 break;
3952             case 2:
3953                 strcat(str, "default=messagebox.NO,");
3954                 break;
3955             case 0:
3956                 strcat(str, "default=messagebox.CANCEL,");
3957                 break;
3958             default:
3959                 break;
3960             }
3961         }
3962         else
3963         {
3964             strcat(str, "showinfo(");
3965         }
3966 
3967         strcat(str, "icon='");
3968         if (eq("question", aIconType) || eq("error", aIconType) || eq("warning", aIconType))
3969         {
3970             strcat(str, aIconType);
3971         }
3972         else
3973         {
3974             strcat(str, "info");
3975         }
3976 
3977         strcat(str, "',");
3978         if (some(aTitle))
3979         {
3980             strcat(str, "title='");
3981             strcat(str, aTitle);
3982             strcat(str, "',");
3983         }
3984         if (some(aMessage))
3985         {
3986             strcat(str, "message='");
3987             lpDialogString = str + strlen(str);
3988             replaceSubStr(aMessage, "\n", "\\n", lpDialogString);
3989             strcat(str, "'");
3990         }
3991 
3992         if (eq("yesnocancel", aDialogType))
3993         {
3994             strcat(str, `);
3995 if res is None:
3996     print(0)
3997 elif res is False:
3998     print(2)
3999 else:
4000     print(1)
4001 "`);
4002         }
4003         else
4004         {
4005             strcat(str, `);
4006 if res is False:
4007     print(0)
4008 else:
4009     print(1)
4010 "`);
4011         }
4012     }
4013     else if (gxmessagePresent() || gmessagePresent() || (!gdialogPresent() && !xdialogPresent() && xmessagePresent()))
4014     {
4015         if (gxmessagePresent())
4016         {
4017             if (lQuery)
4018             {
4019                 response("gxmessage");
4020                 return 1;
4021             }
4022             strcpy(str, "gxmessage");
4023         }
4024         else if (gmessagePresent())
4025         {
4026             if (lQuery)
4027             {
4028                 response("gmessage");
4029                 return 1;
4030             }
4031             strcpy(str, "gmessage");
4032         }
4033         else
4034         {
4035             if (lQuery)
4036             {
4037                 response("xmessage");
4038                 return 1;
4039             }
4040             strcpy(str, "xmessage");
4041         }
4042 
4043         if (eq("okcancel", aDialogType))
4044         {
4045             strcat(str, " -buttons Ok:1,Cancel:0");
4046             switch (aDefaultButton)
4047             {
4048             case 1:
4049                 strcat(str, " -default Ok");
4050                 break;
4051             case 0:
4052                 strcat(str, " -default Cancel");
4053                 break;
4054             default:
4055                 break;
4056             }
4057         }
4058         else if (eq("yesno", aDialogType))
4059         {
4060             strcat(str, " -buttons Yes:1,No:0");
4061             switch (aDefaultButton)
4062             {
4063             case 1:
4064                 strcat(str, " -default Yes");
4065                 break;
4066             case 0:
4067                 strcat(str, " -default No");
4068                 break;
4069             default:
4070                 break;
4071             }
4072         }
4073         else if (eq("yesnocancel", aDialogType))
4074         {
4075             strcat(str, " -buttons Yes:1,No:2,Cancel:0");
4076             switch (aDefaultButton)
4077             {
4078             case 1:
4079                 strcat(str, " -default Yes");
4080                 break;
4081             case 2:
4082                 strcat(str, " -default No");
4083                 break;
4084             case 0:
4085                 strcat(str, " -default Cancel");
4086                 break;
4087             default:
4088                 break;
4089             }
4090         }
4091         else
4092         {
4093             strcat(str, " -buttons Ok:1");
4094             strcat(str, " -default Ok");
4095         }
4096 
4097         strcat(str, " -center \"");
4098         if (some(aMessage))
4099         {
4100             strcat(str, aMessage);
4101         }
4102         strcat(str, "\"");
4103         if (some(aTitle))
4104         {
4105             strcat(str, " -title  \"");
4106             strcat(str, aTitle);
4107             strcat(str, "\"");
4108         }
4109         strcat(str, " ; echo $? ");
4110     }
4111     else if (xdialogPresent() || gdialogPresent() || dialogName() || whiptailPresent())
4112     {
4113         if (gdialogPresent())
4114         {
4115             if (lQuery)
4116             {
4117                 response("gdialog");
4118                 return 1;
4119             }
4120             lWasGraphicDialog = true;
4121             strcpy(str, "(gdialog ");
4122         }
4123         else if (xdialogPresent())
4124         {
4125             if (lQuery)
4126             {
4127                 response("xdialog");
4128                 return 1;
4129             }
4130             lWasGraphicDialog = true;
4131             strcpy(str, "(Xdialog ");
4132         }
4133         else if (dialogName())
4134         {
4135             if (lQuery)
4136             {
4137                 response("dialog");
4138                 return 0;
4139             }
4140             if (isTerminalRunning())
4141             {
4142                 strcpy(str, "(dialog ");
4143             }
4144             else
4145             {
4146                 lWasXterm = true;
4147                 strcpy(str, terminalName());
4148                 strcat(str, "'(");
4149                 strcat(str, dialogName());
4150                 strcat(str, " ");
4151             }
4152         }
4153         else if (isTerminalRunning())
4154         {
4155             if (lQuery)
4156             {
4157                 response("whiptail");
4158                 return 0;
4159             }
4160             strcpy(str, "(whiptail ");
4161         }
4162         else
4163         {
4164             if (lQuery)
4165             {
4166                 response("whiptail");
4167                 return 0;
4168             }
4169             lWasXterm = true;
4170             strcpy(str, terminalName());
4171             strcat(str, "'(whiptail ");
4172         }
4173 
4174         if (some(aTitle))
4175         {
4176             strcat(str, "--title \"");
4177             strcat(str, aTitle);
4178             strcat(str, "\" ");
4179         }
4180 
4181         if (!xdialogPresent() && !gdialogPresent())
4182         {
4183             if (eq("okcancel", aDialogType) || eq("yesno", aDialogType) || eq("yesnocancel", aDialogType))
4184             {
4185                 strcat(str, "--backtitle \"");
4186                 strcat(str, "tab: move focus");
4187                 strcat(str, "\" ");
4188             }
4189         }
4190 
4191         if (eq("okcancel", aDialogType))
4192         {
4193             if (!aDefaultButton)
4194             {
4195                 strcat(str, "--defaultno ");
4196             }
4197             strcat(str,
4198                    "--yes-label \"Ok\" --no-label \"Cancel\" --yesno ");
4199         }
4200         else if (eq("yesno", aDialogType))
4201         {
4202             if (!aDefaultButton)
4203             {
4204                 strcat(str, "--defaultno ");
4205             }
4206             strcat(str, "--yesno ");
4207         }
4208         else if (eq("yesnocancel", aDialogType))
4209         {
4210             if (!aDefaultButton)
4211             {
4212                 strcat(str, "--defaultno ");
4213             }
4214             strcat(str, "--menu ");
4215         }
4216         else
4217         {
4218             strcat(str, "--msgbox ");
4219         }
4220         strcat(str, "\"");
4221         if (some(aMessage))
4222         {
4223             strcat(str, aMessage);
4224         }
4225         strcat(str, "\" ");
4226 
4227         if (lWasGraphicDialog)
4228         {
4229             if (eq("yesnocancel", aDialogType))
4230             {
4231                 strcat(str, "0 60 0 Yes \"\" No \"\") 2>/tmp/tinyfd.txt;" ~
4232                             "if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;" ~
4233                             "tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes");
4234             }
4235             else
4236             {
4237                 strcat(str, "10 60 ) 2>&1;if [ $? = 0 ];then echo 1;else echo 0;fi");
4238             }
4239         }
4240         else
4241         {
4242             if (eq("yesnocancel", aDialogType))
4243             {
4244                 strcat(str, "0 60 0 Yes \"\" No \"\" >/dev/tty ) 2>/tmp/tinyfd.txt;" ~
4245                             "if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;" ~
4246                             "tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes");
4247 
4248                 if (lWasXterm)
4249                 {
4250                     strcat(str, " >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt");
4251                 }
4252                 else
4253                 {
4254                     strcat(str, "; clear >/dev/tty");
4255                 }
4256             }
4257             else
4258             {
4259                 strcat(str, "10 60 >/dev/tty) 2>&1;if [ $? = 0 ];");
4260                 if (lWasXterm)
4261                 {
4262                     strcat(str,
4263                            "then\n\techo 1\nelse\n\techo 0\nfi >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt");
4264                 }
4265                 else
4266                 {
4267                     strcat(str,
4268                            "then echo 1;else echo 0;fi;clear >/dev/tty");
4269                 }
4270             }
4271         }
4272     }
4273     else if (!isTerminalRunning() && terminalName())
4274     {
4275         if (lQuery)
4276         {
4277             response("basicinput");
4278             return 0;
4279         }
4280         strcpy(str, terminalName());
4281         strcat(str, "'");
4282         if (!gWarningDisplayed && !tinyfd_forceConsole)
4283         {
4284             gWarningDisplayed = true;
4285             strcat(str, "echo \"");
4286             strcat(str, gTitle.ptr);
4287             strcat(str, "\";");
4288             strcat(str, "echo \"");
4289             strcat(str, tinyfd_needs.ptr);
4290             strcat(str, "\";echo;echo;");
4291         }
4292         if (some(aTitle))
4293         {
4294             strcat(str, "echo \"");
4295             strcat(str, aTitle);
4296             strcat(str, "\";echo;");
4297         }
4298         if (some(aMessage))
4299         {
4300             strcat(str, "echo \"");
4301             strcat(str, aMessage);
4302             strcat(str, "\"; ");
4303         }
4304         if (eq("yesno", aDialogType))
4305         {
4306             strcat(str, "echo -n \"y/n: \"; ");
4307             strcat(str, "stty sane -echo;");
4308             strcat(str,
4309                    "answer=$( while ! head -c 1 | grep -i [ny];do true ;done);");
4310             strcat(str,
4311                    "if echo \"$answer\" | grep -iq \"^y\";then\n");
4312             strcat(str, "\techo 1\nelse\n\techo 0\nfi");
4313         }
4314         else if (eq("okcancel", aDialogType))
4315         {
4316             strcat(str, "echo -n \"[O]kay/[C]ancel: \"; ");
4317             strcat(str, "stty sane -echo;");
4318             strcat(str,
4319                    "answer=$( while ! head -c 1 | grep -i [oc];do true ;done);");
4320             strcat(str,
4321                    "if echo \"$answer\" | grep -iq \"^o\";then\n");
4322             strcat(str, "\techo 1\nelse\n\techo 0\nfi");
4323         }
4324         else if (eq("yesnocancel", aDialogType))
4325         {
4326             strcat(str, "echo -n \"[Y]es/[N]o/[C]ancel: \"; ");
4327             strcat(str, "stty sane -echo;");
4328             strcat(str,
4329                    "answer=$( while ! head -c 1 | grep -i [nyc];do true ;done);");
4330             strcat(str,
4331                    "if echo \"$answer\" | grep -iq \"^y\";then\n\techo 1\n");
4332             strcat(str, "elif echo \"$answer\" | grep -iq \"^n\";then\n\techo 2\n");
4333             strcat(str, "else\n\techo 0\nfi");
4334         }
4335         else
4336         {
4337             strcat(str, "echo -n \"press enter to continue \"; ");
4338             strcat(str, "stty sane -echo;");
4339             strcat(str,
4340                    "answer=$( while ! head -c 1;do true ;done);echo 1");
4341         }
4342         strcat(str,
4343                " >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt");
4344     }
4345     else if (!isTerminalRunning() && pythonDbusPresent() && eq("ok", aDialogType))
4346     {
4347         if (lQuery)
4348         {
4349             response("python-dbus");
4350             return 1;
4351         }
4352         strcpy(str, gPythonName.ptr);
4353         strcat(str, " -c \"import dbus;bus=dbus.SessionBus();");
4354         strcat(str, "notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');");
4355         strcat(str, "notify=dbus.Interface(notif,'org.freedesktop.Notifications');");
4356         strcat(str, "notify.Notify('',0,'");
4357         if (some(aIconType))
4358         {
4359             strcat(str, aIconType);
4360         }
4361         strcat(str, "','");
4362         if (some(aTitle))
4363         {
4364             strcat(str, aTitle);
4365         }
4366         strcat(str, "','");
4367         if (some(aMessage))
4368         {
4369             lpDialogString = str + strlen(str);
4370             replaceSubStr(aMessage, "\n", "\\n", lpDialogString);
4371         }
4372         strcat(str, "','','',5000)\"");
4373     }
4374     else if (!isTerminalRunning() && (perlPresent() >= 2) && eq("ok", aDialogType))
4375     {
4376         if (lQuery)
4377         {
4378             response("perl-dbus");
4379             return 1;
4380         }
4381         sprintf(str, `perl -e "use Net::DBus;
4382                     my \$sessionBus = Net::DBus->session;
4383                     my \$notificationsService = \$sessionBus->get_service('org.freedesktop.Notifications');
4384                     my \$notificationsObject = \$notificationsService->get_object('/org/freedesktop/Notifications',
4385                         'org.freedesktop.Notifications');
4386                     my \$notificationId;\$notificationId = \$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);"`,
4387                 aIconType ? aIconType : "", aTitle ? aTitle : "", aMessage ? aMessage : "");
4388     }
4389     else if (!isTerminalRunning() && notifysendPresent() && eq("ok", aDialogType))
4390     {
4391 
4392         if (lQuery)
4393         {
4394             response("notifysend");
4395             return 1;
4396         }
4397         strcpy(str, "notify-send");
4398         if (some(aIconType))
4399         {
4400             strcat(str, " -i '");
4401             strcat(str, aIconType);
4402             strcat(str, "'");
4403         }
4404         strcat(str, " \"");
4405         if (some(aTitle))
4406         {
4407             strcat(str, aTitle);
4408             strcat(str, " | ");
4409         }
4410         if (some(aMessage))
4411         {
4412             replaceSubStr(aMessage, "\n\t", " |  ", lBuff.ptr);
4413             replaceSubStr(aMessage, "\n", " | ", lBuff.ptr);
4414             replaceSubStr(aMessage, "\t", "  ", lBuff.ptr);
4415             strcat(str, lBuff.ptr);
4416         }
4417         strcat(str, "\"");
4418     }
4419     else
4420     {
4421         if (lQuery)
4422         {
4423             response("basicinput");
4424             return 0;
4425         }
4426         if (!gWarningDisplayed && !tinyfd_forceConsole)
4427         {
4428             gWarningDisplayed = true;
4429             printf("\n\n%s\n", gTitle.ptr);
4430             printf("%s\n\n", tinyfd_needs.ptr);
4431         }
4432         if (some(aTitle))
4433         {
4434             printf("\n%s\n", aTitle);
4435         }
4436 
4437         tcgetattr(0, &infoOri);
4438         tcgetattr(0, &info);
4439         info.c_lflag &= ~ICANON;
4440         info.c_cc[VMIN] = 1;
4441         info.c_cc[VTIME] = 0;
4442         tcsetattr(0, TCSANOW, &info);
4443         if (eq("yesno", aDialogType))
4444         {
4445             do
4446             {
4447                 if (some(aMessage))
4448                 {
4449                     printf("\n%s\n", aMessage);
4450                 }
4451                 printf("y/n: ");
4452                 fflush(stdout);
4453                 lChar = cast(char)tolower(getchar());
4454                 printf("\n\n");
4455             } while (lChar != 'y' && lChar != 'n');
4456             lResult = lChar == 'y' ? 1 : 0;
4457         }
4458         else if (eq("okcancel", aDialogType))
4459         {
4460             do
4461             {
4462                 if (some(aMessage))
4463                 {
4464                     printf("\n%s\n", aMessage);
4465                 }
4466                 printf("[O]kay/[C]ancel: ");
4467                 fflush(stdout);
4468                 lChar = cast(char)tolower(getchar());
4469                 printf("\n\n");
4470             } while (lChar != 'o' && lChar != 'c');
4471             lResult = lChar == 'o' ? 1 : 0;
4472         }
4473         else if (eq("yesnocancel", aDialogType))
4474         {
4475             do
4476             {
4477                 if (some(aMessage))
4478                 {
4479                     printf("\n%s\n", aMessage);
4480                 }
4481                 printf("[Y]es/[N]o/[C]ancel: ");
4482                 fflush(stdout);
4483                 lChar = cast(char)tolower(getchar());
4484                 printf("\n\n");
4485             } while (lChar != 'y' && lChar != 'n' && lChar != 'c');
4486             lResult = (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0;
4487         }
4488         else
4489         {
4490             if (some(aMessage))
4491             {
4492                 printf("\n%s\n\n", aMessage);
4493             }
4494             printf("press enter to continue ");
4495             fflush(stdout);
4496             getchar();
4497             printf("\n\n");
4498             lResult = 1;
4499         }
4500         tcsetattr(0, TCSANOW, &infoOri);
4501         free(str);
4502         return lResult;
4503     }
4504 
4505     if (tinyfd_verbose)
4506         printf("str: %s\n", str);
4507 
4508     lIn = popen(str, "r");
4509     if (!lIn)
4510     {
4511         free(str);
4512         return 0;
4513     }
4514     while (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
4515     {
4516     }
4517 
4518     pclose(lIn);
4519 
4520     /* printf( "lBuff: %s len: %lu \n" , lBuff , strlen(lBuff.ptr) ) ; */
4521     removeLastNL(lBuff.ptr);
4522     /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff.ptr) ) ; */
4523 
4524     if (eq("yesnocancel", aDialogType))
4525     {
4526         if (lBuff[0] == '1')
4527         {
4528             if (eq(lBuff.ptr + 1, "Yes"))
4529                 strcpy(lBuff.ptr, "1");
4530             else if (eq(lBuff.ptr + 1, "No"))
4531                 strcpy(lBuff.ptr, "2");
4532         }
4533     }
4534     /* printf( "lBuff2: %s len: %lu \n" , lBuff , strlen(lBuff.ptr) ) ; */
4535 
4536     lResult = eq(lBuff.ptr, "2") ? 2 : eq(lBuff.ptr, "1") ? 1 : 0;
4537 
4538     /* printf( "lResult: %d\n" , lResult ) ; */
4539     free(str);
4540     return lResult;
4541 }
4542 
4543 int _notifyPopup(
4544     const char* aTitle,
4545     const char* aMessage,
4546     const char* aIconType)
4547 {
4548     char[MAX_PATH_OR_CMD] lBuff = '\0';
4549     char* str;
4550     char* lpDialogString;
4551     FILE* lIn;
4552     size_t lTitleLen;
4553     size_t lMessageLen;
4554     bool lQuery;
4555 
4556     if (getenv("SSH_TTY"))
4557     {
4558         return _messageBox(aTitle, aMessage, "ok", aIconType, 0);
4559     }
4560 
4561     lQuery = eq(aTitle, "tinyfd_query");
4562     lTitleLen = aTitle ? strlen(aTitle) : 0;
4563     lMessageLen = aMessage ? strlen(aMessage) : 0;
4564     if (!aTitle || !lQuery)
4565     {
4566         str = cast(char*)malloc(MAX_PATH_OR_CMD + lTitleLen + lMessageLen);
4567     }
4568 
4569     if (osascriptPresent())
4570     {
4571         if (lQuery)
4572         {
4573             response("applescript");
4574             return 1;
4575         }
4576 
4577         strcpy(str, "osascript ");
4578         if (!osx9orBetter())
4579             strcat(str, " -e 'tell application \"System Events\"' -e 'Activate'");
4580         strcat(str, " -e 'try' -e 'display notification \"");
4581         if (some(aMessage))
4582         {
4583             strcat(str, aMessage);
4584         }
4585         strcat(str, " \" ");
4586         if (some(aTitle))
4587         {
4588             strcat(str, "with title \"");
4589             strcat(str, aTitle);
4590             strcat(str, "\" ");
4591         }
4592 
4593         strcat(str, "' -e 'end try'");
4594         if (!osx9orBetter())
4595             strcat(str, " -e 'end tell'");
4596     }
4597     else if (kdialogPresent())
4598     {
4599         if (lQuery)
4600         {
4601             response("kdialog");
4602             return 1;
4603         }
4604         strcpy(str, "kdialog");
4605 
4606         if (some(aIconType))
4607         {
4608             strcat(str, " --icon '");
4609             strcat(str, aIconType);
4610             strcat(str, "'");
4611         }
4612         if (some(aTitle))
4613         {
4614             strcat(str, " --title \"");
4615             strcat(str, aTitle);
4616             strcat(str, "\"");
4617         }
4618 
4619         strcat(str, " --passivepopup");
4620         strcat(str, " \"");
4621         if (aMessage)
4622         {
4623             strcat(str, aMessage);
4624         }
4625         strcat(str, " \" 5");
4626     }
4627     else if ((zenity3Present() >= 5) || matedialogPresent() || shellementaryPresent() || qarmaPresent())
4628     {
4629         /* zenity 2.32 & 3.14 has the notification but with a bug: it doesnt return from it */
4630         /* zenity 3.8 show the notification as an alert ok cancel box */
4631         if (zenity3Present() >= 5)
4632         {
4633             if (lQuery)
4634             {
4635                 response("zenity");
4636                 return 1;
4637             }
4638             strcpy(str, "zenity");
4639         }
4640         else if (matedialogPresent())
4641         {
4642             if (lQuery)
4643             {
4644                 response("matedialog");
4645                 return 1;
4646             }
4647             strcpy(str, "matedialog");
4648         }
4649         else if (shellementaryPresent())
4650         {
4651             if (lQuery)
4652             {
4653                 response("shellementary");
4654                 return 1;
4655             }
4656             strcpy(str, "shellementary");
4657         }
4658         else
4659         {
4660             if (lQuery)
4661             {
4662                 response("qarma");
4663                 return 1;
4664             }
4665             strcpy(str, "qarma");
4666         }
4667 
4668         strcat(str, " --notification");
4669 
4670         if (some(aIconType))
4671         {
4672             strcat(str, " --window-icon '");
4673             strcat(str, aIconType);
4674             strcat(str, "'");
4675         }
4676 
4677         strcat(str, " --text \"");
4678         if (some(aTitle))
4679         {
4680             strcat(str, aTitle);
4681             strcat(str, "\n");
4682         }
4683         if (some(aMessage))
4684         {
4685             strcat(str, aMessage);
4686         }
4687         strcat(str, " \"");
4688     }
4689     else if (perlPresent() >= 2)
4690     {
4691         if (lQuery)
4692         {
4693             response("perl-dbus");
4694             return 1;
4695         }
4696         sprintf(str, `perl -e "use Net::DBus;
4697                     my \$sessionBus = Net::DBus->session;
4698                     my \$notificationsService = \$sessionBus->get_service('org.freedesktop.Notifications');
4699                     my \$notificationsObject = \$notificationsService->get_object('/org/freedesktop/Notifications',
4700                         'org.freedesktop.Notifications');
4701                     my \$notificationId;\$notificationId = \$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);"`,
4702                 aIconType ? aIconType : "", aTitle ? aTitle : "", aMessage ? aMessage : "");
4703     }
4704     else if (pythonDbusPresent())
4705     {
4706         if (lQuery)
4707         {
4708             response("python-dbus");
4709             return 1;
4710         }
4711         strcpy(str, gPythonName.ptr);
4712         strcat(str, " -c \"import dbus;bus=dbus.SessionBus();");
4713         strcat(str, "notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');");
4714         strcat(str, "notify=dbus.Interface(notif,'org.freedesktop.Notifications');");
4715         strcat(str, "notify.Notify('',0,'");
4716         if (some(aIconType))
4717         {
4718             strcat(str, aIconType);
4719         }
4720         strcat(str, "','");
4721         if (some(aTitle))
4722         {
4723             strcat(str, aTitle);
4724         }
4725         strcat(str, "','");
4726         if (some(aMessage))
4727         {
4728             lpDialogString = str + strlen(str);
4729             replaceSubStr(aMessage, "\n", "\\n", lpDialogString);
4730         }
4731         strcat(str, "','','',5000)\"");
4732     }
4733     else if (notifysendPresent())
4734     {
4735         if (lQuery)
4736         {
4737             response("notifysend");
4738             return 1;
4739         }
4740         strcpy(str, "notify-send");
4741         if (some(aIconType))
4742         {
4743             strcat(str, " -i '");
4744             strcat(str, aIconType);
4745             strcat(str, "'");
4746         }
4747         strcat(str, " \"");
4748         if (some(aTitle))
4749         {
4750             strcat(str, aTitle);
4751             strcat(str, " | ");
4752         }
4753         if (some(aMessage))
4754         {
4755             replaceSubStr(aMessage, "\n\t", " |  ", lBuff.ptr);
4756             replaceSubStr(aMessage, "\n", " | ", lBuff.ptr);
4757             replaceSubStr(aMessage, "\t", "  ", lBuff.ptr);
4758             strcat(str, lBuff.ptr);
4759         }
4760         strcat(str, "\"");
4761     }
4762     else
4763     {
4764         return _messageBox(aTitle, aMessage, "ok", aIconType, 0);
4765     }
4766 
4767     if (tinyfd_verbose)
4768         printf("str: %s\n", str);
4769 
4770     lIn = popen(str, "r");
4771     if (!lIn)
4772     {
4773         free(str);
4774         return 0;
4775     }
4776 
4777     pclose(lIn);
4778     free(str);
4779     return 1;
4780 }
4781 
4782 const(char*) _inputBox(
4783     const char* aTitle,
4784     const char* aMessage,
4785     const char* aDefaultInput)
4786 {
4787     static char[MAX_PATH_OR_CMD] lBuff = '\0';
4788     const bool lQuery = eq(aTitle, "tinyfd_query");
4789     char* str;
4790     char* lpDialogString;
4791     FILE* lIn;
4792     int lResult;
4793     bool lWasGdialog;
4794     bool lWasGraphicDialog;
4795     bool lWasXterm;
4796     bool lWasBasicXterm;
4797     termios oldt;
4798     termios newt;
4799     char* lEOF;
4800     size_t lTitleLen;
4801     size_t lMessageLen;
4802 
4803     lBuff[0] = '\0';
4804 
4805     lTitleLen = aTitle ? strlen(aTitle) : 0;
4806     lMessageLen = aMessage ? strlen(aMessage) : 0;
4807     if (!aTitle || !lQuery)
4808     {
4809         str = cast(char*)malloc(MAX_PATH_OR_CMD + lTitleLen + lMessageLen);
4810     }
4811 
4812     if (osascriptPresent())
4813     {
4814         if (lQuery)
4815         {
4816             response("applescript");
4817             return cast(const(char)*)1;
4818         }
4819         strcpy(str, "osascript ");
4820         if (!osx9orBetter())
4821             strcat(str, " -e 'tell application \"System Events\"' -e 'Activate'");
4822         strcat(str, " -e 'try' -e 'display dialog \"");
4823         if (some(aMessage))
4824         {
4825             strcat(str, aMessage);
4826         }
4827         strcat(str, "\" ");
4828         strcat(str, "default answer \"");
4829         if (some(aDefaultInput))
4830         {
4831             strcat(str, aDefaultInput);
4832         }
4833         strcat(str, "\" ");
4834         if (!aDefaultInput)
4835         {
4836             strcat(str, "hidden answer true ");
4837         }
4838         if (some(aTitle))
4839         {
4840             strcat(str, "with title \"");
4841             strcat(str, aTitle);
4842             strcat(str, "\" ");
4843         }
4844         strcat(str, "with icon note' ");
4845         strcat(str, "-e '\"1\" & text returned of result' ");
4846         strcat(str, "-e 'on error number -128' ");
4847         strcat(str, "-e '0' ");
4848         strcat(str, "-e 'end try'");
4849         if (!osx9orBetter())
4850             strcat(str, " -e 'end tell'");
4851     }
4852     else if (kdialogPresent())
4853     {
4854         if (lQuery)
4855         {
4856             response("kdialog");
4857             return cast(const(char)*)1;
4858         }
4859         strcpy(str, "szAnswer=$(kdialog");
4860 
4861         if (kdialogPresent() == 2)
4862         {
4863             strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
4864         }
4865 
4866         if (!aDefaultInput)
4867         {
4868             strcat(str, " --password ");
4869         }
4870         else
4871         {
4872             strcat(str, " --inputbox ");
4873         }
4874         strcat(str, "\"");
4875         if (some(aMessage))
4876         {
4877             strcat(str, aMessage);
4878         }
4879         strcat(str, "\" \"");
4880         if (some(aDefaultInput))
4881         {
4882             strcat(str, aDefaultInput);
4883         }
4884         strcat(str, "\"");
4885         if (some(aTitle))
4886         {
4887             strcat(str, " --title \"");
4888             strcat(str, aTitle);
4889             strcat(str, "\"");
4890         }
4891         strcat(str, ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi");
4892     }
4893     else if (zenityPresent() || matedialogPresent() || shellementaryPresent() || qarmaPresent())
4894     {
4895         if (zenityPresent())
4896         {
4897             if (lQuery)
4898             {
4899                 response("zenity");
4900                 return cast(const(char)*)1;
4901             }
4902             strcpy(str, "szAnswer=$(zenity");
4903             if (zenity3Present() >= 4 && !getenv("SSH_TTY"))
4904             {
4905                 strcat(str, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
4906             }
4907         }
4908         else if (matedialogPresent())
4909         {
4910             if (lQuery)
4911             {
4912                 response("matedialog");
4913                 return cast(const(char)*)1;
4914             }
4915             strcpy(str, "szAnswer=$(matedialog");
4916         }
4917         else if (shellementaryPresent())
4918         {
4919             if (lQuery)
4920             {
4921                 response("shellementary");
4922                 return cast(const(char)*)1;
4923             }
4924             strcpy(str, "szAnswer=$(shellementary");
4925         }
4926         else
4927         {
4928             if (lQuery)
4929             {
4930                 response("qarma");
4931                 return cast(const(char)*)1;
4932             }
4933             strcpy(str, "szAnswer=$(qarma");
4934             if (!getenv("SSH_TTY"))
4935             {
4936                 strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
4937             }
4938         }
4939         strcat(str, " --entry");
4940 
4941         if (some(aTitle))
4942         {
4943             strcat(str, " --title=\"");
4944             strcat(str, aTitle);
4945             strcat(str, "\"");
4946         }
4947         if (some(aMessage))
4948         {
4949             strcat(str, " --text=\"");
4950             strcat(str, aMessage);
4951             strcat(str, "\"");
4952         }
4953         if (some(aDefaultInput))
4954         {
4955             strcat(str, " --entry-text=\"");
4956             strcat(str, aDefaultInput);
4957             strcat(str, "\"");
4958         }
4959         else
4960         {
4961             strcat(str, " --hide-text");
4962         }
4963         if (tinyfd_silent)
4964             strcat(str, " 2>/dev/null ");
4965         strcat(str, ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi");
4966     }
4967     else if (gxmessagePresent() || gmessagePresent())
4968     {
4969         if (gxmessagePresent())
4970         {
4971             if (lQuery)
4972             {
4973                 response("gxmessage");
4974                 return cast(const(char)*)1;
4975             }
4976             strcpy(str, "szAnswer=$(gxmessage -buttons Ok:1,Cancel:0 -center \"");
4977         }
4978         else
4979         {
4980             if (lQuery)
4981             {
4982                 response("gmessage");
4983                 return cast(const(char)*)1;
4984             }
4985             strcpy(str, "szAnswer=$(gmessage -buttons Ok:1,Cancel:0 -center \"");
4986         }
4987 
4988         if (some(aMessage))
4989         {
4990             strcat(str, aMessage);
4991         }
4992         strcat(str, "\"");
4993         if (some(aTitle))
4994         {
4995             strcat(str, " -title  \"");
4996             strcat(str, aTitle);
4997             strcat(str, "\" ");
4998         }
4999         strcat(str, " -entrytext \"");
5000         if (some(aDefaultInput))
5001         {
5002             strcat(str, aDefaultInput);
5003         }
5004         strcat(str, "\"");
5005         strcat(str, ");echo $?$szAnswer");
5006     }
5007     else if (!gdialogPresent() && !xdialogPresent() && tkinter2Present())
5008     {
5009         if (lQuery)
5010         {
5011             response("python2-tkinter");
5012             return cast(const(char)*)1;
5013         }
5014         strcpy(str, gPython2Name.ptr);
5015         if (!isTerminalRunning() && isDarwin())
5016         {
5017             strcat(str, " -i"); /* for osx without console */
5018         }
5019 
5020         strcat(str,
5021                " -S -c \"import Tkinter,tkSimpleDialog;root=Tkinter.Tk();root.withdraw();");
5022 
5023         if (isDarwin())
5024         {
5025             strcat(str,
5026                    "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set " ~
5027                    "frontmost of process \\\"Python\\\" to true' ''');");
5028         }
5029 
5030         strcat(str, "res=tkSimpleDialog.askstring(");
5031         if (some(aTitle))
5032         {
5033             strcat(str, "title='");
5034             strcat(str, aTitle);
5035             strcat(str, "',");
5036         }
5037         if (some(aMessage))
5038         {
5039             strcat(str, "prompt='");
5040             lpDialogString = str + strlen(str);
5041             replaceSubStr(aMessage, "\n", "\\n", lpDialogString);
5042             strcat(str, "',");
5043         }
5044         if (aDefaultInput)
5045         {
5046             if (some(aDefaultInput))
5047             {
5048                 strcat(str, "initialvalue='");
5049                 strcat(str, aDefaultInput);
5050                 strcat(str, "',");
5051             }
5052         }
5053         else
5054         {
5055             strcat(str, "show='*'");
5056         }
5057         strcat(str, ");\nif res is None :\n\tprint 0");
5058         strcat(str, "\nelse :\n\tprint '1'+res\n\"");
5059     }
5060     else if (!gdialogPresent() && !xdialogPresent() && tkinter3Present())
5061     {
5062         if (lQuery)
5063         {
5064             response("python3-tkinter");
5065             return cast(const(char)*)1;
5066         }
5067         strcpy(str, gPython3Name.ptr);
5068         strcat(str,
5069                " -S -c \"import tkinter; from tkinter import simpledialog;root=tkinter.Tk();root.withdraw();");
5070         strcat(str, "res=simpledialog.askstring(");
5071         if (some(aTitle))
5072         {
5073             strcat(str, "title='");
5074             strcat(str, aTitle);
5075             strcat(str, "',");
5076         }
5077         if (some(aMessage))
5078         {
5079             strcat(str, "prompt='");
5080             lpDialogString = str + strlen(str);
5081             replaceSubStr(aMessage, "\n", "\\n", lpDialogString);
5082             strcat(str, "',");
5083         }
5084         if (aDefaultInput)
5085         {
5086             if (some(aDefaultInput))
5087             {
5088                 strcat(str, "initialvalue='");
5089                 strcat(str, aDefaultInput);
5090                 strcat(str, "',");
5091             }
5092         }
5093         else
5094         {
5095             strcat(str, "show='*'");
5096         }
5097         strcat(str, ");\nif res is None :\n\tprint(0)");
5098         strcat(str, "\nelse :\n\tprint('1'+res)\n\"");
5099     }
5100     else if (gdialogPresent() || xdialogPresent() || dialogName() || whiptailPresent())
5101     {
5102         if (gdialogPresent())
5103         {
5104             if (lQuery)
5105             {
5106                 response("gdialog");
5107                 return cast(const(char)*)1;
5108             }
5109             lWasGraphicDialog = true;
5110             lWasGdialog = true;
5111             strcpy(str, "(gdialog ");
5112         }
5113         else if (xdialogPresent())
5114         {
5115             if (lQuery)
5116             {
5117                 response("xdialog");
5118                 return cast(const(char)*)1;
5119             }
5120             lWasGraphicDialog = true;
5121             strcpy(str, "(Xdialog ");
5122         }
5123         else if (dialogName())
5124         {
5125             if (lQuery)
5126             {
5127                 response("dialog");
5128                 return cast(const(char)*)0;
5129             }
5130             if (isTerminalRunning())
5131             {
5132                 strcpy(str, "(dialog ");
5133             }
5134             else
5135             {
5136                 lWasXterm = true;
5137                 strcpy(str, terminalName());
5138                 strcat(str, "'(");
5139                 strcat(str, dialogName());
5140                 strcat(str, " ");
5141             }
5142         }
5143         else if (isTerminalRunning())
5144         {
5145             if (lQuery)
5146             {
5147                 response("whiptail");
5148                 return cast(const(char)*)0;
5149             }
5150             strcpy(str, "(whiptail ");
5151         }
5152         else
5153         {
5154             if (lQuery)
5155             {
5156                 response("whiptail");
5157                 return cast(const(char)*)0;
5158             }
5159             lWasXterm = true;
5160             strcpy(str, terminalName());
5161             strcat(str, "'(whiptail ");
5162         }
5163 
5164         if (some(aTitle))
5165         {
5166             strcat(str, "--title \"");
5167             strcat(str, aTitle);
5168             strcat(str, "\" ");
5169         }
5170 
5171         if (!xdialogPresent() && !gdialogPresent())
5172         {
5173             strcat(str, "--backtitle \"");
5174             strcat(str, "tab: move focus");
5175             if (!aDefaultInput && !lWasGdialog)
5176             {
5177                 strcat(str, " (sometimes nothing, no blink nor star, is shown in text field)");
5178             }
5179             strcat(str, "\" ");
5180         }
5181 
5182         if (aDefaultInput || lWasGdialog)
5183         {
5184             strcat(str, "--inputbox");
5185         }
5186         else
5187         {
5188             if (!lWasGraphicDialog && dialogName() && isDialogVersionBetter09b())
5189             {
5190                 strcat(str, "--insecure ");
5191             }
5192             strcat(str, "--passwordbox");
5193         }
5194         strcat(str, " \"");
5195         if (some(aMessage))
5196         {
5197             strcat(str, aMessage);
5198         }
5199         strcat(str, "\" 10 60 ");
5200         if (some(aDefaultInput))
5201         {
5202             strcat(str, "\"");
5203             strcat(str, aDefaultInput);
5204             strcat(str, "\" ");
5205         }
5206         if (lWasGraphicDialog)
5207         {
5208             strcat(str, ") 2>/tmp/tinyfd.txt;" ~
5209                         "if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;" ~
5210                         "tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes");
5211         }
5212         else
5213         {
5214             strcat(str, ">/dev/tty ) 2>/tmp/tinyfd.txt;" ~
5215                         "if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;" ~
5216                         "tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes");
5217 
5218             if (lWasXterm)
5219             {
5220                 strcat(str, " >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt");
5221             }
5222             else
5223             {
5224                 strcat(str, "; clear >/dev/tty");
5225             }
5226         }
5227     }
5228     else if (!isTerminalRunning() && terminalName())
5229     {
5230         if (lQuery)
5231         {
5232             response("basicinput");
5233             return cast(const(char)*)0;
5234         }
5235         lWasBasicXterm = true;
5236         strcpy(str, terminalName());
5237         strcat(str, "'");
5238         if (!gWarningDisplayed && !tinyfd_forceConsole)
5239         {
5240             gWarningDisplayed = true;
5241             _messageBox(gTitle.ptr, tinyfd_needs.ptr, "ok", "warning", 0);
5242         }
5243         if (some(aTitle) && !tinyfd_forceConsole)
5244         {
5245             strcat(str, "echo \"");
5246             strcat(str, aTitle);
5247             strcat(str, "\";echo;");
5248         }
5249 
5250         strcat(str, "echo \"");
5251         if (some(aMessage))
5252         {
5253             strcat(str, aMessage);
5254         }
5255         strcat(str, "\";read ");
5256         if (!aDefaultInput)
5257         {
5258             strcat(str, "-s ");
5259         }
5260         strcat(str, "-p \"");
5261         strcat(str, "(esc+enter to cancel): \" ANSWER ");
5262         strcat(str, ";echo 1$ANSWER >/tmp/tinyfd.txt';");
5263         strcat(str, "cat -v /tmp/tinyfd.txt");
5264     }
5265     else if (!gWarningDisplayed && !isTerminalRunning() && !terminalName())
5266     {
5267         gWarningDisplayed = true;
5268         _messageBox(gTitle.ptr, tinyfd_needs.ptr, "ok", "warning", 0);
5269         if (lQuery)
5270         {
5271             response("no_solution");
5272             return cast(const(char)*)0;
5273         }
5274         return null;
5275     }
5276     else
5277     {
5278         if (lQuery)
5279         {
5280             response("basicinput");
5281             return cast(const(char)*)0;
5282         }
5283         if (!gWarningDisplayed && !tinyfd_forceConsole)
5284         {
5285             gWarningDisplayed = true;
5286             _messageBox(gTitle.ptr, tinyfd_needs.ptr, "ok", "warning", 0);
5287         }
5288         if (some(aTitle))
5289         {
5290             printf("\n%s\n", aTitle);
5291         }
5292         if (some(aMessage))
5293         {
5294             printf("\n%s\n", aMessage);
5295         }
5296         printf("(esc+enter to cancel): ");
5297         fflush(stdout);
5298         if (!aDefaultInput)
5299         {
5300             tcgetattr(STDIN_FILENO, &oldt);
5301             newt = oldt;
5302             newt.c_lflag &= ~ECHO;
5303             tcsetattr(STDIN_FILENO, TCSANOW, &newt);
5304         }
5305 
5306         lEOF = fgets(lBuff.ptr, MAX_PATH_OR_CMD, stdin);
5307         /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */
5308         if (!lEOF || (lBuff[0] == '\0'))
5309         {
5310             free(str);
5311             return null;
5312         }
5313 
5314         if (lBuff[0] == '\n')
5315         {
5316             lEOF = fgets(lBuff.ptr, MAX_PATH_OR_CMD, stdin);
5317             /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */
5318             if (!lEOF || (lBuff[0] == '\0'))
5319             {
5320                 free(str);
5321                 return null;
5322             }
5323         }
5324 
5325         if (!aDefaultInput)
5326         {
5327             tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
5328             printf("\n");
5329         }
5330         printf("\n");
5331         if (strchr(lBuff.ptr, 27))
5332         {
5333             free(str);
5334             return null;
5335         }
5336         removeLastNL(lBuff.ptr);
5337         free(str);
5338         return lBuff.ptr;
5339     }
5340 
5341     if (tinyfd_verbose)
5342         printf("str: %s\n", str);
5343     lIn = popen(str, "r");
5344     if (!lIn)
5345     {
5346         if (fileExists("/tmp/tinyfd.txt"))
5347         {
5348             wipefile("/tmp/tinyfd.txt");
5349             remove("/tmp/tinyfd.txt");
5350         }
5351         if (fileExists("/tmp/tinyfd0.txt"))
5352         {
5353             wipefile("/tmp/tinyfd0.txt");
5354             remove("/tmp/tinyfd0.txt");
5355         }
5356         free(str);
5357         return null;
5358     }
5359     while (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
5360     {
5361     }
5362 
5363     pclose(lIn);
5364 
5365     if (fileExists("/tmp/tinyfd.txt"))
5366     {
5367         wipefile("/tmp/tinyfd.txt");
5368         remove("/tmp/tinyfd.txt");
5369     }
5370     if (fileExists("/tmp/tinyfd0.txt"))
5371     {
5372         wipefile("/tmp/tinyfd0.txt");
5373         remove("/tmp/tinyfd0.txt");
5374     }
5375 
5376     /* printf( "len Buff: %lu\n" , strlen(lBuff.ptr) ) ; */
5377     /* printf( "lBuff0: %s\n" , lBuff ) ; */
5378     removeLastNL(lBuff.ptr);
5379     /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff.ptr) ) ; */
5380     if (lWasBasicXterm)
5381     {
5382         if (strstr(lBuff.ptr, "^[")) /* esc was pressed */
5383         {
5384             free(str);
5385             return null;
5386         }
5387     }
5388 
5389     lResult = strncmp(lBuff.ptr, "1", 1) ? 0 : 1;
5390     /* printf( "lResult: %d \n" , lResult ) ; */
5391     if (!lResult)
5392     {
5393         free(str);
5394         return null;
5395     }
5396     /* printf( "lBuff+1: %s\n" , lBuff+1 ) ; */
5397     free(str);
5398 
5399     return lBuff.ptr + 1;
5400 }
5401 
5402 const(char*) _saveFileDialog(
5403     const char* aTitle,
5404     const char* aDefaultPathAndFile,
5405     const int aNumOfFilterPatterns,
5406     const char** aFilterPatterns,
5407     const char* aSingleFilterDescription)
5408 {
5409     static char[MAX_PATH_OR_CMD] lBuff = '\0';
5410     const bool lQuery = eq(aTitle, "tinyfd_query");
5411     char[MAX_PATH_OR_CMD] str_buf1 = '\0';
5412     char[MAX_PATH_OR_CMD] str_buf2 = '\0';
5413     char* str = str_buf1.ptr;
5414     char* lString = str_buf2.ptr;
5415     bool lWasGraphicDialog;
5416     bool lWasXterm;
5417     const(char)* p;
5418     FILE* lIn;
5419     lBuff[0] = '\0';
5420 
5421     if (osascriptPresent())
5422     {
5423         if (lQuery)
5424         {
5425             response("applescript");
5426             return cast(const(char)*)1;
5427         }
5428         strcpy(str, "osascript ");
5429         if (!osx9orBetter())
5430             strcat(str, " -e 'tell application \"Finder\"' -e 'Activate'");
5431         strcat(str, " -e 'try' -e 'POSIX path of ( choose file name ");
5432         if (some(aTitle))
5433         {
5434             strcat(str, "with prompt \"");
5435             strcat(str, aTitle);
5436             strcat(str, "\" ");
5437         }
5438         getPathWithoutFinalSlash(lString, aDefaultPathAndFile);
5439         if (some(lString))
5440         {
5441             strcat(str, "default location \"");
5442             strcat(str, lString);
5443             strcat(str, "\" ");
5444         }
5445         getLastName(lString, aDefaultPathAndFile);
5446         if (some(lString))
5447         {
5448             strcat(str, "default name \"");
5449             strcat(str, lString);
5450             strcat(str, "\" ");
5451         }
5452         strcat(str, ")' ");
5453         strcat(str, "-e 'on error number -128' ");
5454         strcat(str, "-e 'end try'");
5455         if (!osx9orBetter())
5456             strcat(str, " -e 'end tell'");
5457     }
5458     else if (kdialogPresent())
5459     {
5460         if (lQuery)
5461         {
5462             response("kdialog");
5463             return cast(const(char)*)1;
5464         }
5465 
5466         strcpy(str, "kdialog");
5467         if (kdialogPresent() == 2)
5468         {
5469             strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
5470         }
5471         strcat(str, " --getsavefilename ");
5472 
5473         if (some(aDefaultPathAndFile))
5474         {
5475             if (aDefaultPathAndFile[0] != '/')
5476             {
5477                 strcat(str, "$PWD/");
5478             }
5479             strcat(str, "\"");
5480             strcat(str, aDefaultPathAndFile);
5481             strcat(str, "\"");
5482         }
5483         else
5484         {
5485             strcat(str, "$PWD/");
5486         }
5487 
5488         if (aNumOfFilterPatterns > 0)
5489         {
5490             bool pattern; // otherwise MIME-type filter
5491             foreach (i; 0 .. aNumOfFilterPatterns)
5492             {
5493                 pattern = strchr(aFilterPatterns[i], '*') !is null;
5494                 if (pattern)
5495                     break;
5496             }
5497             strcat(str, " \"");
5498             if (pattern)
5499             {
5500                 if (some(aSingleFilterDescription))
5501                 {
5502                     strcat(str, aSingleFilterDescription);
5503                 }
5504                 strcat(str, "(");
5505             }
5506             foreach (i; 0 .. aNumOfFilterPatterns)
5507             {
5508                 if (i != 0)
5509                     strcat(str, " ");
5510                 strcat(str, aFilterPatterns[i]);
5511             }
5512             if (pattern)
5513             {
5514                 strcat(str, ")");
5515             }
5516             strcat(str, "\"");
5517         }
5518         if (some(aTitle))
5519         {
5520             strcat(str, " --title \"");
5521             strcat(str, aTitle);
5522             strcat(str, "\"");
5523         }
5524     }
5525     else if (zenityPresent() || matedialogPresent() || shellementaryPresent() || qarmaPresent())
5526     {
5527         if (zenityPresent())
5528         {
5529             if (lQuery)
5530             {
5531                 response("zenity");
5532                 return cast(const(char)*)1;
5533             }
5534             strcpy(str, "zenity");
5535             if (zenity3Present() >= 4 && !getenv("SSH_TTY"))
5536             {
5537                 strcat(str, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
5538             }
5539         }
5540         else if (matedialogPresent())
5541         {
5542             if (lQuery)
5543             {
5544                 response("matedialog");
5545                 return cast(const(char)*)1;
5546             }
5547             strcpy(str, "matedialog");
5548         }
5549         else if (shellementaryPresent())
5550         {
5551             if (lQuery)
5552             {
5553                 response("shellementary");
5554                 return cast(const(char)*)1;
5555             }
5556             strcpy(str, "shellementary");
5557         }
5558         else
5559         {
5560             if (lQuery)
5561             {
5562                 response("qarma");
5563                 return cast(const(char)*)1;
5564             }
5565             strcpy(str, "qarma");
5566             if (!getenv("SSH_TTY"))
5567             {
5568                 strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
5569             }
5570         }
5571         strcat(str, " --file-selection --save --confirm-overwrite");
5572 
5573         if (some(aTitle))
5574         {
5575             strcat(str, " --title=\"");
5576             strcat(str, aTitle);
5577             strcat(str, "\"");
5578         }
5579         if (some(aDefaultPathAndFile))
5580         {
5581             strcat(str, " --filename=\"");
5582             strcat(str, aDefaultPathAndFile);
5583             strcat(str, "\"");
5584         }
5585         if (aNumOfFilterPatterns > 0)
5586         {
5587             strcat(str, " --file-filter='");
5588             if (some(aSingleFilterDescription))
5589             {
5590                 strcat(str, aSingleFilterDescription);
5591                 strcat(str, " | ");
5592             }
5593             foreach (i; 0 .. aNumOfFilterPatterns)
5594             {
5595                 strcat(str, aFilterPatterns[i]);
5596                 strcat(str, " ");
5597             }
5598             strcat(str, "' --file-filter='All files | *'");
5599         }
5600         if (tinyfd_silent)
5601             strcat(str, " 2>/dev/null ");
5602     }
5603     else if (!xdialogPresent() && tkinter2Present())
5604     {
5605         if (lQuery)
5606         {
5607             response("python2-tkinter");
5608             return cast(const(char)*)1;
5609         }
5610         strcpy(str, gPython2Name.ptr);
5611         if (!isTerminalRunning() && isDarwin())
5612         {
5613             strcat(str, " -i"); /* for osx without console */
5614         }
5615         strcat(str,
5616                " -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();");
5617 
5618         if (isDarwin())
5619         {
5620             strcat(str,
5621                    "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set " ~
5622                    "frontmost of process \\\"Python\\\" to true' ''');");
5623         }
5624 
5625         strcat(str, "print tkFileDialog.asksaveasfilename(");
5626         if (some(aTitle))
5627         {
5628             strcat(str, "title='");
5629             strcat(str, aTitle);
5630             strcat(str, "',");
5631         }
5632         if (some(aDefaultPathAndFile))
5633         {
5634             getPathWithoutFinalSlash(lString, aDefaultPathAndFile);
5635             if (some(lString))
5636             {
5637                 strcat(str, "initialdir='");
5638                 strcat(str, lString);
5639                 strcat(str, "',");
5640             }
5641             getLastName(lString, aDefaultPathAndFile);
5642             if (some(lString))
5643             {
5644                 strcat(str, "initialfile='");
5645                 strcat(str, lString);
5646                 strcat(str, "',");
5647             }
5648         }
5649         if (aNumOfFilterPatterns > 1 || (aNumOfFilterPatterns == 1 // test because poor osx behaviour
5650                                          && lastch(aFilterPatterns[0]) != '*'))
5651         {
5652             strcat(str, "filetypes=(");
5653             strcat(str, "('");
5654             if (some(aSingleFilterDescription))
5655             {
5656                 strcat(str, aSingleFilterDescription);
5657             }
5658             strcat(str, "',(");
5659             foreach (i; 0 .. aNumOfFilterPatterns)
5660             {
5661                 strcat(str, "'");
5662                 strcat(str, aFilterPatterns[i]);
5663                 strcat(str, "',");
5664             }
5665             strcat(str, ")),");
5666             strcat(str, "('All files','*'))");
5667         }
5668         strcat(str, ")\"");
5669     }
5670     else if (!xdialogPresent() && tkinter3Present())
5671     {
5672         if (lQuery)
5673         {
5674             response("python3-tkinter");
5675             return cast(const(char)*)1;
5676         }
5677         strcpy(str, gPython3Name.ptr);
5678         strcat(str,
5679                " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();");
5680         strcat(str, "print( filedialog.asksaveasfilename(");
5681         if (some(aTitle))
5682         {
5683             strcat(str, "title='");
5684             strcat(str, aTitle);
5685             strcat(str, "',");
5686         }
5687         if (some(aDefaultPathAndFile))
5688         {
5689             getPathWithoutFinalSlash(lString, aDefaultPathAndFile);
5690             if (some(lString))
5691             {
5692                 strcat(str, "initialdir='");
5693                 strcat(str, lString);
5694                 strcat(str, "',");
5695             }
5696             getLastName(lString, aDefaultPathAndFile);
5697             if (some(lString))
5698             {
5699                 strcat(str, "initialfile='");
5700                 strcat(str, lString);
5701                 strcat(str, "',");
5702             }
5703         }
5704         if (aNumOfFilterPatterns > 1 || (aNumOfFilterPatterns == 1 // test because poor osx behaviour
5705                                          && lastch(aFilterPatterns[0]) != '*'))
5706         {
5707             strcat(str, "filetypes=(");
5708             strcat(str, "('");
5709             if (some(aSingleFilterDescription))
5710             {
5711                 strcat(str, aSingleFilterDescription);
5712             }
5713             strcat(str, "',(");
5714             foreach (i; 0 .. aNumOfFilterPatterns)
5715             {
5716                 strcat(str, "'");
5717                 strcat(str, aFilterPatterns[i]);
5718                 strcat(str, "',");
5719             }
5720             strcat(str, ")),");
5721             strcat(str, "('All files','*'))");
5722         }
5723         strcat(str, "))\"");
5724     }
5725     else if (xdialogPresent() || dialogName())
5726     {
5727         if (xdialogPresent())
5728         {
5729             if (lQuery)
5730             {
5731                 response("xdialog");
5732                 return cast(const(char)*)1;
5733             }
5734             lWasGraphicDialog = true;
5735             strcpy(str, "(Xdialog ");
5736         }
5737         else if (isTerminalRunning())
5738         {
5739             if (lQuery)
5740             {
5741                 response("dialog");
5742                 return cast(const(char)*)0;
5743             }
5744             strcpy(str, "(dialog ");
5745         }
5746         else
5747         {
5748             if (lQuery)
5749             {
5750                 response("dialog");
5751                 return cast(const(char)*)0;
5752             }
5753             lWasXterm = true;
5754             strcpy(str, terminalName());
5755             strcat(str, "'(");
5756             strcat(str, dialogName());
5757             strcat(str, " ");
5758         }
5759 
5760         if (some(aTitle))
5761         {
5762             strcat(str, "--title \"");
5763             strcat(str, aTitle);
5764             strcat(str, "\" ");
5765         }
5766 
5767         if (!xdialogPresent() && !gdialogPresent())
5768         {
5769             strcat(str, "--backtitle \"");
5770             strcat(str,
5771                    "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY");
5772             strcat(str, "\" ");
5773         }
5774 
5775         strcat(str, "--fselect \"");
5776         if (some(aDefaultPathAndFile))
5777         {
5778             if (!strchr(aDefaultPathAndFile, '/'))
5779             {
5780                 strcat(str, "./");
5781             }
5782             strcat(str, aDefaultPathAndFile);
5783         }
5784         else if (!isTerminalRunning() && !lWasGraphicDialog)
5785         {
5786             strcat(str, getenv("HOME"));
5787             strcat(str, "/");
5788         }
5789         else
5790         {
5791             strcat(str, "./");
5792         }
5793 
5794         if (lWasGraphicDialog)
5795         {
5796             strcat(str, "\" 0 60 ) 2>&1 ");
5797         }
5798         else
5799         {
5800             strcat(str, "\" 0 60  >/dev/tty) ");
5801             if (lWasXterm)
5802             {
5803                 strcat(str,
5804                        "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt");
5805             }
5806             else
5807             {
5808                 strcat(str, "2>&1 ; clear >/dev/tty");
5809             }
5810         }
5811     }
5812     else
5813     {
5814         if (lQuery)
5815         {
5816             return _inputBox(aTitle, null, null);
5817         }
5818         p = _inputBox(aTitle, "Save file", "");
5819         getPathWithoutFinalSlash(lString, p);
5820         if (!dirExists(lString))
5821             return null;
5822         getLastName(lString, p);
5823         if (!some(lString))
5824             return null;
5825         return p;
5826     }
5827 
5828     if (tinyfd_verbose)
5829         printf("str: %s\n", str);
5830     lIn = popen(str, "r");
5831     if (!lIn)
5832         return null;
5833     while (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
5834     {
5835     }
5836     pclose(lIn);
5837     removeLastNL(lBuff.ptr);
5838     /* printf( "lBuff: %s\n" , lBuff ) ; */
5839     if (!some(lBuff.ptr))
5840         return null;
5841     getPathWithoutFinalSlash(lString, lBuff.ptr);
5842     if (!dirExists(lString))
5843         return null;
5844     getLastName(lString, lBuff.ptr);
5845     if (!filenameValid(lString))
5846         return null;
5847     return lBuff.ptr;
5848 }
5849 
5850 const(char*) _openFileDialog(
5851     const char* aTitle,
5852     const char* aDefaultPathAndFile,
5853     const int aNumOfFilterPatterns,
5854     const char** aFilterPatterns,
5855     const char* aSingleFilterDescription,
5856     const bool aAllowMultipleSelects)
5857 {
5858     static char[MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD] lBuff = '\0';
5859     const bool lQuery = eq(aTitle, "tinyfd_query");
5860     char[MAX_PATH_OR_CMD] str_buf1 = '\0';
5861     char[MAX_PATH_OR_CMD] str_buf2 = '\0';
5862     char* str = str_buf1.ptr;
5863     char* lString = str_buf2.ptr;
5864     FILE* lIn;
5865     char* p;
5866     const(char)* p2;
5867     bool lWasKdialog;
5868     bool lWasGraphicDialog;
5869     bool lWasXterm;
5870     lBuff[0] = '\0';
5871 
5872     if (osascriptPresent())
5873     {
5874         if (lQuery)
5875         {
5876             response("applescript");
5877             return cast(const(char)*)1;
5878         }
5879         strcpy(str, "osascript ");
5880         if (!osx9orBetter())
5881             strcat(str, " -e 'tell application \"System Events\"' -e 'Activate'");
5882         strcat(str, " -e 'try' -e '");
5883         if (!aAllowMultipleSelects)
5884         {
5885 
5886             strcat(str, "POSIX path of ( ");
5887         }
5888         else
5889         {
5890             strcat(str, "set mylist to ");
5891         }
5892         strcat(str, "choose file ");
5893         if (some(aTitle))
5894         {
5895             strcat(str, "with prompt \"");
5896             strcat(str, aTitle);
5897             strcat(str, "\" ");
5898         }
5899         getPathWithoutFinalSlash(lString, aDefaultPathAndFile);
5900         if (some(lString))
5901         {
5902             strcat(str, "default location \"");
5903             strcat(str, lString);
5904             strcat(str, "\" ");
5905         }
5906         if (aNumOfFilterPatterns > 0)
5907         {
5908             strcat(str, "of type {\"");
5909             strcat(str, aFilterPatterns[0] + 2);
5910             strcat(str, "\"");
5911             foreach (i; 1 .. aNumOfFilterPatterns)
5912             {
5913                 strcat(str, ",\"");
5914                 strcat(str, aFilterPatterns[i] + 2);
5915                 strcat(str, "\"");
5916             }
5917             strcat(str, "} ");
5918         }
5919         if (aAllowMultipleSelects)
5920         {
5921             strcat(str, "multiple selections allowed true ' ");
5922             strcat(str,
5923                    "-e 'set mystring to POSIX path of item 1 of mylist' ");
5924             strcat(str,
5925                    "-e 'repeat with  i from 2 to the count of mylist' ");
5926             strcat(str, "-e 'set mystring to mystring & \"|\"' ");
5927             strcat(str,
5928                    "-e 'set mystring to mystring & POSIX path of item i of mylist' ");
5929             strcat(str, "-e 'end repeat' ");
5930             strcat(str, "-e 'mystring' ");
5931         }
5932         else
5933         {
5934             strcat(str, ")' ");
5935         }
5936         strcat(str, "-e 'on error number -128' ");
5937         strcat(str, "-e 'end try'");
5938         if (!osx9orBetter())
5939             strcat(str, " -e 'end tell'");
5940     }
5941     else if (kdialogPresent())
5942     {
5943         if (lQuery)
5944         {
5945             response("kdialog");
5946             return cast(const(char)*)1;
5947         }
5948         lWasKdialog = true;
5949 
5950         strcpy(str, "kdialog");
5951         if (kdialogPresent() == 2)
5952         {
5953             strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
5954         }
5955         strcat(str, " --getopenfilename ");
5956 
5957         if (some(aDefaultPathAndFile))
5958         {
5959             if (aDefaultPathAndFile[0] != '/')
5960             {
5961                 strcat(str, "$PWD/");
5962             }
5963             strcat(str, "\"");
5964             strcat(str, aDefaultPathAndFile);
5965             strcat(str, "\"");
5966         }
5967         else
5968         {
5969             strcat(str, "$PWD/");
5970         }
5971 
5972         if (aNumOfFilterPatterns > 0)
5973         {
5974             bool pattern; // otherwise MIME-type filter
5975             foreach (i; 0 .. aNumOfFilterPatterns)
5976             {
5977                 pattern = strchr(aFilterPatterns[i], '*') !is null;
5978                 if (pattern)
5979                     break;
5980             }
5981             strcat(str, " \"");
5982             if (pattern)
5983             {
5984                 if (some(aSingleFilterDescription))
5985                 {
5986                     strcat(str, aSingleFilterDescription);
5987                 }
5988                 strcat(str, "(");
5989             }
5990             foreach (i; 0 .. aNumOfFilterPatterns)
5991             {
5992                 if (i != 0)
5993                     strcat(str, " ");
5994                 strcat(str, aFilterPatterns[i]);
5995             }
5996             if (pattern)
5997             {
5998                 strcat(str, ")");
5999             }
6000             strcat(str, "\"");
6001         }
6002         if (aAllowMultipleSelects)
6003         {
6004             strcat(str, " --multiple --separate-output");
6005         }
6006         if (some(aTitle))
6007         {
6008             strcat(str, " --title \"");
6009             strcat(str, aTitle);
6010             strcat(str, "\"");
6011         }
6012     }
6013     else if (zenityPresent() || matedialogPresent() || shellementaryPresent() || qarmaPresent())
6014     {
6015         if (zenityPresent())
6016         {
6017             if (lQuery)
6018             {
6019                 response("zenity");
6020                 return cast(const(char)*)1;
6021             }
6022             strcpy(str, "zenity");
6023             if (zenity3Present() >= 4 && !getenv("SSH_TTY"))
6024             {
6025                 strcat(str, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6026             }
6027         }
6028         else if (matedialogPresent())
6029         {
6030             if (lQuery)
6031             {
6032                 response("matedialog");
6033                 return cast(const(char)*)1;
6034             }
6035             strcpy(str, "matedialog");
6036         }
6037         else if (shellementaryPresent())
6038         {
6039             if (lQuery)
6040             {
6041                 response("shellementary");
6042                 return cast(const(char)*)1;
6043             }
6044             strcpy(str, "shellementary");
6045         }
6046         else
6047         {
6048             if (lQuery)
6049             {
6050                 response("qarma");
6051                 return cast(const(char)*)1;
6052             }
6053             strcpy(str, "qarma");
6054             if (!getenv("SSH_TTY"))
6055             {
6056                 strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6057             }
6058         }
6059         strcat(str, " --file-selection");
6060 
6061         if (aAllowMultipleSelects)
6062         {
6063             strcat(str, " --multiple");
6064         }
6065         if (some(aTitle))
6066         {
6067             strcat(str, " --title=\"");
6068             strcat(str, aTitle);
6069             strcat(str, "\"");
6070         }
6071         if (some(aDefaultPathAndFile))
6072         {
6073             strcat(str, " --filename=\"");
6074             strcat(str, aDefaultPathAndFile);
6075             strcat(str, "\"");
6076         }
6077         if (aNumOfFilterPatterns > 0)
6078         {
6079             strcat(str, " --file-filter='");
6080             if (some(aSingleFilterDescription))
6081             {
6082                 strcat(str, aSingleFilterDescription);
6083                 strcat(str, " | ");
6084             }
6085             foreach (i; 0 .. aNumOfFilterPatterns)
6086             {
6087                 strcat(str, aFilterPatterns[i]);
6088                 strcat(str, " ");
6089             }
6090             strcat(str, "' --file-filter='All files | *'");
6091         }
6092         if (tinyfd_silent)
6093             strcat(str, " 2>/dev/null ");
6094     }
6095     else if (tkinter2Present())
6096     {
6097         if (lQuery)
6098         {
6099             response("python2-tkinter");
6100             return cast(const(char)*)1;
6101         }
6102         strcpy(str, gPython2Name.ptr);
6103         if (!isTerminalRunning() && isDarwin())
6104         {
6105             strcat(str, " -i"); /* for osx without console */
6106         }
6107         strcat(str,
6108                " -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();");
6109 
6110         if (isDarwin())
6111         {
6112             strcat(str,
6113                    "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set " ~
6114                    "frontmost of process \\\"Python\\\" to true' ''');");
6115         }
6116         strcat(str, "lFiles=tkFileDialog.askopenfilename(");
6117         if (aAllowMultipleSelects)
6118         {
6119             strcat(str, "multiple=1,");
6120         }
6121         if (some(aTitle))
6122         {
6123             strcat(str, "title='");
6124             strcat(str, aTitle);
6125             strcat(str, "',");
6126         }
6127         if (some(aDefaultPathAndFile))
6128         {
6129             getPathWithoutFinalSlash(lString, aDefaultPathAndFile);
6130             if (some(lString))
6131             {
6132                 strcat(str, "initialdir='");
6133                 strcat(str, lString);
6134                 strcat(str, "',");
6135             }
6136             getLastName(lString, aDefaultPathAndFile);
6137             if (some(lString))
6138             {
6139                 strcat(str, "initialfile='");
6140                 strcat(str, lString);
6141                 strcat(str, "',");
6142             }
6143         }
6144         if (aNumOfFilterPatterns > 1 || (aNumOfFilterPatterns == 1 // test because poor osx behaviour
6145                                          && lastch(aFilterPatterns[0]) != '*'))
6146         {
6147             strcat(str, "filetypes=(");
6148             strcat(str, "('");
6149             if (some(aSingleFilterDescription))
6150             {
6151                 strcat(str, aSingleFilterDescription);
6152             }
6153             strcat(str, "',(");
6154             foreach (i; 0 .. aNumOfFilterPatterns)
6155             {
6156                 strcat(str, "'");
6157                 strcat(str, aFilterPatterns[i]);
6158                 strcat(str, "',");
6159             }
6160             strcat(str, ")),");
6161             strcat(str, "('All files','*'))");
6162         }
6163         strcat(str, `);
6164 if not isinstance(lFiles, tuple):
6165     print lFiles
6166 else:
6167     lFilesString=''
6168     for lFile in lFiles:
6169         lFilesString += str(lFile) + '|'
6170     print lFilesString[:-1]
6171 "`);
6172     }
6173     else if (tkinter3Present())
6174     {
6175         if (lQuery)
6176         {
6177             response("python3-tkinter");
6178             return cast(const(char)*)1;
6179         }
6180         strcpy(str, gPython3Name.ptr);
6181         strcat(str,
6182                " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();");
6183         strcat(str, "lFiles=filedialog.askopenfilename(");
6184         if (aAllowMultipleSelects)
6185         {
6186             strcat(str, "multiple=1,");
6187         }
6188         if (some(aTitle))
6189         {
6190             strcat(str, "title='");
6191             strcat(str, aTitle);
6192             strcat(str, "',");
6193         }
6194         if (some(aDefaultPathAndFile))
6195         {
6196             getPathWithoutFinalSlash(lString, aDefaultPathAndFile);
6197             if (some(lString))
6198             {
6199                 strcat(str, "initialdir='");
6200                 strcat(str, lString);
6201                 strcat(str, "',");
6202             }
6203             getLastName(lString, aDefaultPathAndFile);
6204             if (some(lString))
6205             {
6206                 strcat(str, "initialfile='");
6207                 strcat(str, lString);
6208                 strcat(str, "',");
6209             }
6210         }
6211         if (aNumOfFilterPatterns > 1 || (aNumOfFilterPatterns == 1 // test because poor osx behaviour
6212                                          && lastch(aFilterPatterns[0]) != '*'))
6213         {
6214             strcat(str, "filetypes=(");
6215             strcat(str, "('");
6216             if (some(aSingleFilterDescription))
6217             {
6218                 strcat(str, aSingleFilterDescription);
6219             }
6220             strcat(str, "',(");
6221             foreach (i; 0 .. aNumOfFilterPatterns)
6222             {
6223                 strcat(str, "'");
6224                 strcat(str, aFilterPatterns[i]);
6225                 strcat(str, "',");
6226             }
6227             strcat(str, ")),");
6228             strcat(str, "('All files','*'))");
6229         }
6230         strcat(str, `);
6231 if not isinstance(lFiles, tuple):
6232     print(lFiles)
6233 else:
6234     lFilesString = ''
6235     for lFile in lFiles:
6236         lFilesString += str(lFile) + '|'
6237     print(lFilesString[:-1])
6238 "`);
6239     }
6240     else if (xdialogPresent() || dialogName())
6241     {
6242         if (xdialogPresent())
6243         {
6244             if (lQuery)
6245             {
6246                 response("xdialog");
6247                 return cast(const(char)*)1;
6248             }
6249             lWasGraphicDialog = true;
6250             strcpy(str, "(Xdialog ");
6251         }
6252         else if (isTerminalRunning())
6253         {
6254             if (lQuery)
6255             {
6256                 response("dialog");
6257                 return cast(const(char)*)0;
6258             }
6259             strcpy(str, "(dialog ");
6260         }
6261         else
6262         {
6263             if (lQuery)
6264             {
6265                 response("dialog");
6266                 return cast(const(char)*)0;
6267             }
6268             lWasXterm = true;
6269             strcpy(str, terminalName());
6270             strcat(str, "'(");
6271             strcat(str, dialogName());
6272             strcat(str, " ");
6273         }
6274 
6275         if (some(aTitle))
6276         {
6277             strcat(str, "--title \"");
6278             strcat(str, aTitle);
6279             strcat(str, "\" ");
6280         }
6281 
6282         if (!xdialogPresent() && !gdialogPresent())
6283         {
6284             strcat(str, "--backtitle \"");
6285             strcat(str,
6286                    "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY");
6287             strcat(str, "\" ");
6288         }
6289 
6290         strcat(str, "--fselect \"");
6291         if (some(aDefaultPathAndFile))
6292         {
6293             if (!strchr(aDefaultPathAndFile, '/'))
6294             {
6295                 strcat(str, "./");
6296             }
6297             strcat(str, aDefaultPathAndFile);
6298         }
6299         else if (!isTerminalRunning() && !lWasGraphicDialog)
6300         {
6301             strcat(str, getenv("HOME"));
6302             strcat(str, "/");
6303         }
6304         else
6305         {
6306             strcat(str, "./");
6307         }
6308 
6309         if (lWasGraphicDialog)
6310         {
6311             strcat(str, "\" 0 60 ) 2>&1 ");
6312         }
6313         else
6314         {
6315             strcat(str, "\" 0 60  >/dev/tty) ");
6316             if (lWasXterm)
6317             {
6318                 strcat(str,
6319                        "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt");
6320             }
6321             else
6322             {
6323                 strcat(str, "2>&1 ; clear >/dev/tty");
6324             }
6325         }
6326     }
6327     else
6328     {
6329         if (lQuery)
6330         {
6331             return _inputBox(aTitle, null, null);
6332         }
6333         p2 = _inputBox(aTitle, "Open file", "");
6334         if (!fileExists(p2))
6335             return null;
6336         return p2;
6337     }
6338 
6339     if (tinyfd_verbose)
6340         printf("str: %s\n", str);
6341     lIn = popen(str, "r");
6342     if (!lIn)
6343         return null;
6344 
6345     lBuff[0] = '\0';
6346     p = lBuff.ptr;
6347     while (fgets(p, lBuff.sizeof, lIn) !is null)
6348     {
6349         p += strlen(p);
6350     }
6351     pclose(lIn);
6352     removeLastNL(lBuff.ptr);
6353     /* printf( "lBuff: %s\n" , lBuff ) ; */
6354     if (lWasKdialog && aAllowMultipleSelects)
6355     {
6356         p = lBuff.ptr;
6357         while (p)
6358         {
6359             p = strchr(p, '\n');
6360             *p = '|';
6361         }
6362     }
6363     /* printf( "lBuff2: %s\n" , lBuff ) ; */
6364     if (!some(lBuff.ptr))
6365         return null;
6366 
6367     if (aAllowMultipleSelects && strchr(lBuff.ptr, '|'))
6368     {
6369         p2 = ensureFilesExist(lBuff.ptr, lBuff.ptr);
6370     }
6371     else if (fileExists(lBuff.ptr))
6372     {
6373         p2 = lBuff.ptr;
6374     }
6375     else
6376         return null;
6377     /* printf( "lBuff3: %s\n" , p2 ) ; */
6378 
6379     return p2;
6380 }
6381 
6382 const(char*) _selectFolderDialog(const char* aTitle, const char* aDefaultPath)
6383 {
6384     static char[MAX_PATH_OR_CMD] lBuff = '\0';
6385     const bool lQuery = eq(aTitle, "tinyfd_query");
6386     char[MAX_PATH_OR_CMD] str_buf = '\0';
6387     char* str = str_buf.ptr;
6388     FILE* lIn;
6389     const(char)* p;
6390     bool lWasGraphicDialog;
6391     bool lWasXterm;
6392     lBuff[0] = '\0';
6393 
6394     if (osascriptPresent())
6395     {
6396         if (lQuery)
6397         {
6398             response("applescript");
6399             return cast(const(char)*)1;
6400         }
6401         strcpy(str, "osascript ");
6402         if (!osx9orBetter())
6403             strcat(str, " -e 'tell application \"System Events\"' -e 'Activate'");
6404         strcat(str, " -e 'try' -e 'POSIX path of ( choose folder ");
6405         if (some(aTitle))
6406         {
6407             strcat(str, "with prompt \"");
6408             strcat(str, aTitle);
6409             strcat(str, "\" ");
6410         }
6411         if (some(aDefaultPath))
6412         {
6413             strcat(str, "default location \"");
6414             strcat(str, aDefaultPath);
6415             strcat(str, "\" ");
6416         }
6417         strcat(str, ")' ");
6418         strcat(str, "-e 'on error number -128' ");
6419         strcat(str, "-e 'end try'");
6420         if (!osx9orBetter())
6421             strcat(str, " -e 'end tell'");
6422     }
6423     else if (kdialogPresent())
6424     {
6425         if (lQuery)
6426         {
6427             response("kdialog");
6428             return cast(const(char)*)1;
6429         }
6430         strcpy(str, "kdialog");
6431         if (kdialogPresent() == 2)
6432         {
6433             strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6434         }
6435         strcat(str, " --getexistingdirectory ");
6436 
6437         if (some(aDefaultPath))
6438         {
6439             if (aDefaultPath[0] != '/')
6440             {
6441                 strcat(str, "$PWD/");
6442             }
6443             strcat(str, "\"");
6444             strcat(str, aDefaultPath);
6445             strcat(str, "\"");
6446         }
6447         else
6448         {
6449             strcat(str, "$PWD/");
6450         }
6451 
6452         if (some(aTitle))
6453         {
6454             strcat(str, " --title \"");
6455             strcat(str, aTitle);
6456             strcat(str, "\"");
6457         }
6458     }
6459     else if (zenityPresent() || matedialogPresent() || shellementaryPresent() || qarmaPresent())
6460     {
6461         if (zenityPresent())
6462         {
6463             if (lQuery)
6464             {
6465                 response("zenity");
6466                 return cast(const(char)*)1;
6467             }
6468             strcpy(str, "zenity");
6469             if (zenity3Present() >= 4 && !getenv("SSH_TTY"))
6470             {
6471                 strcat(str, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6472             }
6473         }
6474         else if (matedialogPresent())
6475         {
6476             if (lQuery)
6477             {
6478                 response("matedialog");
6479                 return cast(const(char)*)1;
6480             }
6481             strcpy(str, "matedialog");
6482         }
6483         else if (shellementaryPresent())
6484         {
6485             if (lQuery)
6486             {
6487                 response("shellementary");
6488                 return cast(const(char)*)1;
6489             }
6490             strcpy(str, "shellementary");
6491         }
6492         else
6493         {
6494             if (lQuery)
6495             {
6496                 response("qarma");
6497                 return cast(const(char)*)1;
6498             }
6499             strcpy(str, "qarma");
6500             if (!getenv("SSH_TTY"))
6501             {
6502                 strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6503             }
6504         }
6505         strcat(str, " --file-selection --directory");
6506 
6507         if (some(aTitle))
6508         {
6509             strcat(str, " --title=\"");
6510             strcat(str, aTitle);
6511             strcat(str, "\"");
6512         }
6513         if (some(aDefaultPath))
6514         {
6515             strcat(str, " --filename=\"");
6516             strcat(str, aDefaultPath);
6517             strcat(str, "\"");
6518         }
6519         if (tinyfd_silent)
6520             strcat(str, " 2>/dev/null ");
6521     }
6522     else if (!xdialogPresent() && tkinter2Present())
6523     {
6524         if (lQuery)
6525         {
6526             response("python2-tkinter");
6527             return cast(const(char)*)1;
6528         }
6529         strcpy(str, gPython2Name.ptr);
6530         if (!isTerminalRunning() && isDarwin())
6531         {
6532             strcat(str, " -i"); /* for osx without console */
6533         }
6534         strcat(str,
6535                " -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();");
6536 
6537         if (isDarwin())
6538         {
6539             strcat(str,
6540                    "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set " ~
6541                    "frontmost of process \\\"Python\\\" to true' ''');");
6542         }
6543 
6544         strcat(str, "print tkFileDialog.askdirectory(");
6545         if (some(aTitle))
6546         {
6547             strcat(str, "title='");
6548             strcat(str, aTitle);
6549             strcat(str, "',");
6550         }
6551         if (some(aDefaultPath))
6552         {
6553             strcat(str, "initialdir='");
6554             strcat(str, aDefaultPath);
6555             strcat(str, "'");
6556         }
6557         strcat(str, ")\"");
6558     }
6559     else if (!xdialogPresent() && tkinter3Present())
6560     {
6561         if (lQuery)
6562         {
6563             response("python3-tkinter");
6564             return cast(const(char)*)1;
6565         }
6566         strcpy(str, gPython3Name.ptr);
6567         strcat(str,
6568                " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();");
6569         strcat(str, "print( filedialog.askdirectory(");
6570         if (some(aTitle))
6571         {
6572             strcat(str, "title='");
6573             strcat(str, aTitle);
6574             strcat(str, "',");
6575         }
6576         if (some(aDefaultPath))
6577         {
6578             strcat(str, "initialdir='");
6579             strcat(str, aDefaultPath);
6580             strcat(str, "'");
6581         }
6582         strcat(str, ") )\"");
6583     }
6584     else if (xdialogPresent() || dialogName())
6585     {
6586         if (xdialogPresent())
6587         {
6588             if (lQuery)
6589             {
6590                 response("xdialog");
6591                 return cast(const(char)*)1;
6592             }
6593             lWasGraphicDialog = true;
6594             strcpy(str, "(Xdialog ");
6595         }
6596         else if (isTerminalRunning())
6597         {
6598             if (lQuery)
6599             {
6600                 response("dialog");
6601                 return cast(const(char)*)0;
6602             }
6603             strcpy(str, "(dialog ");
6604         }
6605         else
6606         {
6607             if (lQuery)
6608             {
6609                 response("dialog");
6610                 return cast(const(char)*)0;
6611             }
6612             lWasXterm = true;
6613             strcpy(str, terminalName());
6614             strcat(str, "'(");
6615             strcat(str, dialogName());
6616             strcat(str, " ");
6617         }
6618 
6619         if (some(aTitle))
6620         {
6621             strcat(str, "--title \"");
6622             strcat(str, aTitle);
6623             strcat(str, "\" ");
6624         }
6625 
6626         if (!xdialogPresent() && !gdialogPresent())
6627         {
6628             strcat(str, "--backtitle \"");
6629             strcat(str,
6630                    "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY");
6631             strcat(str, "\" ");
6632         }
6633 
6634         strcat(str, "--dselect \"");
6635         if (some(aDefaultPath))
6636         {
6637             strcat(str, aDefaultPath);
6638             ensureFinalSlash(str);
6639         }
6640         else if (!isTerminalRunning() && !lWasGraphicDialog)
6641         {
6642             strcat(str, getenv("HOME"));
6643             strcat(str, "/");
6644         }
6645         else
6646         {
6647             strcat(str, "./");
6648         }
6649 
6650         if (lWasGraphicDialog)
6651         {
6652             strcat(str, "\" 0 60 ) 2>&1 ");
6653         }
6654         else
6655         {
6656             strcat(str, "\" 0 60  >/dev/tty) ");
6657             if (lWasXterm)
6658             {
6659                 strcat(str,
6660                        "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt");
6661             }
6662             else
6663             {
6664                 strcat(str, "2>&1 ; clear >/dev/tty");
6665             }
6666         }
6667     }
6668     else
6669     {
6670         if (lQuery)
6671         {
6672             return _inputBox(aTitle, null, null);
6673         }
6674         p = _inputBox(aTitle, "Select folder", "");
6675         if (!dirExists(p))
6676             return null;
6677         return p;
6678     }
6679     if (tinyfd_verbose)
6680         printf("str: %s\n", str);
6681     lIn = popen(str, "r");
6682     if (!lIn)
6683         return null;
6684 
6685     while (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
6686     {
6687     }
6688     pclose(lIn);
6689     removeLastNL(lBuff.ptr);
6690     /* printf( "lBuff: %s\n" , lBuff ) ; */
6691     if (!dirExists(lBuff.ptr))
6692         return null;
6693     return lBuff.ptr;
6694 }
6695 
6696 const(char*) _colorChooser(
6697     const char* aTitle,
6698     const char* aDefaultHexRGB,
6699     ref const ubyte[3] aDefaultRGB,
6700     ref ubyte[3] aoResultRGB)
6701 {
6702     static char[128] lBuff = '\0';
6703     const bool lQuery = eq(aTitle, "tinyfd_query");
6704     char[128] tmp_buf = '\0';
6705     char[MAX_PATH_OR_CMD] str_buf = '\0';
6706     char* lTmp = tmp_buf.ptr;
6707     char* str = str_buf.ptr;
6708     char[8] lDefaultHexRGB = '\0';
6709     char* lpDefaultHexRGB;
6710     ubyte[3] lDefaultRGB;
6711     const(char)* p;
6712     FILE* lIn;
6713     bool lWasZenity3;
6714     bool lWasOsascript;
6715     bool lWasXdialog;
6716     lBuff[0] = '\0';
6717 
6718     if (aDefaultHexRGB)
6719     {
6720         Hex2RGB(aDefaultHexRGB, lDefaultRGB);
6721         lpDefaultHexRGB = cast(char*)aDefaultHexRGB;
6722     }
6723     else
6724     {
6725         lDefaultRGB[0] = aDefaultRGB[0];
6726         lDefaultRGB[1] = aDefaultRGB[1];
6727         lDefaultRGB[2] = aDefaultRGB[2];
6728         RGB2Hex(aDefaultRGB, lDefaultHexRGB.ptr);
6729         lpDefaultHexRGB = lDefaultHexRGB.ptr;
6730     }
6731 
6732     if (osascriptPresent())
6733     {
6734         if (lQuery)
6735         {
6736             response("applescript");
6737             return cast(const(char)*)1;
6738         }
6739         lWasOsascript = true;
6740         strcpy(str, "osascript");
6741 
6742         if (!osx9orBetter())
6743         {
6744             strcat(str, " -e 'tell application \"System Events\"' -e 'Activate'");
6745             strcat(str, " -e 'try' -e 'set mycolor to choose color default color {");
6746         }
6747         else
6748         {
6749             strcat(str,
6750                    " -e 'try' -e 'tell app (path to frontmost application as Unicode text) " ~
6751                    "to set mycolor to choose color default color {");
6752         }
6753 
6754         sprintf(lTmp, "%d", 256 * lDefaultRGB[0]);
6755         strcat(str, lTmp);
6756         strcat(str, ",");
6757         sprintf(lTmp, "%d", 256 * lDefaultRGB[1]);
6758         strcat(str, lTmp);
6759         strcat(str, ",");
6760         sprintf(lTmp, "%d", 256 * lDefaultRGB[2]);
6761         strcat(str, lTmp);
6762         strcat(str, "}' ");
6763         strcat(str,
6764                "-e 'set mystring to ((item 1 of mycolor) div 256 as integer) as string' ");
6765         strcat(str,
6766                "-e 'repeat with i from 2 to the count of mycolor' ");
6767         strcat(str,
6768                "-e 'set mystring to mystring & \" \" & ((item i of mycolor) div 256 as integer) as string' ");
6769         strcat(str, "-e 'end repeat' ");
6770         strcat(str, "-e 'mystring' ");
6771         strcat(str, "-e 'on error number -128' ");
6772         strcat(str, "-e 'end try'");
6773         if (!osx9orBetter())
6774             strcat(str, " -e 'end tell'");
6775     }
6776     else if (kdialogPresent())
6777     {
6778         if (lQuery)
6779         {
6780             response("kdialog");
6781             return cast(const(char)*)1;
6782         }
6783         strcpy(str, "kdialog");
6784         if (kdialogPresent() == 2)
6785         {
6786             strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6787         }
6788         sprintf(str + strlen(str), " --getcolor --default '%s'", lpDefaultHexRGB);
6789 
6790         if (some(aTitle))
6791         {
6792             strcat(str, " --title \"");
6793             strcat(str, aTitle);
6794             strcat(str, "\"");
6795         }
6796     }
6797     else if (zenity3Present() || matedialogPresent() || shellementaryPresent() || qarmaPresent())
6798     {
6799         lWasZenity3 = true;
6800         if (zenity3Present())
6801         {
6802             if (lQuery)
6803             {
6804                 response("zenity3");
6805                 return cast(const(char)*)1;
6806             }
6807             strcpy(str, "zenity");
6808             if (zenity3Present() >= 4 && !getenv("SSH_TTY"))
6809             {
6810                 strcat(str, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6811             }
6812         }
6813         else if (matedialogPresent())
6814         {
6815             if (lQuery)
6816             {
6817                 response("matedialog");
6818                 return cast(const(char)*)1;
6819             }
6820             strcpy(str, "matedialog");
6821         }
6822         else if (shellementaryPresent())
6823         {
6824             if (lQuery)
6825             {
6826                 response("shellementary");
6827                 return cast(const(char)*)1;
6828             }
6829             strcpy(str, "shellementary");
6830         }
6831         else
6832         {
6833             if (lQuery)
6834             {
6835                 response("qarma");
6836                 return cast(const(char)*)1;
6837             }
6838             strcpy(str, "qarma");
6839             if (!getenv("SSH_TTY"))
6840             {
6841                 strcat(str, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */
6842             }
6843         }
6844         strcat(str, " --color-selection --show-palette");
6845         sprintf(str + strlen(str), " --color=%s", lpDefaultHexRGB);
6846 
6847         if (some(aTitle))
6848         {
6849             strcat(str, " --title=\"");
6850             strcat(str, aTitle);
6851             strcat(str, "\"");
6852         }
6853         if (tinyfd_silent)
6854             strcat(str, " 2>/dev/null ");
6855     }
6856     else if (xdialogPresent())
6857     {
6858         if (lQuery)
6859         {
6860             response("xdialog");
6861             return cast(const(char)*)1;
6862         }
6863         lWasXdialog = true;
6864         strcpy(str, "Xdialog --colorsel \"");
6865         if (some(aTitle))
6866         {
6867             strcat(str, aTitle);
6868         }
6869         strcat(str, "\" 0 60 ");
6870         sprintf(lTmp, "%hhu %hhu %hhu", lDefaultRGB[0], lDefaultRGB[1], lDefaultRGB[2]);
6871         strcat(str, lTmp);
6872         strcat(str, " 2>&1");
6873     }
6874     else if (tkinter2Present())
6875     {
6876         if (lQuery)
6877         {
6878             response("python2-tkinter");
6879             return cast(const(char)*)1;
6880         }
6881         strcpy(str, gPython2Name.ptr);
6882         if (!isTerminalRunning() && isDarwin())
6883         {
6884             strcat(str, " -i"); /* for osx without console */
6885         }
6886 
6887         strcat(str,
6888                " -S -c \"import Tkinter,tkColorChooser;root=Tkinter.Tk();root.withdraw();");
6889 
6890         if (isDarwin())
6891         {
6892             strcat(str,
6893                    "import os;os.system('''osascript -e 'tell app \\\"Finder\\\" to set " ~
6894                    "frontmost of process \\\"Python\\\" to true' ''');");
6895         }
6896 
6897         strcat(str, "res=tkColorChooser.askcolor(color='");
6898         strcat(str, lpDefaultHexRGB);
6899         strcat(str, "'");
6900 
6901         if (some(aTitle))
6902         {
6903             strcat(str, ",title='");
6904             strcat(str, aTitle);
6905             strcat(str, "'");
6906         }
6907         strcat(str, `);
6908 if res[1] is not None:
6909     print res[1]
6910 "`);
6911     }
6912     else if (tkinter3Present())
6913     {
6914         if (lQuery)
6915         {
6916             response("python3-tkinter");
6917             return cast(const(char)*)1;
6918         }
6919         strcpy(str, gPython3Name.ptr);
6920         strcat(str,
6921                " -S -c \"import tkinter;from tkinter import colorchooser;root=tkinter.Tk();root.withdraw();");
6922         strcat(str, "res=colorchooser.askcolor(color='");
6923         strcat(str, lpDefaultHexRGB);
6924         strcat(str, "'");
6925 
6926         if (some(aTitle))
6927         {
6928             strcat(str, ",title='");
6929             strcat(str, aTitle);
6930             strcat(str, "'");
6931         }
6932         strcat(str, `);
6933 if res[1] is not None:
6934     print(res[1])
6935 "`);
6936     }
6937     else
6938     {
6939         if (lQuery)
6940         {
6941             return _inputBox(aTitle, null, null);
6942         }
6943         p = _inputBox(aTitle, "Enter hex rgb color (i.e. #f5ca20)", lpDefaultHexRGB);
6944         if (!p || strlen(p) != 7 || p[0] != '#')
6945             return null;
6946 
6947         foreach (i; 1 .. 7)
6948         {
6949             if (!isxdigit(p[i]))
6950                 return null;
6951         }
6952         Hex2RGB(p, aoResultRGB);
6953         return p;
6954     }
6955 
6956     if (tinyfd_verbose)
6957         printf("str: %s\n", str);
6958     lIn = popen(str, "r");
6959     if (!lIn)
6960         return null;
6961 
6962     while (fgets(lBuff.ptr, lBuff.sizeof, lIn) !is null)
6963     {
6964     }
6965     pclose(lIn);
6966     if (!some(lBuff.ptr))
6967         return null;
6968     /* printf( "len Buff: %lu\n" , strlen(lBuff.ptr) ) ; */
6969     /* printf( "lBuff0: %s\n" , lBuff ) ; */
6970     removeLastNL(lBuff.ptr);
6971 
6972     if (lWasZenity3)
6973     {
6974         if (lBuff[0] == '#')
6975         {
6976             if (strlen(lBuff.ptr) > 7)
6977             {
6978                 lBuff[3] = lBuff[5];
6979                 lBuff[4] = lBuff[6];
6980                 lBuff[5] = lBuff[9];
6981                 lBuff[6] = lBuff[10];
6982                 lBuff[7] = '\0';
6983             }
6984             Hex2RGB(lBuff.ptr, aoResultRGB);
6985         }
6986         else if (lBuff[3] == '(')
6987         {
6988             sscanf(lBuff.ptr, "rgb(%hhu,%hhu,%hhu",
6989                    &aoResultRGB[0], &aoResultRGB[1], &aoResultRGB[2]);
6990             RGB2Hex(aoResultRGB, lBuff.ptr);
6991         }
6992         else if (lBuff[4] == '(')
6993         {
6994             sscanf(lBuff.ptr, "rgba(%hhu,%hhu,%hhu",
6995                    &aoResultRGB[0], &aoResultRGB[1], &aoResultRGB[2]);
6996             RGB2Hex(aoResultRGB, lBuff.ptr);
6997         }
6998     }
6999     else if (lWasOsascript || lWasXdialog)
7000     {
7001         /* printf( "lBuff: %s\n" , lBuff ) ; */
7002         sscanf(lBuff.ptr, "%hhu %hhu %hhu",
7003                &aoResultRGB[0], &aoResultRGB[1], &aoResultRGB[2]);
7004         RGB2Hex(aoResultRGB, lBuff.ptr);
7005     }
7006     else
7007     {
7008         Hex2RGB(lBuff.ptr, aoResultRGB);
7009     }
7010     /* printf("%d %d %d\n", aoResultRGB[0],aoResultRGB[1],aoResultRGB[2]); */
7011     /* printf( "lBuff: %s\n" , lBuff ) ; */
7012     return lBuff.ptr;
7013 }
7014 
7015 } // windows-unix