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