Mario Kart 64
Loading...
Searching...
No Matches
portable-file-dialogs.h
Go to the documentation of this file.
1//
2// Portable File Dialogs
3//
4// Copyright © 2018–2022 Sam Hocevar <sam@hocevar.net>
5//
6// This library is free software. It comes without any warranty, to
7// the extent permitted by applicable law. You can redistribute it
8// and/or modify it under the terms of the Do What the Fuck You Want
9// to Public License, Version 2, as published by the WTFPL Task Force.
10// See http://www.wtfpl.net/ for more details.
11//
12
13#pragma once
14
15#if _WIN32
16#ifndef WIN32_LEAN_AND_MEAN
17#define WIN32_LEAN_AND_MEAN 1
18#endif
19#include <windows.h>
20#include <commdlg.h>
21#include <shlobj.h>
22#include <shobjidl.h> // IFileDialog
23#include <shellapi.h>
24#include <strsafe.h>
25#include <future> // std::async
26#include <userenv.h> // GetUserProfileDirectory()
27
28#elif __EMSCRIPTEN__
29#include <emscripten.h>
30
31#else
32#ifndef _POSIX_C_SOURCE
33#define _POSIX_C_SOURCE 2 // for popen()
34#endif
35#ifdef __APPLE__
36#ifndef _DARWIN_C_SOURCE
37#define _DARWIN_C_SOURCE
38#endif
39#endif
40#include <cstdio> // popen()
41#include <cstdlib> // std::getenv()
42#include <fcntl.h> // fcntl()
43#include <unistd.h> // read(), pipe(), dup2(), getuid()
44#include <csignal> // ::kill, std::signal
45#include <sys/stat.h> // stat()
46#include <sys/wait.h> // waitpid()
47#include <pwd.h> // getpwnam()
48#endif
49
50#include <string> // std::string
51#include <memory> // std::shared_ptr
52#include <iostream> // std::ostream
53#include <map> // std::map
54#include <set> // std::set
55#include <regex> // std::regex
56#include <thread> // std::mutex, std::this_thread
57#include <chrono> // std::chrono
58
59// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog
60#ifndef PFD_HAS_IFILEDIALOG
61#define PFD_HAS_IFILEDIALOG 1
62#if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION
63#if __GXX_ABI_VERSION <= 1013
64#undef PFD_HAS_IFILEDIALOG
65#define PFD_HAS_IFILEDIALOG 0
66#endif
67#endif
68#endif
69
70namespace pfd {
71
81
90
91enum class icon {
92 info = 0,
96};
97
98// Additional option flags for various dialog constructors
99enum class opt : uint8_t {
100 none = 0,
101 // For file open, allow multiselect.
103 // For file save, force overwrite and disable the confirmation dialog.
105 // For folder select, force path to be the provided argument instead
106 // of the last opened directory, which is the Microsoft-recommended,
107 // user-friendly behaviour.
109};
110
111inline opt operator|(opt a, opt b) {
112 return opt(uint8_t(a) | uint8_t(b));
113}
114inline bool operator&(opt a, opt b) {
115 return bool(uint8_t(a) & uint8_t(b));
116}
117
118// The settings class, only exposing to the user a way to set verbose mode
119// and to force a rescan of installed desktop helpers (zenity, kdialog…).
120class settings {
121 public:
122 static bool available();
123
124 static void verbose(bool value);
125 static void rescan();
126
127 protected:
128 explicit settings(bool resync = false);
129
130 bool check_program(std::string const& program);
131
132 inline bool is_osascript() const;
133 inline bool is_zenity() const;
134 inline bool is_kdialog() const;
135
148
149 // Static array of flags for internal state
150 bool const& flags(flag in_flag) const;
151
152 // Non-const getter for the static array of flags
153 bool& flags(flag in_flag);
154};
155
156// Internal classes, not to be used by client applications
157namespace internal {
158
159// Process wait timeout, in milliseconds
160static int const default_wait_timeout = 20;
161
162class executor {
163 friend class dialog;
164
165 public:
166 // High level function to get the result of a command
167 std::string result(int* exit_code = nullptr);
168
169 // High level function to abort
170 bool kill();
171
172#if _WIN32
173 void start_func(std::function<std::string(int*)> const& fun);
174 static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam);
175#elif __EMSCRIPTEN__
176 void start(int exit_code);
177#else
178 void start_process(std::vector<std::string> const& command);
179#endif
180
181 ~executor();
182
183 protected:
184 bool ready(int timeout = default_wait_timeout);
185 void stop();
186
187 private:
188 bool m_running = false;
189 std::string m_stdout;
190 int m_exit_code = -1;
191#if _WIN32
192 std::future<std::string> m_future;
193 std::set<HWND> m_windows;
194 std::condition_variable m_cond;
195 std::mutex m_mutex;
196 DWORD m_tid;
197#elif __EMSCRIPTEN__ || __NX__
198 // FIXME: do something
199#else
200 pid_t m_pid = 0;
201 int m_fd = -1;
202#endif
203};
204
205class platform {
206 protected:
207#if _WIN32
208 // Helper class around LoadLibraryA() and GetProcAddress() with some safety
209 class dll {
210 public:
211 dll(std::string const& name);
212 ~dll();
213
214 template <typename T> class proc {
215 public:
216 proc(dll const& lib, std::string const& sym)
217 : m_proc(reinterpret_cast<T*>((void*)::GetProcAddress(lib.handle, sym.c_str()))) {
218 }
219
220 operator bool() const {
221 return m_proc != nullptr;
222 }
223 operator T*() const {
224 return m_proc;
225 }
226
227 private:
228 T* m_proc;
229 };
230
231 private:
232 HMODULE handle;
233 };
234
235 // Helper class around CoInitialize() and CoUnInitialize()
236 class ole32_dll : public dll {
237 public:
238 ole32_dll();
239 ~ole32_dll();
240 bool is_initialized();
241
242 private:
243 HRESULT m_state;
244 };
245
246 // Helper class around CreateActCtx() and ActivateActCtx()
247 class new_style_context {
248 public:
249 new_style_context();
250 ~new_style_context();
251
252 private:
253 HANDLE create();
254 ULONG_PTR m_cookie = 0;
255 };
256#endif
257};
258
259class dialog : protected settings, protected platform {
260 public:
261 bool ready(int timeout = default_wait_timeout) const;
262 bool kill() const;
263
264 protected:
265 explicit dialog();
266
267 std::vector<std::string> desktop_helper() const;
268 static std::string buttons_to_name(choice _choice);
269 static std::string get_icon_name(icon _icon);
270
271 std::string powershell_quote(std::string const& str) const;
272 std::string osascript_quote(std::string const& str) const;
273 std::string shell_quote(std::string const& str) const;
274
275 // Keep handle to executing command
276 std::shared_ptr<executor> m_async;
277};
278
279class file_dialog : public dialog {
280 protected:
286
287 file_dialog(type in_type, std::string const& title, std::string const& default_path = "",
288 std::vector<std::string> const& filters = {}, opt options = opt::none);
289
290 protected:
291 std::string string_result();
292 std::vector<std::string> vector_result();
293
294#if _WIN32
295 static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData);
296#if PFD_HAS_IFILEDIALOG
297 std::string select_folder_vista(IFileDialog* ifd, bool force_path);
298#endif
299
300 std::wstring m_wtitle;
301 std::wstring m_wdefault_path;
302
303 std::vector<std::string> m_vector_result;
304#endif
305};
306
307} // namespace internal
308
309//
310// The path class provides some platform-specific path constants
311//
312
313class path : protected internal::platform {
314 public:
315 static std::string home();
316 static std::string separator();
317};
318
319//
320// The notify widget
321//
322
323class notify : public internal::dialog {
324 public:
325 notify(std::string const& title, std::string const& message, icon _icon = icon::info);
326};
327
328//
329// The message widget
330//
331
332class message : public internal::dialog {
333 public:
334 message(std::string const& title, std::string const& text, choice _choice = choice::ok_cancel,
335 icon _icon = icon::info);
336
337 button result();
338
339 private:
340 // Some extra logic to map the exit code to button number
341 std::map<int, button> m_mappings;
342};
343
344//
345// The open_file, save_file, and open_folder widgets
346//
347
349 public:
350 open_file(std::string const& title, std::string const& default_path = "",
351 std::vector<std::string> const& filters = { "All Files", "*" }, opt options = opt::none);
352
353#if defined(__has_cpp_attribute)
354#if __has_cpp_attribute(deprecated)
355 // Backwards compatibility
356 [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]]
357#endif
358#endif
359 open_file(std::string const& title, std::string const& default_path, std::vector<std::string> const& filters,
360 bool allow_multiselect);
361
362 std::vector<std::string> result();
363};
364
366 public:
367 save_file(std::string const& title, std::string const& default_path = "",
368 std::vector<std::string> const& filters = { "All Files", "*" }, opt options = opt::none);
369
370#if defined(__has_cpp_attribute)
371#if __has_cpp_attribute(deprecated)
372 // Backwards compatibility
373 [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]]
374#endif
375#endif
376 save_file(std::string const& title, std::string const& default_path, std::vector<std::string> const& filters,
377 bool confirm_overwrite);
378
379 std::string result();
380};
381
383 public:
384 select_folder(std::string const& title, std::string const& default_path = "", opt options = opt::none);
385
386 std::string result();
387};
388
389//
390// Below this are all the method implementations. You may choose to define the
391// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except
392// in one place. This may reduce compilation times.
393//
394
395#if !defined PFD_SKIP_IMPLEMENTATION
396
397// internal free functions implementations
398
399namespace internal {
400
401#if _WIN32
402static inline std::wstring str2wstr(std::string const& str) {
403 int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0);
404 std::wstring ret(len, '\0');
405 MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size());
406 return ret;
407}
408
409static inline std::string wstr2str(std::wstring const& str) {
410 int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr);
411 std::string ret(len, '\0');
412 WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr);
413 return ret;
414}
415
416static inline bool is_vista() {
417 OSVERSIONINFOEXW osvi;
418 memset(&osvi, 0, sizeof(osvi));
419 DWORDLONG const mask =
420 VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL),
421 VER_MINORVERSION, VER_GREATER_EQUAL),
422 VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
423 osvi.dwOSVersionInfoSize = sizeof(osvi);
424 osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA);
425 osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA);
426 osvi.wServicePackMajor = 0;
427
428 return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE;
429}
430#endif
431
432// This is necessary until C++20 which will have std::string::ends_with() etc.
433
434static inline bool ends_with(std::string const& str, std::string const& suffix) {
435 return suffix.size() <= str.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
436}
437
438static inline bool starts_with(std::string const& str, std::string const& prefix) {
439 return prefix.size() <= str.size() && str.compare(0, prefix.size(), prefix) == 0;
440}
441
442// This is necessary until C++17 which will have std::filesystem::is_directory
443
444static inline bool is_directory(std::string const& path) {
445#if _WIN32
446 auto attr = GetFileAttributesA(path.c_str());
447 return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY);
448#elif __EMSCRIPTEN__
449 // TODO
450 return false;
451#else
452 struct stat s;
453 return stat(path.c_str(), &s) == 0 && S_ISDIR(s.st_mode);
454#endif
455}
456
457// This is necessary because getenv is not thread-safe
458
459static inline std::string getenv(std::string const& str) {
460#if _MSC_VER
461 char* buf = nullptr;
462 size_t size = 0;
463 if (_dupenv_s(&buf, &size, str.c_str()) == 0 && buf) {
464 std::string ret(buf);
465 free(buf);
466 return ret;
467 }
468 return "";
469#else
470 auto buf = std::getenv(str.c_str());
471 return buf ? buf : "";
472#endif
473}
474
475} // namespace internal
476
477// settings implementation
478
479inline settings::settings(bool resync) {
480 flags(flag::is_scanned) &= !resync;
481
483 return;
484
485 auto pfd_verbose = internal::getenv("PFD_VERBOSE");
486 auto match_no = std::regex("(|0|no|false)", std::regex_constants::icase);
487 if (!std::regex_match(pfd_verbose, match_no))
488 flags(flag::is_verbose) = true;
489
490#if _WIN32
491 flags(flag::is_vista) = internal::is_vista();
492#elif !__APPLE__
497
498 // If multiple helpers are available, try to default to the best one
500 auto desktop_name = internal::getenv("XDG_SESSION_DESKTOP");
501 if (desktop_name == std::string("gnome"))
502 flags(flag::has_kdialog) = false;
503 else if (desktop_name == std::string("KDE"))
504 flags(flag::has_zenity) = false;
505 }
506#endif
507
508 flags(flag::is_scanned) = true;
509}
510
511inline bool settings::available() {
512#if _WIN32
513 return true;
514#elif __APPLE__
515 return true;
516#elif __EMSCRIPTEN__
517 // FIXME: Return true after implementation is complete.
518 return false;
519#else
520 settings tmp;
523#endif
524}
525
526inline void settings::verbose(bool value) {
527 settings().flags(flag::is_verbose) = value;
528}
529
530inline void settings::rescan() {
531 settings(/* resync = */ true);
532}
533
534// Check whether a program is present using “which”.
535inline bool settings::check_program(std::string const& program) {
536#if _WIN32
537 (void)program;
538 return false;
539#elif __EMSCRIPTEN__
540 (void)program;
541 return false;
542#else
543 int exit_code = -1;
544 internal::executor async;
545 async.start_process({ "/bin/sh", "-c", "which " + program });
546 async.result(&exit_code);
547 return exit_code == 0;
548#endif
549}
550
551inline bool settings::is_osascript() const {
552#if __APPLE__
553 return true;
554#else
555 return false;
556#endif
557}
558
562
563inline bool settings::is_kdialog() const {
564 return flags(flag::has_kdialog);
565}
566
567inline bool const& settings::flags(flag in_flag) const {
568 static bool flags[size_t(flag::max_flag)];
569 return flags[size_t(in_flag)];
570}
571
572inline bool& settings::flags(flag in_flag) {
573 return const_cast<bool&>(static_cast<settings const*>(this)->flags(in_flag));
574}
575
576// path implementation
577inline std::string path::home() {
578#if _WIN32
579 // First try the USERPROFILE environment variable
580 auto user_profile = internal::getenv("USERPROFILE");
581 if (user_profile.size() > 0)
582 return user_profile;
583 // Otherwise, try GetUserProfileDirectory()
584 HANDLE token = nullptr;
585 DWORD len = MAX_PATH;
586 char buf[MAX_PATH] = { '\0' };
587 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
588 dll userenv("userenv.dll");
589 dll::proc<BOOL WINAPI(HANDLE, LPSTR, LPDWORD)> get_user_profile_directory(userenv, "GetUserProfileDirectoryA");
590 get_user_profile_directory(token, buf, &len);
591 CloseHandle(token);
592 if (*buf)
593 return buf;
594 }
595#elif __EMSCRIPTEN__
596 return "/";
597#else
598 // First try the HOME environment variable
599 auto home = internal::getenv("HOME");
600 if (home.size() > 0)
601 return home;
602 // Otherwise, try getpwuid_r()
603 size_t len = 4096;
604#if defined(_SC_GETPW_R_SIZE_MAX)
605 auto size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
606 if (size_max != -1)
607 len = size_t(size_max);
608#endif
609 std::vector<char> buf(len);
610 struct passwd pwd, *result;
611 if (getpwuid_r(getuid(), &pwd, buf.data(), buf.size(), &result) == 0)
612 return result->pw_dir;
613#endif
614 return "/";
615}
616
617inline std::string path::separator() {
618#if _WIN32
619 return "\\";
620#else
621 return "/";
622#endif
623}
624
625// executor implementation
626
627inline std::string internal::executor::result(int* exit_code /* = nullptr */) {
628 stop();
629 if (exit_code)
630 *exit_code = m_exit_code;
631 return m_stdout;
632}
633
635#if _WIN32
636 if (m_future.valid()) {
637 // Close all windows that weren’t open when we started the future
638 auto previous_windows = m_windows;
639 EnumWindows(&enum_windows_callback, (LPARAM)this);
640 for (auto hwnd : m_windows)
641 if (previous_windows.find(hwnd) == previous_windows.end()) {
642 SendMessage(hwnd, WM_CLOSE, 0, 0);
643 // Also send IDNO in case of a Yes/No or Abort/Retry/Ignore messagebox
644 SendMessage(hwnd, WM_COMMAND, IDNO, 0);
645 }
646 }
647#elif __EMSCRIPTEN__ || __NX__
648 // FIXME: do something
649 return false; // cannot kill
650#else
651 ::kill(m_pid, SIGKILL);
652#endif
653 stop();
654 return true;
655}
656
657#if _WIN32
658inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) {
659 auto that = (executor*)lParam;
660
661 DWORD pid;
662 auto tid = GetWindowThreadProcessId(hwnd, &pid);
663 if (tid == that->m_tid)
664 that->m_windows.insert(hwnd);
665 return TRUE;
666}
667#endif
668
669#if _WIN32
670inline void internal::executor::start_func(std::function<std::string(int*)> const& fun) {
671 stop();
672
673 auto trampoline = [fun, this]() {
674 // Save our thread id so that the caller can cancel us
675 m_tid = GetCurrentThreadId();
676 EnumWindows(&enum_windows_callback, (LPARAM)this);
677 m_cond.notify_all();
678 return fun(&m_exit_code);
679 };
680
681 std::unique_lock<std::mutex> lock(m_mutex);
682 m_future = std::async(std::launch::async, trampoline);
683 m_cond.wait(lock);
684 m_running = true;
685}
686
687#elif __EMSCRIPTEN__
688inline void internal::executor::start(int exit_code) {
689 m_exit_code = exit_code;
690}
691
692#else
693inline void internal::executor::start_process(std::vector<std::string> const& command) {
694 stop();
695 m_stdout.clear();
696 m_exit_code = -1;
697
698 int in[2], out[2];
699 if (pipe(in) != 0 || pipe(out) != 0)
700 return;
701
702 m_pid = fork();
703 if (m_pid < 0)
704 return;
705
706 close(in[m_pid ? 0 : 1]);
707 close(out[m_pid ? 1 : 0]);
708
709 if (m_pid == 0) {
710 dup2(in[0], STDIN_FILENO);
711 dup2(out[1], STDOUT_FILENO);
712
713 // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity)
714 int fd = open("/dev/null", O_WRONLY);
715 dup2(fd, STDERR_FILENO);
716 close(fd);
717
718 std::vector<char*> args;
719 std::transform(command.cbegin(), command.cend(), std::back_inserter(args),
720 [](std::string const& s) { return const_cast<char*>(s.c_str()); });
721 args.push_back(nullptr); // null-terminate argv[]
722
723 execvp(args[0], args.data());
724 exit(1);
725 }
726
727 close(in[1]);
728 m_fd = out[0];
729 auto flags = fcntl(m_fd, F_GETFL);
730 fcntl(m_fd, F_SETFL, flags | O_NONBLOCK);
731
732 m_running = true;
733}
734#endif
735
737 stop();
738}
739
740inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) {
741 if (!m_running)
742 return true;
743
744#if _WIN32
745 if (m_future.valid()) {
746 auto status = m_future.wait_for(std::chrono::milliseconds(timeout));
747 if (status != std::future_status::ready) {
748 // On Windows, we need to run the message pump. If the async
749 // thread uses a Windows API dialog, it may be attached to the
750 // main thread and waiting for messages that only we can dispatch.
751 MSG msg;
752 while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
753 TranslateMessage(&msg);
754 DispatchMessage(&msg);
755 }
756 return false;
757 }
758
759 m_stdout = m_future.get();
760 }
761#elif __EMSCRIPTEN__ || __NX__
762 // FIXME: do something
763 (void)timeout;
764#else
765 char buf[BUFSIZ];
766 ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore
767 if (received > 0) {
768 m_stdout += std::string(buf, received);
769 return false;
770 }
771
772 // Reap child process if it is dead. It is possible that the system has already reaped it
773 // (this happens when the calling application handles or ignores SIG_CHLD) and results in
774 // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for
775 // a little while.
776 int status;
777 pid_t child = waitpid(m_pid, &status, WNOHANG);
778 if (child != m_pid && (child >= 0 || errno != ECHILD)) {
779 // FIXME: this happens almost always at first iteration
780 std::this_thread::sleep_for(std::chrono::milliseconds(timeout));
781 return false;
782 }
783
784 close(m_fd);
785 m_exit_code = WEXITSTATUS(status);
786#endif
787
788 m_running = false;
789 return true;
790}
791
793 // Loop until the user closes the dialog
794 while (!ready())
795 ;
796}
797
798// dll implementation
799
800#if _WIN32
801inline internal::platform::dll::dll(std::string const& name) : handle(::LoadLibraryA(name.c_str())) {
802}
803
804inline internal::platform::dll::~dll() {
805 if (handle)
806 ::FreeLibrary(handle);
807}
808#endif // _WIN32
809
810// ole32_dll implementation
811
812#if _WIN32
813inline internal::platform::ole32_dll::ole32_dll() : dll("ole32.dll") {
814 // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes.
815 // See https://github.com/samhocevar/portable-file-dialogs/issues/51
816 auto coinit = proc<HRESULT WINAPI(LPVOID, DWORD)>(*this, "CoInitializeEx");
817 m_state = coinit(nullptr, COINIT_MULTITHREADED);
818}
819
820inline internal::platform::ole32_dll::~ole32_dll() {
821 if (is_initialized())
822 proc<void WINAPI()>(*this, "CoUninitialize")();
823}
824
825inline bool internal::platform::ole32_dll::is_initialized() {
826 return m_state == S_OK || m_state == S_FALSE;
827}
828#endif
829
830// new_style_context implementation
831
832#if _WIN32
833inline internal::platform::new_style_context::new_style_context() {
834 // Only create one activation context for the whole app lifetime.
835 static HANDLE hctx = create();
836
837 if (hctx != INVALID_HANDLE_VALUE)
838 ActivateActCtx(hctx, &m_cookie);
839}
840
841inline internal::platform::new_style_context::~new_style_context() {
842 DeactivateActCtx(0, m_cookie);
843}
844
845inline HANDLE internal::platform::new_style_context::create() {
846 // This “hack” seems to be necessary for this code to work on windows XP.
847 // Without it, dialogs do not show and close immediately. GetError()
848 // returns 0 so I don’t know what causes this. I was not able to reproduce
849 // this behavior on Windows 7 and 10 but just in case, let it be here for
850 // those versions too.
851 // This hack is not required if other dialogs are used (they load comdlg32
852 // automatically), only if message boxes are used.
853 dll comdlg32("comdlg32.dll");
854
855 // Using approach as shown here: https://stackoverflow.com/a/10444161
856 UINT len = ::GetSystemDirectoryA(nullptr, 0);
857 std::string sys_dir(len, '\0');
858 ::GetSystemDirectoryA(&sys_dir[0], len);
859
860 ACTCTXA act_ctx = {
861 // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a
862 // crash with error “default context is already set”.
863 sizeof(act_ctx),
864 ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
865 "shell32.dll",
866 0,
867 0,
868 sys_dir.c_str(),
869 (LPCSTR)124,
870 nullptr,
871 0,
872 };
873
874 return ::CreateActCtxA(&act_ctx);
875}
876#endif // _WIN32
877
878// dialog implementation
879
880inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const {
881 return m_async->ready(timeout);
882}
883
884inline bool internal::dialog::kill() const {
885 return m_async->kill();
886}
887
888inline internal::dialog::dialog() : m_async(std::make_shared<executor>()) {
889}
890
891inline std::vector<std::string> internal::dialog::desktop_helper() const {
892#if __APPLE__
893 return { "osascript" };
894#else
895 return { flags(flag::has_zenity) ? "zenity"
896 : flags(flag::has_matedialog) ? "matedialog"
897 : flags(flag::has_qarma) ? "qarma"
898 : flags(flag::has_kdialog) ? "kdialog"
899 : "echo" };
900#endif
901}
902
903inline std::string internal::dialog::buttons_to_name(choice _choice) {
904 switch (_choice) {
906 return "okcancel";
907 case choice::yes_no:
908 return "yesno";
910 return "yesnocancel";
912 return "retrycancel";
914 return "abortretryignore";
915 /* case choice::ok: */ default:
916 return "ok";
917 }
918}
919
920inline std::string internal::dialog::get_icon_name(icon _icon) {
921 switch (_icon) {
922 case icon::warning:
923 return "warning";
924 case icon::error:
925 return "error";
926 case icon::question:
927 return "question";
928 // Zenity wants "information" but WinForms wants "info"
929 /* case icon::info: */ default:
930#if _WIN32
931 return "info";
932#else
933 return "information";
934#endif
935 }
936}
937
938// This is only used for debugging purposes
939inline std::ostream& operator<<(std::ostream& s, std::vector<std::string> const& v) {
940 int not_first = 0;
941 for (auto& e : v)
942 s << (not_first++ ? " " : "") << e;
943 return s;
944}
945
946// Properly quote a string for Powershell: replace ' or " with '' or ""
947// FIXME: we should probably get rid of newlines!
948// FIXME: the \" sequence seems unsafe, too!
949// XXX: this is no longer used but I would like to keep it around just in case
950inline std::string internal::dialog::powershell_quote(std::string const& str) const {
951 return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'";
952}
953
954// Properly quote a string for osascript: replace \ or " with \\ or \"
955// XXX: this also used to replace ' with \' when popen was used, but it would be
956// smarter to do shell_quote(osascript_quote(...)) if this is needed again.
957inline std::string internal::dialog::osascript_quote(std::string const& str) const {
958 return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\"";
959}
960
961// Properly quote a string for the shell: just replace ' with '\''
962// XXX: this is no longer used but I would like to keep it around just in case
963inline std::string internal::dialog::shell_quote(std::string const& str) const {
964 return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'";
965}
966
967// file_dialog implementation
968
969inline internal::file_dialog::file_dialog(type in_type, std::string const& title,
970 std::string const& default_path /* = "" */,
971 std::vector<std::string> const& filters /* = {} */,
972 opt options /* = opt::none */) {
973#if _WIN32
974 std::string filter_list;
975 std::regex whitespace(" *");
976 for (size_t i = 0; i + 1 < filters.size(); i += 2) {
977 filter_list += filters[i] + '\0';
978 filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0';
979 }
980 filter_list += '\0';
981
982 m_async->start_func([this, in_type, title, default_path, filter_list, options](int* exit_code) -> std::string {
983 (void)exit_code;
984 m_wtitle = internal::str2wstr(title);
985 m_wdefault_path = internal::str2wstr(default_path);
986 auto wfilter_list = internal::str2wstr(filter_list);
987
988 // Initialise COM. This is required for the new folder selection window,
989 // (see https://github.com/samhocevar/portable-file-dialogs/pull/21)
990 // and to avoid random crashes with GetOpenFileNameW() (see
991 // https://github.com/samhocevar/portable-file-dialogs/issues/51)
992 ole32_dll ole32;
993
994 // Folder selection uses a different method
995 if (in_type == type::folder) {
996#if PFD_HAS_IFILEDIALOG
997 if (flags(flag::is_vista)) {
998 // On Vista and higher we should be able to use IFileDialog for folder selection
999 IFileDialog* ifd;
1000 HRESULT hr = dll::proc<HRESULT WINAPI(REFCLSID, LPUNKNOWN, DWORD, REFIID, LPVOID*)>(
1001 ole32, "CoCreateInstance")(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd));
1002
1003 // In case CoCreateInstance fails (which it should not), try legacy approach
1004 if (SUCCEEDED(hr))
1005 return select_folder_vista(ifd, options & opt::force_path);
1006 }
1007#endif
1008
1009 BROWSEINFOW bi;
1010 memset(&bi, 0, sizeof(bi));
1011
1012 bi.lpfn = &bffcallback;
1013 bi.lParam = (LPARAM)this;
1014
1015 if (flags(flag::is_vista)) {
1016 if (ole32.is_initialized())
1017 bi.ulFlags |= BIF_NEWDIALOGSTYLE;
1018 bi.ulFlags |= BIF_EDITBOX;
1019 bi.ulFlags |= BIF_STATUSTEXT;
1020 }
1021
1022 auto* list = SHBrowseForFolderW(&bi);
1023 std::string ret;
1024 if (list) {
1025 auto buffer = new wchar_t[MAX_PATH];
1026 SHGetPathFromIDListW(list, buffer);
1027 dll::proc<void WINAPI(LPVOID)>(ole32, "CoTaskMemFree")(list);
1028 ret = internal::wstr2str(buffer);
1029 delete[] buffer;
1030 }
1031 return ret;
1032 }
1033
1034 OPENFILENAMEW ofn;
1035 memset(&ofn, 0, sizeof(ofn));
1036 ofn.lStructSize = sizeof(OPENFILENAMEW);
1037 ofn.hwndOwner = GetActiveWindow();
1038
1039 ofn.lpstrFilter = wfilter_list.c_str();
1040
1041 auto woutput = std::wstring(MAX_PATH * 256, L'\0');
1042 ofn.lpstrFile = (LPWSTR)woutput.data();
1043 ofn.nMaxFile = (DWORD)woutput.size();
1044 if (!m_wdefault_path.empty()) {
1045 // If a directory was provided, use it as the initial directory. If
1046 // a valid path was provided, use it as the initial file. Otherwise,
1047 // let the Windows API decide.
1048 auto path_attr = GetFileAttributesW(m_wdefault_path.c_str());
1049 if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY))
1050 ofn.lpstrInitialDir = m_wdefault_path.c_str();
1051 else if (m_wdefault_path.size() <= woutput.size())
1052 // second argument is size of buffer, not length of string
1053 StringCchCopyW(ofn.lpstrFile, MAX_PATH * 256 + 1, m_wdefault_path.c_str());
1054 else {
1055 ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data();
1056 ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size();
1057 }
1058 }
1059 ofn.lpstrTitle = m_wtitle.c_str();
1060 ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER;
1061
1062 dll comdlg32("comdlg32.dll");
1063
1064 // Apply new visual style (required for windows XP)
1065 new_style_context ctx;
1066
1067 if (in_type == type::save) {
1068 if (!(options & opt::force_overwrite))
1069 ofn.Flags |= OFN_OVERWRITEPROMPT;
1070
1071 dll::proc<BOOL WINAPI(LPOPENFILENAMEW)> get_save_file_name(comdlg32, "GetSaveFileNameW");
1072 if (get_save_file_name(&ofn) == 0)
1073 return "";
1074 return internal::wstr2str(woutput.c_str());
1075 } else {
1076 if (options & opt::multiselect)
1077 ofn.Flags |= OFN_ALLOWMULTISELECT;
1078 ofn.Flags |= OFN_PATHMUSTEXIST;
1079
1080 dll::proc<BOOL WINAPI(LPOPENFILENAMEW)> get_open_file_name(comdlg32, "GetOpenFileNameW");
1081 if (get_open_file_name(&ofn) == 0)
1082 return "";
1083 }
1084
1085 std::string prefix;
1086 for (wchar_t const* p = woutput.c_str(); *p;) {
1087 auto filename = internal::wstr2str(p);
1088 p += wcslen(p);
1089 // In multiselect mode, we advance p one wchar further and
1090 // check for another filename. If there is one and the
1091 // prefix is empty, it means we just read the prefix.
1092 if ((options & opt::multiselect) && *++p && prefix.empty()) {
1093 prefix = filename + "/";
1094 continue;
1095 }
1096
1097 m_vector_result.push_back(prefix + filename);
1098 }
1099
1100 return "";
1101 });
1102#elif __EMSCRIPTEN__
1103 // FIXME: do something
1104 (void)in_type;
1105 (void)title;
1106 (void)default_path;
1107 (void)filters;
1108 (void)options;
1109#else
1110 auto command = desktop_helper();
1111
1112 if (is_osascript()) {
1113 std::string script = "set ret to choose";
1114 switch (in_type) {
1115 case type::save:
1116 script += " file name";
1117 break;
1118 case type::open:
1119 default:
1120 script += " file";
1121 if (options & opt::multiselect)
1122 script += " with multiple selections allowed";
1123 break;
1124 case type::folder:
1125 script += " folder";
1126 break;
1127 }
1128
1129 if (default_path.size()) {
1130 if (in_type == type::folder || is_directory(default_path))
1131 script += " default location ";
1132 else
1133 script += " default name ";
1134 script += osascript_quote(default_path);
1135 }
1136
1137 script += " with prompt " + osascript_quote(title);
1138
1139 if (in_type == type::open) {
1140 // Concatenate all user-provided filter patterns
1141 std::string patterns;
1142 for (size_t i = 0; i < filters.size() / 2; ++i)
1143 patterns += " " + filters[2 * i + 1];
1144
1145 // Split the pattern list to check whether "*" is in there; if it
1146 // is, we have to disable filters because there is no mechanism in
1147 // OS X for the user to override the filter.
1148 std::regex sep("\\s+");
1149 std::string filter_list;
1150 bool has_filter = true;
1151 std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1);
1152 std::sregex_token_iterator end;
1153 for (; iter != end; ++iter) {
1154 auto pat = iter->str();
1155 if (pat == "*" || pat == "*.*")
1156 has_filter = false;
1157 else if (internal::starts_with(pat, "*."))
1158 filter_list += "," + osascript_quote(pat.substr(2, pat.size() - 2));
1159 }
1160
1161 if (has_filter && filter_list.size() > 0) {
1162 // There is a weird AppleScript bug where file extensions of length != 3 are
1163 // ignored, e.g. type{"txt"} works, but type{"json"} does not. Fortunately if
1164 // the whole list starts with a 3-character extension, everything works again.
1165 // We use "///" for such an extension because we are sure it cannot appear in
1166 // an actual filename.
1167 script += " of type {\"///\"" + filter_list + "}";
1168 }
1169 }
1170
1171 if (in_type == type::open && (options & opt::multiselect)) {
1172 script += "\nset s to \"\"";
1173 script += "\nrepeat with i in ret";
1174 script += "\n set s to s & (POSIX path of i) & \"\\n\"";
1175 script += "\nend repeat";
1176 script += "\ncopy s to stdout";
1177 } else {
1178 script += "\nPOSIX path of ret";
1179 }
1180
1181 command.push_back("-e");
1182 command.push_back(script);
1183 } else if (is_zenity()) {
1184 command.push_back("--file-selection");
1185
1186 // If the default path is a directory, make sure it ends with "/" otherwise zenity will
1187 // open the file dialog in the parent directory.
1188 auto filename_arg = "--filename=" + default_path;
1189 if (in_type != type::folder && !ends_with(default_path, "/") && internal::is_directory(default_path))
1190 filename_arg += "/";
1191 command.push_back(filename_arg);
1192
1193 command.push_back("--title");
1194 command.push_back(title);
1195 command.push_back("--separator=\n");
1196
1197 for (size_t i = 0; i < filters.size() / 2; ++i) {
1198 command.push_back("--file-filter");
1199 command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]);
1200 }
1201
1202 if (in_type == type::save)
1203 command.push_back("--save");
1204 if (in_type == type::folder)
1205 command.push_back("--directory");
1206 if (!(options & opt::force_overwrite))
1207 command.push_back("--confirm-overwrite");
1208 if (options & opt::multiselect)
1209 command.push_back("--multiple");
1210 } else if (is_kdialog()) {
1211 switch (in_type) {
1212 case type::save:
1213 command.push_back("--getsavefilename");
1214 break;
1215 case type::open:
1216 command.push_back("--getopenfilename");
1217 break;
1218 case type::folder:
1219 command.push_back("--getexistingdirectory");
1220 break;
1221 }
1222 if (options & opt::multiselect) {
1223 command.push_back("--multiple");
1224 command.push_back("--separate-output");
1225 }
1226
1227 command.push_back(default_path);
1228
1229 std::string filter;
1230 for (size_t i = 0; i < filters.size() / 2; ++i)
1231 filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")";
1232 command.push_back(filter);
1233
1234 command.push_back("--title");
1235 command.push_back(title);
1236 }
1237
1239 std::cerr << "pfd: " << command << std::endl;
1240
1241 m_async->start_process(command);
1242#endif
1243}
1244
1246#if _WIN32
1247 return m_async->result();
1248#else
1249 auto ret = m_async->result();
1250 // Strip potential trailing newline (zenity). Also strip trailing slash
1251 // added by osascript for consistency with other backends.
1252 while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/'))
1253 ret.pop_back();
1254 return ret;
1255#endif
1256}
1257
1258inline std::vector<std::string> internal::file_dialog::vector_result() {
1259#if _WIN32
1260 m_async->result();
1261 return m_vector_result;
1262#else
1263 std::vector<std::string> ret;
1264 auto result = m_async->result();
1265 for (;;) {
1266 // Split result along newline characters
1267 auto i = result.find('\n');
1268 if (i == 0 || i == std::string::npos)
1269 break;
1270 ret.push_back(result.substr(0, i));
1271 result = result.substr(i + 1, result.size());
1272 }
1273 return ret;
1274#endif
1275}
1276
1277#if _WIN32
1278// Use a static function to pass as BFFCALLBACK for legacy folder select
1279inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData) {
1280 auto inst = (file_dialog*)pData;
1281 switch (uMsg) {
1282 case BFFM_INITIALIZED:
1283 SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str());
1284 break;
1285 }
1286 return 0;
1287}
1288
1289#if PFD_HAS_IFILEDIALOG
1290inline std::string internal::file_dialog::select_folder_vista(IFileDialog* ifd, bool force_path) {
1291 std::string result;
1292
1293 IShellItem* folder;
1294
1295 // Load library at runtime so app doesn't link it at load time (which will fail on windows XP)
1296 dll shell32("shell32.dll");
1297 dll::proc<HRESULT WINAPI(PCWSTR, IBindCtx*, REFIID, void**)> create_item(shell32, "SHCreateItemFromParsingName");
1298
1299 if (!create_item)
1300 return "";
1301
1302 auto hr = create_item(m_wdefault_path.c_str(), nullptr, IID_PPV_ARGS(&folder));
1303
1304 // Set default folder if found. This only sets the default folder. If
1305 // Windows has any info about the most recently selected folder, it
1306 // will display it instead. Generally, calling SetFolder() to set the
1307 // current directory “is not a good or expected user experience and
1308 // should therefore be avoided”:
1309 // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder
1310 if (SUCCEEDED(hr)) {
1311 if (force_path)
1312 ifd->SetFolder(folder);
1313 else
1314 ifd->SetDefaultFolder(folder);
1315 folder->Release();
1316 }
1317
1318 // Set the dialog title and option to select folders
1319 ifd->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM);
1320 ifd->SetTitle(m_wtitle.c_str());
1321
1322 hr = ifd->Show(GetActiveWindow());
1323 if (SUCCEEDED(hr)) {
1324 IShellItem* item;
1325 hr = ifd->GetResult(&item);
1326 if (SUCCEEDED(hr)) {
1327 wchar_t* wname = nullptr;
1328 // This is unlikely to fail because we use FOS_FORCEFILESYSTEM, but try
1329 // to output a debug message just in case.
1330 if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &wname))) {
1331 result = internal::wstr2str(std::wstring(wname));
1332 dll::proc<void WINAPI(LPVOID)>(ole32_dll(), "CoTaskMemFree")(wname);
1333 } else {
1334 if (SUCCEEDED(item->GetDisplayName(SIGDN_NORMALDISPLAY, &wname))) {
1335 auto name = internal::wstr2str(std::wstring(wname));
1336 dll::proc<void WINAPI(LPVOID)>(ole32_dll(), "CoTaskMemFree")(wname);
1337 std::cerr << "pfd: failed to get path for " << name << std::endl;
1338 } else
1339 std::cerr << "pfd: item of unknown type selected" << std::endl;
1340 }
1341
1342 item->Release();
1343 }
1344 }
1345
1346 ifd->Release();
1347
1348 return result;
1349}
1350#endif
1351#endif
1352
1353// notify implementation
1354
1355inline notify::notify(std::string const& title, std::string const& message, icon _icon /* = icon::info */) {
1356 if (_icon == icon::question) // Not supported by notifications
1357 _icon = icon::info;
1358
1359#if _WIN32
1360 // Use a static shared pointer for notify_icon so that we can delete
1361 // it whenever we need to display a new one, and we can also wait
1362 // until the program has finished running.
1363 struct notify_icon_data : public NOTIFYICONDATAW {
1364 ~notify_icon_data() {
1365 Shell_NotifyIconW(NIM_DELETE, this);
1366 }
1367 };
1368
1369 static std::shared_ptr<notify_icon_data> nid;
1370
1371 // Release the previous notification icon, if any, and allocate a new
1372 // one. Note that std::make_shared() does value initialization, so there
1373 // is no need to memset the structure.
1374 nid = nullptr;
1375 nid = std::make_shared<notify_icon_data>();
1376
1377 // For XP support
1378 nid->cbSize = NOTIFYICONDATAW_V2_SIZE;
1379 nid->hWnd = nullptr;
1380 nid->uID = 0;
1381
1382 // Flag Description:
1383 // - NIF_ICON The hIcon member is valid.
1384 // - NIF_MESSAGE The uCallbackMessage member is valid.
1385 // - NIF_TIP The szTip member is valid.
1386 // - NIF_STATE The dwState and dwStateMask members are valid.
1387 // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and
1388 // dwInfoFlags members are valid.
1389 // - NIF_GUID Reserved.
1390 nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO;
1391
1392 // Flag Description
1393 // - NIIF_ERROR An error icon.
1394 // - NIIF_INFO An information icon.
1395 // - NIIF_NONE No icon.
1396 // - NIIF_WARNING A warning icon.
1397 // - NIIF_ICON_MASK Version 6.0. Reserved.
1398 // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips
1399 switch (_icon) {
1400 case icon::warning:
1401 nid->dwInfoFlags = NIIF_WARNING;
1402 break;
1403 case icon::error:
1404 nid->dwInfoFlags = NIIF_ERROR;
1405 break;
1406 /* case icon::info: */ default:
1407 nid->dwInfoFlags = NIIF_INFO;
1408 break;
1409 }
1410
1411 ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL {
1412 ((NOTIFYICONDATAW*)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName);
1413 return false;
1414 };
1415
1416 nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
1417 ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get());
1418
1419 nid->uTimeout = 5000;
1420
1421 StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str());
1422 StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str());
1423
1424 // Display the new icon
1425 Shell_NotifyIconW(NIM_ADD, nid.get());
1426#elif __EMSCRIPTEN__
1427 // FIXME: do something
1428 (void)title;
1429 (void)message;
1430#else
1431 auto command = desktop_helper();
1432
1433 if (is_osascript()) {
1434 command.push_back("-e");
1435 command.push_back("display notification " + osascript_quote(message) + " with title " + osascript_quote(title));
1436 } else if (is_zenity()) {
1437 command.push_back("--notification");
1438 command.push_back("--window-icon");
1439 command.push_back(get_icon_name(_icon));
1440 command.push_back("--text");
1441 command.push_back(title + "\n" + message);
1442 } else if (is_kdialog()) {
1443 command.push_back("--icon");
1444 command.push_back(get_icon_name(_icon));
1445 command.push_back("--title");
1446 command.push_back(title);
1447 command.push_back("--passivepopup");
1448 command.push_back(message);
1449 command.push_back("5");
1450 }
1451
1453 std::cerr << "pfd: " << command << std::endl;
1454
1455 m_async->start_process(command);
1456#endif
1457}
1458
1459// message implementation
1460
1461inline message::message(std::string const& title, std::string const& text, choice _choice /* = choice::ok_cancel */,
1462 icon _icon /* = icon::info */) {
1463#if _WIN32
1464 // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought
1465 // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52
1466 UINT style = MB_SYSTEMMODAL;
1467 switch (_icon) {
1468 case icon::warning:
1469 style |= MB_ICONWARNING;
1470 break;
1471 case icon::error:
1472 style |= MB_ICONERROR;
1473 break;
1474 case icon::question:
1475 style |= MB_ICONQUESTION;
1476 break;
1477 /* case icon::info: */ default:
1478 style |= MB_ICONINFORMATION;
1479 break;
1480 }
1481
1482 switch (_choice) {
1483 case choice::ok_cancel:
1484 style |= MB_OKCANCEL;
1485 break;
1486 case choice::yes_no:
1487 style |= MB_YESNO;
1488 break;
1490 style |= MB_YESNOCANCEL;
1491 break;
1493 style |= MB_RETRYCANCEL;
1494 break;
1496 style |= MB_ABORTRETRYIGNORE;
1497 break;
1498 /* case choice::ok: */ default:
1499 style |= MB_OK;
1500 break;
1501 }
1502
1503 m_mappings[IDCANCEL] = button::cancel;
1504 m_mappings[IDOK] = button::ok;
1505 m_mappings[IDYES] = button::yes;
1506 m_mappings[IDNO] = button::no;
1507 m_mappings[IDABORT] = button::abort;
1508 m_mappings[IDRETRY] = button::retry;
1509 m_mappings[IDIGNORE] = button::ignore;
1510
1511 m_async->start_func([text, title, style](int* exit_code) -> std::string {
1512 auto wtext = internal::str2wstr(text);
1513 auto wtitle = internal::str2wstr(title);
1514 // Apply new visual style (required for all Windows versions)
1515 new_style_context ctx;
1516 *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style);
1517 return "";
1518 });
1519
1520#elif __EMSCRIPTEN__
1521 std::string full_message;
1522 switch (_icon) {
1523 case icon::warning:
1524 full_message = "⚠️";
1525 break;
1526 case icon::error:
1527 full_message = "⛔";
1528 break;
1529 case icon::question:
1530 full_message = "❓";
1531 break;
1532 /* case icon::info: */ default:
1533 full_message = "ℹ";
1534 break;
1535 }
1536
1537 full_message += ' ' + title + "\n\n" + text;
1538
1539 // This does not really start an async task; it just passes the
1540 // EM_ASM_INT return value to a fake start() function.
1541 m_async->start(EM_ASM_INT(
1542 {
1543 if ($1)
1544 return window.confirm(UTF8ToString($0)) ? 0 : -1;
1545 alert(UTF8ToString($0));
1546 return 0;
1547 },
1548 full_message.c_str(), _choice == choice::ok_cancel));
1549#else
1550 auto command = desktop_helper();
1551
1552 if (is_osascript()) {
1553 std::string script = "display dialog " + osascript_quote(text) + " with title " + osascript_quote(title);
1554 auto if_cancel = button::cancel;
1555 switch (_choice) {
1556 case choice::ok_cancel:
1557 script += "buttons {\"OK\", \"Cancel\"}"
1558 " default button \"OK\""
1559 " cancel button \"Cancel\"";
1560 break;
1561 case choice::yes_no:
1562 script += "buttons {\"Yes\", \"No\"}"
1563 " default button \"Yes\""
1564 " cancel button \"No\"";
1565 if_cancel = button::no;
1566 break;
1568 script += "buttons {\"Yes\", \"No\", \"Cancel\"}"
1569 " default button \"Yes\""
1570 " cancel button \"Cancel\"";
1571 break;
1573 script += "buttons {\"Retry\", \"Cancel\"}"
1574 " default button \"Retry\""
1575 " cancel button \"Cancel\"";
1576 break;
1578 script += "buttons {\"Abort\", \"Retry\", \"Ignore\"}"
1579 " default button \"Abort\""
1580 " cancel button \"Retry\"";
1581 if_cancel = button::retry;
1582 break;
1583 case choice::ok:
1584 default:
1585 script += "buttons {\"OK\"}"
1586 " default button \"OK\""
1587 " cancel button \"OK\"";
1588 if_cancel = button::ok;
1589 break;
1590 }
1591 m_mappings[1] = if_cancel;
1592 m_mappings[256] = if_cancel; // XXX: I think this was never correct
1593 script += " with icon ";
1594 switch (_icon) {
1595#define PFD_OSX_ICON(n) \
1596 "alias ((path to library folder from system domain) as text " \
1597 "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")"
1598 case icon::info:
1599 default:
1600 script += PFD_OSX_ICON("ToolBarInfo");
1601 break;
1602 case icon::warning:
1603 script += "caution";
1604 break;
1605 case icon::error:
1606 script += "stop";
1607 break;
1608 case icon::question:
1609 script += PFD_OSX_ICON("GenericQuestionMarkIcon");
1610 break;
1611#undef PFD_OSX_ICON
1612 }
1613
1614 command.push_back("-e");
1615 command.push_back(script);
1616 } else if (is_zenity()) {
1617 switch (_choice) {
1618 case choice::ok_cancel:
1619 command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" });
1620 break;
1621 case choice::yes_no:
1622 // Do not use standard --question because it causes “No” to return -1,
1623 // which is inconsistent with the “Yes/No/Cancel” mode below.
1624 command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" });
1625 break;
1627 command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No",
1628 "--extra-button=Yes" });
1629 break;
1631 command.insert(command.end(),
1632 { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" });
1633 break;
1635 command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore",
1636 "--extra-button=Abort", "--extra-button=Retry" });
1637 break;
1638 case choice::ok:
1639 default:
1640 switch (_icon) {
1641 case icon::error:
1642 command.push_back("--error");
1643 break;
1644 case icon::warning:
1645 command.push_back("--warning");
1646 break;
1647 default:
1648 command.push_back("--info");
1649 break;
1650 }
1651 }
1652
1653 command.insert(command.end(), { "--title", title, "--width=300", "--height=0", // sensible defaults
1654 "--no-markup", // do not interpret text as Pango markup
1655 "--text", text, "--icon-name=dialog-" + get_icon_name(_icon) });
1656 } else if (is_kdialog()) {
1657 if (_choice == choice::ok) {
1658 switch (_icon) {
1659 case icon::error:
1660 command.push_back("--error");
1661 break;
1662 case icon::warning:
1663 command.push_back("--sorry");
1664 break;
1665 default:
1666 command.push_back("--msgbox");
1667 break;
1668 }
1669 } else {
1670 std::string flag = "--";
1671 if (_icon == icon::warning || _icon == icon::error)
1672 flag += "warning";
1673 flag += "yesno";
1674 if (_choice == choice::yes_no_cancel)
1675 flag += "cancel";
1676 command.push_back(flag);
1677 if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) {
1679 m_mappings[256] = button::no;
1680 }
1681 }
1682
1683 command.push_back(text);
1684 command.push_back("--title");
1685 command.push_back(title);
1686
1687 // Must be after the above part
1688 if (_choice == choice::ok_cancel)
1689 command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" });
1690 }
1691
1693 std::cerr << "pfd: " << command << std::endl;
1694
1695 m_async->start_process(command);
1696#endif
1697}
1698
1700 int exit_code;
1701 auto ret = m_async->result(&exit_code);
1702 // osascript will say "button returned:Cancel\n"
1703 // and others will just say "Cancel\n"
1704 if (internal::ends_with(ret, "Cancel\n"))
1705 return button::cancel;
1706 if (internal::ends_with(ret, "OK\n"))
1707 return button::ok;
1708 if (internal::ends_with(ret, "Yes\n"))
1709 return button::yes;
1710 if (internal::ends_with(ret, "No\n"))
1711 return button::no;
1712 if (internal::ends_with(ret, "Abort\n"))
1713 return button::abort;
1714 if (internal::ends_with(ret, "Retry\n"))
1715 return button::retry;
1716 if (internal::ends_with(ret, "Ignore\n"))
1717 return button::ignore;
1718 if (m_mappings.count(exit_code) != 0)
1719 return m_mappings[exit_code];
1720 return exit_code == 0 ? button::ok : button::cancel;
1721}
1722
1723// open_file implementation
1724
1725inline open_file::open_file(std::string const& title, std::string const& default_path /* = "" */,
1726 std::vector<std::string> const& filters /* = { "All Files", "*" } */,
1727 opt options /* = opt::none */)
1728 : file_dialog(type::open, title, default_path, filters, options) {
1729}
1730
1731inline open_file::open_file(std::string const& title, std::string const& default_path,
1732 std::vector<std::string> const& filters, bool allow_multiselect)
1733 : open_file(title, default_path, filters, (allow_multiselect ? opt::multiselect : opt::none)) {
1734}
1735
1736inline std::vector<std::string> open_file::result() {
1737 return vector_result();
1738}
1739
1740// save_file implementation
1741
1742inline save_file::save_file(std::string const& title, std::string const& default_path /* = "" */,
1743 std::vector<std::string> const& filters /* = { "All Files", "*" } */,
1744 opt options /* = opt::none */)
1745 : file_dialog(type::save, title, default_path, filters, options) {
1746}
1747
1748inline save_file::save_file(std::string const& title, std::string const& default_path,
1749 std::vector<std::string> const& filters, bool confirm_overwrite)
1750 : save_file(title, default_path, filters, (confirm_overwrite ? opt::none : opt::force_overwrite)) {
1751}
1752
1753inline std::string save_file::result() {
1754 return string_result();
1755}
1756
1757// select_folder implementation
1758
1759inline select_folder::select_folder(std::string const& title, std::string const& default_path /* = "" */,
1760 opt options /* = opt::none */)
1761 : file_dialog(type::folder, title, default_path, {}, options) {
1762}
1763
1764inline std::string select_folder::result() {
1765 return string_result();
1766}
1767
1768#endif // PFD_SKIP_IMPLEMENTATION
1769
1770} // namespace pfd
static const char * filters[3]
Definition ImguiUI.cpp:121
Definition portable-file-dialogs.h:259
dialog()
Definition portable-file-dialogs.h:888
std::string shell_quote(std::string const &str) const
Definition portable-file-dialogs.h:963
bool kill() const
Definition portable-file-dialogs.h:884
bool ready(int timeout=default_wait_timeout) const
Definition portable-file-dialogs.h:880
std::shared_ptr< executor > m_async
Definition portable-file-dialogs.h:276
std::vector< std::string > desktop_helper() const
Definition portable-file-dialogs.h:891
std::string powershell_quote(std::string const &str) const
Definition portable-file-dialogs.h:950
static std::string buttons_to_name(choice _choice)
Definition portable-file-dialogs.h:903
std::string osascript_quote(std::string const &str) const
Definition portable-file-dialogs.h:957
static std::string get_icon_name(icon _icon)
Definition portable-file-dialogs.h:920
Definition portable-file-dialogs.h:162
std::string m_stdout
Definition portable-file-dialogs.h:189
std::string result(int *exit_code=nullptr)
Definition portable-file-dialogs.h:627
int m_fd
Definition portable-file-dialogs.h:201
~executor()
Definition portable-file-dialogs.h:736
void stop()
Definition portable-file-dialogs.h:792
bool m_running
Definition portable-file-dialogs.h:188
pid_t m_pid
Definition portable-file-dialogs.h:200
bool ready(int timeout=default_wait_timeout)
Definition portable-file-dialogs.h:740
friend class dialog
Definition portable-file-dialogs.h:163
bool kill()
Definition portable-file-dialogs.h:634
void start_process(std::vector< std::string > const &command)
Definition portable-file-dialogs.h:693
int m_exit_code
Definition portable-file-dialogs.h:190
Definition portable-file-dialogs.h:279
file_dialog(type in_type, std::string const &title, std::string const &default_path="", std::vector< std::string > const &filters={}, opt options=opt::none)
Definition portable-file-dialogs.h:969
type
Definition portable-file-dialogs.h:281
@ save
Definition portable-file-dialogs.h:283
@ open
Definition portable-file-dialogs.h:282
@ folder
Definition portable-file-dialogs.h:284
std::string string_result()
Definition portable-file-dialogs.h:1245
std::vector< std::string > vector_result()
Definition portable-file-dialogs.h:1258
Definition portable-file-dialogs.h:205
Definition portable-file-dialogs.h:332
message(std::string const &title, std::string const &text, choice _choice=choice::ok_cancel, icon _icon=icon::info)
Definition portable-file-dialogs.h:1461
std::map< int, button > m_mappings
Definition portable-file-dialogs.h:341
button result()
Definition portable-file-dialogs.h:1699
notify(std::string const &title, std::string const &message, icon _icon=icon::info)
Definition portable-file-dialogs.h:1355
open_file(std::string const &title, std::string const &default_path="", std::vector< std::string > const &filters={ "All Files", "*" }, opt options=opt::none)
Definition portable-file-dialogs.h:1725
std::vector< std::string > result()
Definition portable-file-dialogs.h:1736
Definition portable-file-dialogs.h:313
static std::string home()
Definition portable-file-dialogs.h:577
static std::string separator()
Definition portable-file-dialogs.h:617
save_file(std::string const &title, std::string const &default_path="", std::vector< std::string > const &filters={ "All Files", "*" }, opt options=opt::none)
Definition portable-file-dialogs.h:1742
std::string result()
Definition portable-file-dialogs.h:1753
std::string result()
Definition portable-file-dialogs.h:1764
select_folder(std::string const &title, std::string const &default_path="", opt options=opt::none)
Definition portable-file-dialogs.h:1759
bool is_zenity() const
Definition portable-file-dialogs.h:559
static void verbose(bool value)
Definition portable-file-dialogs.h:526
bool const & flags(flag in_flag) const
Definition portable-file-dialogs.h:567
bool is_osascript() const
Definition portable-file-dialogs.h:551
static bool available()
Definition portable-file-dialogs.h:511
static void rescan()
Definition portable-file-dialogs.h:530
flag
Definition portable-file-dialogs.h:136
@ is_scanned
Definition portable-file-dialogs.h:137
@ has_matedialog
Definition portable-file-dialogs.h:141
@ has_qarma
Definition portable-file-dialogs.h:142
@ has_kdialog
Definition portable-file-dialogs.h:143
@ max_flag
Definition portable-file-dialogs.h:146
@ is_verbose
Definition portable-file-dialogs.h:138
@ is_vista
Definition portable-file-dialogs.h:144
@ has_zenity
Definition portable-file-dialogs.h:140
settings(bool resync=false)
Definition portable-file-dialogs.h:479
bool is_kdialog() const
Definition portable-file-dialogs.h:563
bool check_program(std::string const &program)
Definition portable-file-dialogs.h:535
uint16_t in
Definition mixer.c:80
int16_t filter[8]
Definition mixer.c:94
uint16_t out
Definition mixer.c:81
union @111115220225173102313045065244126153342261214353::@304166376113356371145235255022050055005326174300 buf
Definition portable-file-dialogs.h:157
static bool starts_with(std::string const &str, std::string const &prefix)
Definition portable-file-dialogs.h:438
static bool is_directory(std::string const &path)
Definition portable-file-dialogs.h:444
static std::string getenv(std::string const &str)
Definition portable-file-dialogs.h:459
static bool ends_with(std::string const &str, std::string const &suffix)
Definition portable-file-dialogs.h:434
static int const default_wait_timeout
Definition portable-file-dialogs.h:160
Definition portable-file-dialogs.h:70
opt
Definition portable-file-dialogs.h:99
@ none
Definition portable-file-dialogs.h:100
@ force_path
Definition portable-file-dialogs.h:108
@ force_overwrite
Definition portable-file-dialogs.h:104
@ multiselect
Definition portable-file-dialogs.h:102
choice
Definition portable-file-dialogs.h:82
@ yes_no
Definition portable-file-dialogs.h:85
@ ok
Definition portable-file-dialogs.h:83
@ yes_no_cancel
Definition portable-file-dialogs.h:86
@ retry_cancel
Definition portable-file-dialogs.h:87
@ ok_cancel
Definition portable-file-dialogs.h:84
@ abort_retry_ignore
Definition portable-file-dialogs.h:88
icon
Definition portable-file-dialogs.h:91
@ question
Definition portable-file-dialogs.h:95
@ warning
Definition portable-file-dialogs.h:93
@ info
Definition portable-file-dialogs.h:92
@ error
Definition portable-file-dialogs.h:94
button
Definition portable-file-dialogs.h:72
@ cancel
Definition portable-file-dialogs.h:73
@ retry
Definition portable-file-dialogs.h:78
@ ok
Definition portable-file-dialogs.h:74
@ ignore
Definition portable-file-dialogs.h:79
@ abort
Definition portable-file-dialogs.h:77
@ no
Definition portable-file-dialogs.h:76
@ yes
Definition portable-file-dialogs.h:75
opt operator|(opt a, opt b)
Definition portable-file-dialogs.h:111
bool operator&(opt a, opt b)
Definition portable-file-dialogs.h:114
std::ostream & operator<<(std::ostream &s, std::vector< std::string > const &v)
Definition portable-file-dialogs.h:939
#define PFD_OSX_ICON(n)