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