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