2022-03-14 03:02:50 +00:00
|
|
|
|
//
|
|
|
|
|
// Portable File Dialogs
|
|
|
|
|
//
|
|
|
|
|
// Copyright © 2018—2020 Sam Hocevar <sam@hocevar.net>
|
|
|
|
|
//
|
|
|
|
|
// This library is free software. It comes without any warranty, to
|
|
|
|
|
// the extent permitted by applicable law. You can redistribute it
|
|
|
|
|
// and/or modify it under the terms of the Do What the Fuck You Want
|
|
|
|
|
// to Public License, Version 2, as published by the WTFPL Task Force.
|
|
|
|
|
// See http://www.wtfpl.net/ for more details.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
|
|
|
# define WIN32_LEAN_AND_MEAN 1
|
|
|
|
|
#endif
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <commdlg.h>
|
|
|
|
|
#include <shlobj.h>
|
|
|
|
|
#include <shobjidl.h> // IFileDialog
|
|
|
|
|
#include <shellapi.h>
|
|
|
|
|
#include <strsafe.h>
|
|
|
|
|
#include <future> // std::async
|
|
|
|
|
|
|
|
|
|
#elif __EMSCRIPTEN__
|
|
|
|
|
#include <emscripten.h>
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
#ifndef _POSIX_C_SOURCE
|
|
|
|
|
# define _POSIX_C_SOURCE 2 // for popen()
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef __APPLE__
|
|
|
|
|
# ifndef _DARWIN_C_SOURCE
|
|
|
|
|
# define _DARWIN_C_SOURCE
|
|
|
|
|
# endif
|
|
|
|
|
#endif
|
|
|
|
|
#include <cstdio> // popen()
|
|
|
|
|
#include <cstdlib> // std::getenv()
|
|
|
|
|
#include <fcntl.h> // fcntl()
|
|
|
|
|
#include <unistd.h> // read(), pipe(), dup2()
|
|
|
|
|
#include <csignal> // ::kill, std::signal
|
|
|
|
|
#include <sys/wait.h> // waitpid()
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <string> // std::string
|
|
|
|
|
#include <memory> // std::shared_ptr
|
|
|
|
|
#include <iostream> // std::ostream
|
|
|
|
|
#include <map> // std::map
|
|
|
|
|
#include <set> // std::set
|
|
|
|
|
#include <regex> // std::regex
|
|
|
|
|
#include <thread> // std::mutex, std::this_thread
|
|
|
|
|
#include <chrono> // std::chrono
|
|
|
|
|
|
|
|
|
|
// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog
|
2022-12-31 21:50:41 +00:00
|
|
|
|
// we don't use PFD on Windows anyway
|
2022-03-14 03:02:50 +00:00
|
|
|
|
#ifndef PFD_HAS_IFILEDIALOG
|
2022-12-31 21:50:41 +00:00
|
|
|
|
#define PFD_HAS_IFILEDIALOG 0
|
|
|
|
|
//# define PFD_HAS_IFILEDIALOG 1
|
|
|
|
|
//# if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION
|
|
|
|
|
//# if __GXX_ABI_VERSION <= 1014
|
|
|
|
|
//# undef PFD_HAS_IFILEDIALOG
|
|
|
|
|
//# define PFD_HAS_IFILEDIALOG 0
|
|
|
|
|
//# endif
|
|
|
|
|
//# endif
|
2022-03-14 03:02:50 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
namespace pfd
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
enum class button
|
|
|
|
|
{
|
|
|
|
|
cancel = -1,
|
|
|
|
|
ok,
|
|
|
|
|
yes,
|
|
|
|
|
no,
|
|
|
|
|
abort,
|
|
|
|
|
retry,
|
|
|
|
|
ignore,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class choice
|
|
|
|
|
{
|
|
|
|
|
ok = 0,
|
|
|
|
|
ok_cancel,
|
|
|
|
|
yes_no,
|
|
|
|
|
yes_no_cancel,
|
|
|
|
|
retry_cancel,
|
|
|
|
|
abort_retry_ignore,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class icon
|
|
|
|
|
{
|
|
|
|
|
info = 0,
|
|
|
|
|
warning,
|
|
|
|
|
error,
|
|
|
|
|
question,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Additional option flags for various dialog constructors
|
|
|
|
|
enum class opt : uint8_t
|
|
|
|
|
{
|
|
|
|
|
none = 0,
|
|
|
|
|
// For file open, allow multiselect.
|
|
|
|
|
multiselect = 0x1,
|
|
|
|
|
// For file save, force overwrite and disable the confirmation dialog.
|
|
|
|
|
force_overwrite = 0x2,
|
|
|
|
|
// For folder select, force path to be the provided argument instead
|
|
|
|
|
// of the last opened directory, which is the Microsoft-recommended,
|
|
|
|
|
// user-friendly behaviour.
|
|
|
|
|
force_path = 0x4,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
inline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); }
|
|
|
|
|
inline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); }
|
|
|
|
|
|
|
|
|
|
// The settings class, only exposing to the user a way to set verbose mode
|
|
|
|
|
// and to force a rescan of installed desktop helpers (zenity, kdialog…).
|
|
|
|
|
class settings
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static bool available();
|
|
|
|
|
|
|
|
|
|
static void verbose(bool value);
|
|
|
|
|
static void rescan();
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
explicit settings(bool resync = false);
|
|
|
|
|
|
|
|
|
|
bool check_program(std::string const &program);
|
|
|
|
|
|
|
|
|
|
inline bool is_osascript() const;
|
|
|
|
|
inline bool is_zenity() const;
|
|
|
|
|
inline bool is_kdialog() const;
|
|
|
|
|
|
|
|
|
|
enum class flag
|
|
|
|
|
{
|
|
|
|
|
is_scanned = 0,
|
|
|
|
|
is_verbose,
|
|
|
|
|
|
|
|
|
|
has_zenity,
|
|
|
|
|
has_matedialog,
|
|
|
|
|
has_qarma,
|
|
|
|
|
has_kdialog,
|
|
|
|
|
is_vista,
|
|
|
|
|
|
|
|
|
|
max_flag,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Static array of flags for internal state
|
|
|
|
|
bool const &flags(flag in_flag) const;
|
|
|
|
|
|
|
|
|
|
// Non-const getter for the static array of flags
|
|
|
|
|
bool &flags(flag in_flag);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Internal classes, not to be used by client applications
|
|
|
|
|
namespace internal
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// Process wait timeout, in milliseconds
|
|
|
|
|
static int const default_wait_timeout = 20;
|
|
|
|
|
|
|
|
|
|
class executor
|
|
|
|
|
{
|
|
|
|
|
friend class dialog;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
// High level function to get the result of a command
|
|
|
|
|
std::string result(int *exit_code = nullptr);
|
|
|
|
|
|
|
|
|
|
// High level function to abort
|
|
|
|
|
bool kill();
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
void start_func(std::function<std::string(int *)> const &fun);
|
|
|
|
|
static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam);
|
|
|
|
|
#elif __EMSCRIPTEN__
|
|
|
|
|
void start(int exit_code);
|
|
|
|
|
#else
|
|
|
|
|
void start_process(std::vector<std::string> const &command);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
~executor();
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
bool ready(int timeout = default_wait_timeout);
|
|
|
|
|
void stop();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool m_running = false;
|
|
|
|
|
std::string m_stdout;
|
|
|
|
|
int m_exit_code = -1;
|
|
|
|
|
#if _WIN32
|
|
|
|
|
std::future<std::string> m_future;
|
|
|
|
|
std::set<HWND> m_windows;
|
|
|
|
|
std::condition_variable m_cond;
|
|
|
|
|
std::mutex m_mutex;
|
|
|
|
|
DWORD m_tid;
|
|
|
|
|
#elif __EMSCRIPTEN__ || __NX__
|
|
|
|
|
// FIXME: do something
|
|
|
|
|
#else
|
|
|
|
|
pid_t m_pid = 0;
|
|
|
|
|
int m_fd = -1;
|
|
|
|
|
#endif
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class platform
|
|
|
|
|
{
|
|
|
|
|
protected:
|
|
|
|
|
#if _WIN32
|
|
|
|
|
// Helper class around LoadLibraryA() and GetProcAddress() with some safety
|
|
|
|
|
class dll
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
dll(std::string const &name);
|
|
|
|
|
~dll();
|
|
|
|
|
|
|
|
|
|
template<typename T> class proc
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
proc(dll const &lib, std::string const &sym)
|
|
|
|
|
: m_proc(reinterpret_cast<T *>(::GetProcAddress(lib.handle, sym.c_str())))
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
operator bool() const { return m_proc != nullptr; }
|
|
|
|
|
operator T *() const { return m_proc; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
T *m_proc;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
HMODULE handle;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Helper class around CoInitialize() and CoUnInitialize()
|
|
|
|
|
class ole32_dll : public dll
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
ole32_dll();
|
|
|
|
|
~ole32_dll();
|
|
|
|
|
bool is_initialized();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
HRESULT m_state;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Helper class around CreateActCtx() and ActivateActCtx()
|
|
|
|
|
class new_style_context
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
new_style_context();
|
|
|
|
|
~new_style_context();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
HANDLE create();
|
|
|
|
|
ULONG_PTR m_cookie = 0;
|
|
|
|
|
};
|
|
|
|
|
#endif
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class dialog : protected settings, protected platform
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
bool ready(int timeout = default_wait_timeout) const;
|
|
|
|
|
bool kill() const;
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
explicit dialog();
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> desktop_helper() const;
|
|
|
|
|
static std::string buttons_to_name(choice _choice);
|
|
|
|
|
static std::string get_icon_name(icon _icon);
|
|
|
|
|
|
|
|
|
|
std::string powershell_quote(std::string const &str) const;
|
|
|
|
|
std::string osascript_quote(std::string const &str) const;
|
|
|
|
|
std::string shell_quote(std::string const &str) const;
|
|
|
|
|
|
|
|
|
|
// Keep handle to executing command
|
|
|
|
|
std::shared_ptr<executor> m_async;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class file_dialog : public dialog
|
|
|
|
|
{
|
|
|
|
|
protected:
|
|
|
|
|
enum type
|
|
|
|
|
{
|
|
|
|
|
open,
|
|
|
|
|
save,
|
|
|
|
|
folder,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
file_dialog(type in_type,
|
|
|
|
|
std::string const &title,
|
|
|
|
|
std::string const &default_path = "",
|
|
|
|
|
std::vector<std::string> const &filters = {},
|
|
|
|
|
opt options = opt::none);
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
std::string string_result();
|
|
|
|
|
std::vector<std::string> vector_result();
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData);
|
|
|
|
|
#if PFD_HAS_IFILEDIALOG
|
|
|
|
|
std::string select_folder_vista(IFileDialog *ifd, bool force_path);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
std::wstring m_wtitle;
|
|
|
|
|
std::wstring m_wdefault_path;
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> m_vector_result;
|
|
|
|
|
#endif
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// The notify widget
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
class notify : public internal::dialog
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
notify(std::string const &title,
|
|
|
|
|
std::string const &message,
|
|
|
|
|
icon _icon = icon::info);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// The message widget
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
class message : public internal::dialog
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
message(std::string const &title,
|
|
|
|
|
std::string const &text,
|
|
|
|
|
choice _choice = choice::ok_cancel,
|
|
|
|
|
icon _icon = icon::info);
|
|
|
|
|
|
|
|
|
|
button result();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Some extra logic to map the exit code to button number
|
|
|
|
|
std::map<int, button> m_mappings;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// The open_file, save_file, and open_folder widgets
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
class open_file : public internal::file_dialog
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
open_file(std::string const &title,
|
|
|
|
|
std::string const &default_path = "",
|
|
|
|
|
std::vector<std::string> const &filters = { "All Files", "*" },
|
|
|
|
|
opt options = opt::none);
|
|
|
|
|
|
|
|
|
|
#if defined(__has_cpp_attribute)
|
|
|
|
|
#if __has_cpp_attribute(deprecated)
|
|
|
|
|
// Backwards compatibility
|
|
|
|
|
[[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]]
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
open_file(std::string const &title,
|
|
|
|
|
std::string const &default_path,
|
|
|
|
|
std::vector<std::string> const &filters,
|
|
|
|
|
bool allow_multiselect);
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> result();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class save_file : public internal::file_dialog
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
save_file(std::string const &title,
|
|
|
|
|
std::string const &default_path = "",
|
|
|
|
|
std::vector<std::string> const &filters = { "All Files", "*" },
|
|
|
|
|
opt options = opt::none);
|
|
|
|
|
|
|
|
|
|
#if defined(__has_cpp_attribute)
|
|
|
|
|
#if __has_cpp_attribute(deprecated)
|
|
|
|
|
// Backwards compatibility
|
|
|
|
|
[[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]]
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
save_file(std::string const &title,
|
|
|
|
|
std::string const &default_path,
|
|
|
|
|
std::vector<std::string> const &filters,
|
|
|
|
|
bool confirm_overwrite);
|
|
|
|
|
|
|
|
|
|
std::string result();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class select_folder : public internal::file_dialog
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
select_folder(std::string const &title,
|
|
|
|
|
std::string const &default_path = "",
|
|
|
|
|
opt options = opt::none);
|
|
|
|
|
|
|
|
|
|
std::string result();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Below this are all the method implementations. You may choose to define the
|
|
|
|
|
// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except
|
|
|
|
|
// in one place. This may reduce compilation times.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#if !defined PFD_SKIP_IMPLEMENTATION
|
|
|
|
|
|
|
|
|
|
// internal free functions implementations
|
|
|
|
|
|
|
|
|
|
namespace internal
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
static inline std::wstring str2wstr(std::string const &str)
|
|
|
|
|
{
|
|
|
|
|
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0);
|
|
|
|
|
std::wstring ret(len, '\0');
|
|
|
|
|
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size());
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline std::string wstr2str(std::wstring const &str)
|
|
|
|
|
{
|
|
|
|
|
int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr);
|
|
|
|
|
std::string ret(len, '\0');
|
|
|
|
|
WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline bool is_vista()
|
|
|
|
|
{
|
|
|
|
|
OSVERSIONINFOEXW osvi;
|
|
|
|
|
memset(&osvi, 0, sizeof(osvi));
|
|
|
|
|
DWORDLONG const mask = VerSetConditionMask(
|
|
|
|
|
VerSetConditionMask(
|
|
|
|
|
VerSetConditionMask(
|
|
|
|
|
0, VER_MAJORVERSION, VER_GREATER_EQUAL),
|
|
|
|
|
VER_MINORVERSION, VER_GREATER_EQUAL),
|
|
|
|
|
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
|
|
|
|
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
|
|
|
|
osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA);
|
|
|
|
|
osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA);
|
|
|
|
|
osvi.wServicePackMajor = 0;
|
|
|
|
|
|
|
|
|
|
return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// This is necessary until C++20 which will have std::string::ends_with() etc.
|
|
|
|
|
|
|
|
|
|
static inline bool ends_with(std::string const &str, std::string const &suffix)
|
|
|
|
|
{
|
|
|
|
|
return suffix.size() <= str.size() &&
|
|
|
|
|
str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline bool starts_with(std::string const &str, std::string const &prefix)
|
|
|
|
|
{
|
|
|
|
|
return prefix.size() <= str.size() &&
|
|
|
|
|
str.compare(0, prefix.size(), prefix) == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
|
|
|
|
|
|
// settings implementation
|
|
|
|
|
|
|
|
|
|
inline settings::settings(bool resync)
|
|
|
|
|
{
|
|
|
|
|
flags(flag::is_scanned) &= !resync;
|
|
|
|
|
|
|
|
|
|
if (flags(flag::is_scanned))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
flags(flag::is_vista) = internal::is_vista();
|
|
|
|
|
#elif !__APPLE__
|
|
|
|
|
flags(flag::has_zenity) = check_program("zenity");
|
|
|
|
|
flags(flag::has_matedialog) = check_program("matedialog");
|
|
|
|
|
flags(flag::has_qarma) = check_program("qarma");
|
|
|
|
|
flags(flag::has_kdialog) = check_program("kdialog");
|
|
|
|
|
|
|
|
|
|
// If multiple helpers are available, try to default to the best one
|
|
|
|
|
if (flags(flag::has_zenity) && flags(flag::has_kdialog))
|
|
|
|
|
{
|
|
|
|
|
auto desktop_name = std::getenv("XDG_SESSION_DESKTOP");
|
|
|
|
|
if (desktop_name && desktop_name == std::string("gnome"))
|
|
|
|
|
flags(flag::has_kdialog) = false;
|
|
|
|
|
else if (desktop_name && desktop_name == std::string("KDE"))
|
|
|
|
|
flags(flag::has_zenity) = false;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
flags(flag::is_scanned) = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool settings::available()
|
|
|
|
|
{
|
|
|
|
|
#if _WIN32
|
|
|
|
|
return true;
|
|
|
|
|
#elif __APPLE__
|
|
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
settings tmp;
|
|
|
|
|
return tmp.flags(flag::has_zenity) ||
|
|
|
|
|
tmp.flags(flag::has_matedialog) ||
|
|
|
|
|
tmp.flags(flag::has_qarma) ||
|
|
|
|
|
tmp.flags(flag::has_kdialog);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void settings::verbose(bool value)
|
|
|
|
|
{
|
|
|
|
|
settings().flags(flag::is_verbose) = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void settings::rescan()
|
|
|
|
|
{
|
|
|
|
|
settings(/* resync = */ true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check whether a program is present using “which”.
|
|
|
|
|
inline bool settings::check_program(std::string const &program)
|
|
|
|
|
{
|
|
|
|
|
#if _WIN32
|
|
|
|
|
(void)program;
|
|
|
|
|
return false;
|
|
|
|
|
#elif __EMSCRIPTEN__
|
|
|
|
|
(void)program;
|
|
|
|
|
return false;
|
|
|
|
|
#else
|
|
|
|
|
int exit_code = -1;
|
|
|
|
|
internal::executor async;
|
|
|
|
|
async.start_process({"/bin/sh", "-c", "which " + program});
|
|
|
|
|
async.result(&exit_code);
|
|
|
|
|
return exit_code == 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool settings::is_osascript() const
|
|
|
|
|
{
|
|
|
|
|
#if __APPLE__
|
|
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool settings::is_zenity() const
|
|
|
|
|
{
|
|
|
|
|
return flags(flag::has_zenity) ||
|
|
|
|
|
flags(flag::has_matedialog) ||
|
|
|
|
|
flags(flag::has_qarma);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool settings::is_kdialog() const
|
|
|
|
|
{
|
|
|
|
|
return flags(flag::has_kdialog);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool const &settings::flags(flag in_flag) const
|
|
|
|
|
{
|
|
|
|
|
static bool flags[size_t(flag::max_flag)];
|
|
|
|
|
return flags[size_t(in_flag)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool &settings::flags(flag in_flag)
|
|
|
|
|
{
|
|
|
|
|
return const_cast<bool &>(static_cast<settings const *>(this)->flags(in_flag));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// executor implementation
|
|
|
|
|
|
|
|
|
|
inline std::string internal::executor::result(int *exit_code /* = nullptr */)
|
|
|
|
|
{
|
|
|
|
|
stop();
|
|
|
|
|
if (exit_code)
|
|
|
|
|
*exit_code = m_exit_code;
|
|
|
|
|
return m_stdout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool internal::executor::kill()
|
|
|
|
|
{
|
|
|
|
|
#if _WIN32
|
|
|
|
|
if (m_future.valid())
|
|
|
|
|
{
|
|
|
|
|
// Close all windows that weren’t open when we started the future
|
|
|
|
|
auto previous_windows = m_windows;
|
|
|
|
|
EnumWindows(&enum_windows_callback, (LPARAM)this);
|
|
|
|
|
for (auto hwnd : m_windows)
|
|
|
|
|
if (previous_windows.find(hwnd) == previous_windows.end())
|
|
|
|
|
SendMessage(hwnd, WM_CLOSE, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
#elif __EMSCRIPTEN__ || __NX__
|
|
|
|
|
// FIXME: do something
|
|
|
|
|
(void)timeout;
|
|
|
|
|
return false; // cannot kill
|
|
|
|
|
#else
|
|
|
|
|
::kill(m_pid, SIGKILL);
|
|
|
|
|
#endif
|
|
|
|
|
stop();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
auto that = (executor *)lParam;
|
|
|
|
|
|
|
|
|
|
DWORD pid;
|
|
|
|
|
auto tid = GetWindowThreadProcessId(hwnd, &pid);
|
|
|
|
|
if (tid == that->m_tid)
|
|
|
|
|
that->m_windows.insert(hwnd);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
inline void internal::executor::start_func(std::function<std::string(int *)> const &fun)
|
|
|
|
|
{
|
|
|
|
|
stop();
|
|
|
|
|
|
|
|
|
|
auto trampoline = [fun, this]()
|
|
|
|
|
{
|
|
|
|
|
// Save our thread id so that the caller can cancel us
|
|
|
|
|
m_tid = GetCurrentThreadId();
|
|
|
|
|
EnumWindows(&enum_windows_callback, (LPARAM)this);
|
|
|
|
|
m_cond.notify_all();
|
|
|
|
|
return fun(&m_exit_code);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
|
m_future = std::async(std::launch::async, trampoline);
|
|
|
|
|
m_cond.wait(lock);
|
|
|
|
|
m_running = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#elif __EMSCRIPTEN__
|
|
|
|
|
inline void internal::executor::start(int exit_code)
|
|
|
|
|
{
|
|
|
|
|
m_exit_code = exit_code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
inline void internal::executor::start_process(std::vector<std::string> const &command)
|
|
|
|
|
{
|
|
|
|
|
stop();
|
|
|
|
|
m_stdout.clear();
|
|
|
|
|
m_exit_code = -1;
|
|
|
|
|
|
|
|
|
|
int in[2], out[2];
|
|
|
|
|
if (pipe(in) != 0 || pipe(out) != 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_pid = fork();
|
|
|
|
|
if (m_pid < 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
close(in[m_pid ? 0 : 1]);
|
|
|
|
|
close(out[m_pid ? 1 : 0]);
|
|
|
|
|
|
|
|
|
|
if (m_pid == 0)
|
|
|
|
|
{
|
|
|
|
|
dup2(in[0], STDIN_FILENO);
|
|
|
|
|
dup2(out[1], STDOUT_FILENO);
|
|
|
|
|
|
|
|
|
|
// Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity)
|
|
|
|
|
int fd = open("/dev/null", O_WRONLY);
|
|
|
|
|
dup2(fd, STDERR_FILENO);
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
|
|
std::vector<char *> args;
|
|
|
|
|
std::transform(command.cbegin(), command.cend(), std::back_inserter(args),
|
|
|
|
|
[](std::string const &s) { return const_cast<char *>(s.c_str()); });
|
|
|
|
|
args.push_back(nullptr); // null-terminate argv[]
|
|
|
|
|
|
|
|
|
|
execvp(args[0], args.data());
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close(in[1]);
|
|
|
|
|
m_fd = out[0];
|
|
|
|
|
auto flags = fcntl(m_fd, F_GETFL);
|
|
|
|
|
fcntl(m_fd, F_SETFL, flags | O_NONBLOCK);
|
|
|
|
|
|
|
|
|
|
m_running = true;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
inline internal::executor::~executor()
|
|
|
|
|
{
|
|
|
|
|
stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool internal::executor::ready(int timeout /* = default_wait_timeout */)
|
|
|
|
|
{
|
|
|
|
|
if (!m_running)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
if (m_future.valid())
|
|
|
|
|
{
|
|
|
|
|
auto status = m_future.wait_for(std::chrono::milliseconds(timeout));
|
|
|
|
|
if (status != std::future_status::ready)
|
|
|
|
|
{
|
|
|
|
|
// On Windows, we need to run the message pump. If the async
|
|
|
|
|
// thread uses a Windows API dialog, it may be attached to the
|
|
|
|
|
// main thread and waiting for messages that only we can dispatch.
|
|
|
|
|
MSG msg;
|
|
|
|
|
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
|
|
|
|
|
{
|
|
|
|
|
TranslateMessage(&msg);
|
|
|
|
|
DispatchMessage(&msg);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_stdout = m_future.get();
|
|
|
|
|
}
|
|
|
|
|
#elif __EMSCRIPTEN__ || __NX__
|
|
|
|
|
// FIXME: do something
|
|
|
|
|
(void)timeout;
|
|
|
|
|
#else
|
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
|
ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore
|
|
|
|
|
if (received > 0)
|
|
|
|
|
{
|
|
|
|
|
m_stdout += std::string(buf, received);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reap child process if it is dead. It is possible that the system has already reaped it
|
|
|
|
|
// (this happens when the calling application handles or ignores SIG_CHLD) and results in
|
|
|
|
|
// waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for
|
|
|
|
|
// a little while.
|
|
|
|
|
int status;
|
|
|
|
|
pid_t child = waitpid(m_pid, &status, WNOHANG);
|
|
|
|
|
if (child != m_pid && (child >= 0 || errno != ECHILD))
|
|
|
|
|
{
|
|
|
|
|
// FIXME: this happens almost always at first iteration
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(timeout));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close(m_fd);
|
|
|
|
|
m_exit_code = WEXITSTATUS(status);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
m_running = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void internal::executor::stop()
|
|
|
|
|
{
|
|
|
|
|
// Loop until the user closes the dialog
|
|
|
|
|
while (!ready())
|
|
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dll implementation
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
inline internal::platform::dll::dll(std::string const &name)
|
|
|
|
|
: handle(::LoadLibraryA(name.c_str()))
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
inline internal::platform::dll::~dll()
|
|
|
|
|
{
|
|
|
|
|
if (handle)
|
|
|
|
|
::FreeLibrary(handle);
|
|
|
|
|
}
|
|
|
|
|
#endif // _WIN32
|
|
|
|
|
|
|
|
|
|
// ole32_dll implementation
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
inline internal::platform::ole32_dll::ole32_dll()
|
|
|
|
|
: dll("ole32.dll")
|
|
|
|
|
{
|
|
|
|
|
// Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes.
|
|
|
|
|
// See https://github.com/samhocevar/portable-file-dialogs/issues/51
|
|
|
|
|
auto coinit = proc<HRESULT WINAPI (LPVOID, DWORD)>(*this, "CoInitializeEx");
|
|
|
|
|
m_state = coinit(nullptr, COINIT_MULTITHREADED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline internal::platform::ole32_dll::~ole32_dll()
|
|
|
|
|
{
|
|
|
|
|
if (is_initialized())
|
|
|
|
|
proc<void WINAPI ()>(*this, "CoUninitialize")();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool internal::platform::ole32_dll::is_initialized()
|
|
|
|
|
{
|
|
|
|
|
return m_state == S_OK || m_state == S_FALSE;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// new_style_context implementation
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
inline internal::platform::new_style_context::new_style_context()
|
|
|
|
|
{
|
|
|
|
|
// Only create one activation context for the whole app lifetime.
|
|
|
|
|
static HANDLE hctx = create();
|
|
|
|
|
|
|
|
|
|
if (hctx != INVALID_HANDLE_VALUE)
|
|
|
|
|
ActivateActCtx(hctx, &m_cookie);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline internal::platform::new_style_context::~new_style_context()
|
|
|
|
|
{
|
|
|
|
|
DeactivateActCtx(0, m_cookie);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline HANDLE internal::platform::new_style_context::create()
|
|
|
|
|
{
|
|
|
|
|
// This “hack” seems to be necessary for this code to work on windows XP.
|
|
|
|
|
// Without it, dialogs do not show and close immediately. GetError()
|
|
|
|
|
// returns 0 so I don’t know what causes this. I was not able to reproduce
|
|
|
|
|
// this behavior on Windows 7 and 10 but just in case, let it be here for
|
|
|
|
|
// those versions too.
|
|
|
|
|
// This hack is not required if other dialogs are used (they load comdlg32
|
|
|
|
|
// automatically), only if message boxes are used.
|
|
|
|
|
dll comdlg32("comdlg32.dll");
|
|
|
|
|
|
|
|
|
|
// Using approach as shown here: https://stackoverflow.com/a/10444161
|
|
|
|
|
UINT len = ::GetSystemDirectoryA(nullptr, 0);
|
|
|
|
|
std::string sys_dir(len, '\0');
|
|
|
|
|
::GetSystemDirectoryA(&sys_dir[0], len);
|
|
|
|
|
|
|
|
|
|
ACTCTXA act_ctx =
|
|
|
|
|
{
|
|
|
|
|
// Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a
|
|
|
|
|
// crash with error “default context is already set”.
|
|
|
|
|
sizeof(act_ctx),
|
|
|
|
|
ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
|
|
|
|
|
"shell32.dll", 0, 0, sys_dir.c_str(), (LPCSTR)124, NULL, NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return ::CreateActCtxA(&act_ctx);
|
|
|
|
|
}
|
|
|
|
|
#endif // _WIN32
|
|
|
|
|
|
|
|
|
|
// dialog implementation
|
|
|
|
|
|
|
|
|
|
inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const
|
|
|
|
|
{
|
|
|
|
|
return m_async->ready(timeout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool internal::dialog::kill() const
|
|
|
|
|
{
|
|
|
|
|
return m_async->kill();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline internal::dialog::dialog()
|
|
|
|
|
: m_async(std::make_shared<executor>())
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::vector<std::string> internal::dialog::desktop_helper() const
|
|
|
|
|
{
|
|
|
|
|
#if __APPLE__
|
|
|
|
|
return { "osascript" };
|
|
|
|
|
#else
|
|
|
|
|
return { flags(flag::has_zenity) ? "zenity"
|
|
|
|
|
: flags(flag::has_matedialog) ? "matedialog"
|
|
|
|
|
: flags(flag::has_qarma) ? "qarma"
|
|
|
|
|
: flags(flag::has_kdialog) ? "kdialog"
|
|
|
|
|
: "echo" };
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::string internal::dialog::buttons_to_name(choice _choice)
|
|
|
|
|
{
|
|
|
|
|
switch (_choice)
|
|
|
|
|
{
|
|
|
|
|
case choice::ok_cancel: return "okcancel";
|
|
|
|
|
case choice::yes_no: return "yesno";
|
|
|
|
|
case choice::yes_no_cancel: return "yesnocancel";
|
|
|
|
|
case choice::retry_cancel: return "retrycancel";
|
|
|
|
|
case choice::abort_retry_ignore: return "abortretryignore";
|
|
|
|
|
/* case choice::ok: */ default: return "ok";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::string internal::dialog::get_icon_name(icon _icon)
|
|
|
|
|
{
|
|
|
|
|
switch (_icon)
|
|
|
|
|
{
|
|
|
|
|
case icon::warning: return "warning";
|
|
|
|
|
case icon::error: return "error";
|
|
|
|
|
case icon::question: return "question";
|
|
|
|
|
// Zenity wants "information" but WinForms wants "info"
|
|
|
|
|
/* case icon::info: */ default:
|
|
|
|
|
#if _WIN32
|
|
|
|
|
return "info";
|
|
|
|
|
#else
|
|
|
|
|
return "information";
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// THis is only used for debugging purposes
|
|
|
|
|
inline std::ostream& operator <<(std::ostream &s, std::vector<std::string> const &v)
|
|
|
|
|
{
|
|
|
|
|
int not_first = 0;
|
|
|
|
|
for (auto &e : v)
|
|
|
|
|
s << (not_first++ ? " " : "") << e;
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Properly quote a string for Powershell: replace ' or " with '' or ""
|
|
|
|
|
// FIXME: we should probably get rid of newlines!
|
|
|
|
|
// FIXME: the \" sequence seems unsafe, too!
|
|
|
|
|
// XXX: this is no longer used but I would like to keep it around just in case
|
|
|
|
|
inline std::string internal::dialog::powershell_quote(std::string const &str) const
|
|
|
|
|
{
|
|
|
|
|
return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Properly quote a string for osascript: replace \ or " with \\ or \"
|
|
|
|
|
// XXX: this also used to replace ' with \' when popen was used, but it would be
|
|
|
|
|
// smarter to do shell_quote(osascript_quote(...)) if this is needed again.
|
|
|
|
|
inline std::string internal::dialog::osascript_quote(std::string const &str) const
|
|
|
|
|
{
|
|
|
|
|
return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\"";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Properly quote a string for the shell: just replace ' with '\''
|
|
|
|
|
// XXX: this is no longer used but I would like to keep it around just in case
|
|
|
|
|
inline std::string internal::dialog::shell_quote(std::string const &str) const
|
|
|
|
|
{
|
|
|
|
|
return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// file_dialog implementation
|
|
|
|
|
|
|
|
|
|
inline internal::file_dialog::file_dialog(type in_type,
|
|
|
|
|
std::string const &title,
|
|
|
|
|
std::string const &default_path /* = "" */,
|
|
|
|
|
std::vector<std::string> const &filters /* = {} */,
|
|
|
|
|
opt options /* = opt::none */)
|
|
|
|
|
{
|
|
|
|
|
#if _WIN32
|
|
|
|
|
std::string filter_list;
|
|
|
|
|
std::regex whitespace(" *");
|
|
|
|
|
for (size_t i = 0; i + 1 < filters.size(); i += 2)
|
|
|
|
|
{
|
|
|
|
|
filter_list += filters[i] + '\0';
|
|
|
|
|
filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0';
|
|
|
|
|
}
|
|
|
|
|
filter_list += '\0';
|
|
|
|
|
|
|
|
|
|
m_async->start_func([this, in_type, title, default_path, filter_list,
|
|
|
|
|
options](int *exit_code) -> std::string
|
|
|
|
|
{
|
|
|
|
|
(void)exit_code;
|
|
|
|
|
m_wtitle = internal::str2wstr(title);
|
|
|
|
|
m_wdefault_path = internal::str2wstr(default_path);
|
|
|
|
|
auto wfilter_list = internal::str2wstr(filter_list);
|
|
|
|
|
|
|
|
|
|
// Initialise COM. This is required for the new folder selection window,
|
|
|
|
|
// (see https://github.com/samhocevar/portable-file-dialogs/pull/21)
|
|
|
|
|
// and to avoid random crashes with GetOpenFileNameW() (see
|
|
|
|
|
// https://github.com/samhocevar/portable-file-dialogs/issues/51)
|
|
|
|
|
ole32_dll ole32;
|
|
|
|
|
|
|
|
|
|
// Folder selection uses a different method
|
|
|
|
|
if (in_type == type::folder)
|
|
|
|
|
{
|
|
|
|
|
#if PFD_HAS_IFILEDIALOG
|
|
|
|
|
if (flags(flag::is_vista))
|
|
|
|
|
{
|
|
|
|
|
// On Vista and higher we should be able to use IFileDialog for folder selection
|
|
|
|
|
IFileDialog *ifd;
|
|
|
|
|
HRESULT hr = dll::proc<HRESULT WINAPI (REFCLSID, LPUNKNOWN, DWORD, REFIID, LPVOID *)>(ole32, "CoCreateInstance")
|
|
|
|
|
(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd));
|
|
|
|
|
|
|
|
|
|
// In case CoCreateInstance fails (which it should not), try legacy approach
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
return select_folder_vista(ifd, options & opt::force_path);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
BROWSEINFOW bi;
|
|
|
|
|
memset(&bi, 0, sizeof(bi));
|
|
|
|
|
|
|
|
|
|
bi.lpfn = &bffcallback;
|
|
|
|
|
bi.lParam = (LPARAM)this;
|
|
|
|
|
|
|
|
|
|
if (flags(flag::is_vista))
|
|
|
|
|
{
|
|
|
|
|
if (ole32.is_initialized())
|
|
|
|
|
bi.ulFlags |= BIF_NEWDIALOGSTYLE;
|
|
|
|
|
bi.ulFlags |= BIF_EDITBOX;
|
|
|
|
|
bi.ulFlags |= BIF_STATUSTEXT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto *list = SHBrowseForFolderW(&bi);
|
|
|
|
|
std::string ret;
|
|
|
|
|
if (list)
|
|
|
|
|
{
|
|
|
|
|
auto buffer = new wchar_t[MAX_PATH];
|
|
|
|
|
SHGetPathFromIDListW(list, buffer);
|
|
|
|
|
dll::proc<void WINAPI (LPVOID)>(ole32, "CoTaskMemFree")(list);
|
|
|
|
|
ret = internal::wstr2str(buffer);
|
|
|
|
|
delete[] buffer;
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OPENFILENAMEW ofn;
|
|
|
|
|
memset(&ofn, 0, sizeof(ofn));
|
|
|
|
|
ofn.lStructSize = sizeof(OPENFILENAMEW);
|
|
|
|
|
ofn.hwndOwner = GetActiveWindow();
|
|
|
|
|
|
|
|
|
|
ofn.lpstrFilter = wfilter_list.c_str();
|
|
|
|
|
|
|
|
|
|
auto woutput = std::wstring(MAX_PATH * 256, L'\0');
|
|
|
|
|
ofn.lpstrFile = (LPWSTR)woutput.data();
|
|
|
|
|
ofn.nMaxFile = (DWORD)woutput.size();
|
|
|
|
|
if (!m_wdefault_path.empty())
|
|
|
|
|
{
|
|
|
|
|
// If a directory was provided, use it as the initial directory. If
|
|
|
|
|
// a valid path was provided, use it as the initial file. Otherwise,
|
|
|
|
|
// let the Windows API decide.
|
|
|
|
|
auto path_attr = GetFileAttributesW(m_wdefault_path.c_str());
|
|
|
|
|
if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY))
|
|
|
|
|
ofn.lpstrInitialDir = m_wdefault_path.c_str();
|
|
|
|
|
else if (m_wdefault_path.size() <= woutput.size())
|
|
|
|
|
//second argument is size of buffer, not length of string
|
|
|
|
|
StringCchCopyW(ofn.lpstrFile, MAX_PATH*256+1, m_wdefault_path.c_str());
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data();
|
|
|
|
|
ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ofn.lpstrTitle = m_wtitle.c_str();
|
|
|
|
|
ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER;
|
|
|
|
|
|
|
|
|
|
dll comdlg32("comdlg32.dll");
|
|
|
|
|
|
|
|
|
|
// Apply new visual style (required for windows XP)
|
|
|
|
|
new_style_context ctx;
|
|
|
|
|
|
|
|
|
|
if (in_type == type::save)
|
|
|
|
|
{
|
|
|
|
|
if (!(options & opt::force_overwrite))
|
|
|
|
|
ofn.Flags |= OFN_OVERWRITEPROMPT;
|
|
|
|
|
|
|
|
|
|
dll::proc<BOOL WINAPI (LPOPENFILENAMEW)> get_save_file_name(comdlg32, "GetSaveFileNameW");
|
|
|
|
|
if (get_save_file_name(&ofn) == 0)
|
|
|
|
|
return "";
|
|
|
|
|
return internal::wstr2str(woutput.c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (options & opt::multiselect)
|
|
|
|
|
ofn.Flags |= OFN_ALLOWMULTISELECT;
|
|
|
|
|
ofn.Flags |= OFN_PATHMUSTEXIST;
|
|
|
|
|
|
|
|
|
|
dll::proc<BOOL WINAPI (LPOPENFILENAMEW)> get_open_file_name(comdlg32, "GetOpenFileNameW");
|
|
|
|
|
if (get_open_file_name(&ofn) == 0)
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string prefix;
|
|
|
|
|
for (wchar_t const *p = woutput.c_str(); *p; )
|
|
|
|
|
{
|
|
|
|
|
auto filename = internal::wstr2str(p);
|
|
|
|
|
p += wcslen(p);
|
|
|
|
|
// In multiselect mode, we advance p one wchar further and
|
|
|
|
|
// check for another filename. If there is one and the
|
|
|
|
|
// prefix is empty, it means we just read the prefix.
|
|
|
|
|
if ((options & opt::multiselect) && *++p && prefix.empty())
|
|
|
|
|
{
|
|
|
|
|
prefix = filename + "/";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_vector_result.push_back(prefix + filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
});
|
|
|
|
|
#else
|
|
|
|
|
auto command = desktop_helper();
|
|
|
|
|
|
|
|
|
|
if (is_osascript())
|
|
|
|
|
{
|
|
|
|
|
std::string script = "set ret to choose";
|
|
|
|
|
switch (in_type)
|
|
|
|
|
{
|
|
|
|
|
case type::save:
|
|
|
|
|
script += " file name";
|
|
|
|
|
break;
|
|
|
|
|
case type::open: default:
|
|
|
|
|
script += " file";
|
|
|
|
|
if (options & opt::multiselect)
|
|
|
|
|
script += " with multiple selections allowed";
|
|
|
|
|
break;
|
|
|
|
|
case type::folder:
|
|
|
|
|
script += " folder";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (default_path.size())
|
|
|
|
|
script += " default location " + osascript_quote(default_path);
|
|
|
|
|
script += " with prompt " + osascript_quote(title);
|
|
|
|
|
|
|
|
|
|
if (in_type == type::open)
|
|
|
|
|
{
|
|
|
|
|
// Concatenate all user-provided filter patterns
|
|
|
|
|
std::string patterns;
|
|
|
|
|
for (size_t i = 0; i < filters.size() / 2; ++i)
|
|
|
|
|
patterns += " " + filters[2 * i + 1];
|
|
|
|
|
|
|
|
|
|
// Split the pattern list to check whether "*" is in there; if it
|
|
|
|
|
// is, we have to disable filters because there is no mechanism in
|
|
|
|
|
// OS X for the user to override the filter.
|
|
|
|
|
std::regex sep("\\s+");
|
|
|
|
|
std::string filter_list;
|
|
|
|
|
bool has_filter = true;
|
|
|
|
|
std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1);
|
|
|
|
|
std::sregex_token_iterator end;
|
|
|
|
|
for ( ; iter != end; ++iter)
|
|
|
|
|
{
|
|
|
|
|
auto pat = iter->str();
|
|
|
|
|
if (pat == "*" || pat == "*.*")
|
|
|
|
|
has_filter = false;
|
|
|
|
|
else if (internal::starts_with(pat, "*."))
|
|
|
|
|
filter_list += (filter_list.size() == 0 ? "" : ",") +
|
|
|
|
|
osascript_quote(pat.substr(2, pat.size() - 2));
|
|
|
|
|
}
|
|
|
|
|
if (has_filter && filter_list.size() > 0)
|
|
|
|
|
script += " of type {" + filter_list + "}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (in_type == type::open && (options & opt::multiselect))
|
|
|
|
|
{
|
|
|
|
|
script += "\nset s to \"\"";
|
|
|
|
|
script += "\nrepeat with i in ret";
|
|
|
|
|
script += "\n set s to s & (POSIX path of i) & \"\\n\"";
|
|
|
|
|
script += "\nend repeat";
|
|
|
|
|
script += "\ncopy s to stdout";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
script += "\nPOSIX path of ret";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command.push_back("-e");
|
|
|
|
|
command.push_back(script);
|
|
|
|
|
}
|
|
|
|
|
else if (is_zenity())
|
|
|
|
|
{
|
|
|
|
|
command.push_back("--file-selection");
|
|
|
|
|
command.push_back("--filename=" + default_path);
|
|
|
|
|
command.push_back("--title");
|
|
|
|
|
command.push_back(title);
|
|
|
|
|
command.push_back("--separator=\n");
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < filters.size() / 2; ++i)
|
|
|
|
|
{
|
|
|
|
|
command.push_back("--file-filter");
|
|
|
|
|
command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (in_type == type::save)
|
|
|
|
|
command.push_back("--save");
|
|
|
|
|
if (in_type == type::folder)
|
|
|
|
|
command.push_back("--directory");
|
|
|
|
|
if (!(options & opt::force_overwrite))
|
|
|
|
|
command.push_back("--confirm-overwrite");
|
|
|
|
|
if (options & opt::multiselect)
|
|
|
|
|
command.push_back("--multiple");
|
|
|
|
|
}
|
|
|
|
|
else if (is_kdialog())
|
|
|
|
|
{
|
|
|
|
|
switch (in_type)
|
|
|
|
|
{
|
|
|
|
|
case type::save: command.push_back("--getsavefilename"); break;
|
|
|
|
|
case type::open: command.push_back("--getopenfilename"); break;
|
|
|
|
|
case type::folder: command.push_back("--getexistingdirectory"); break;
|
|
|
|
|
}
|
|
|
|
|
if (options & opt::multiselect)
|
|
|
|
|
{
|
|
|
|
|
command.push_back("--multiple");
|
|
|
|
|
command.push_back("--separate-output");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command.push_back(default_path);
|
|
|
|
|
|
|
|
|
|
std::string filter;
|
|
|
|
|
for (size_t i = 0; i < filters.size() / 2; ++i)
|
|
|
|
|
filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")";
|
|
|
|
|
command.push_back(filter);
|
|
|
|
|
|
|
|
|
|
command.push_back("--title");
|
|
|
|
|
command.push_back(title);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags(flag::is_verbose))
|
|
|
|
|
std::cerr << "pfd: " << command << std::endl;
|
|
|
|
|
|
|
|
|
|
m_async->start_process(command);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::string internal::file_dialog::string_result()
|
|
|
|
|
{
|
|
|
|
|
#if _WIN32
|
|
|
|
|
return m_async->result();
|
|
|
|
|
#else
|
|
|
|
|
auto ret = m_async->result();
|
|
|
|
|
// Strip potential trailing newline (zenity). Also strip trailing slash
|
|
|
|
|
// added by osascript for consistency with other backends.
|
|
|
|
|
while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/'))
|
|
|
|
|
ret.pop_back();
|
|
|
|
|
return ret;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::vector<std::string> internal::file_dialog::vector_result()
|
|
|
|
|
{
|
|
|
|
|
#if _WIN32
|
|
|
|
|
m_async->result();
|
|
|
|
|
return m_vector_result;
|
|
|
|
|
#else
|
|
|
|
|
std::vector<std::string> ret;
|
|
|
|
|
auto result = m_async->result();
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
// Split result along newline characters
|
|
|
|
|
auto i = result.find('\n');
|
|
|
|
|
if (i == 0 || i == std::string::npos)
|
|
|
|
|
break;
|
|
|
|
|
ret.push_back(result.substr(0, i));
|
|
|
|
|
result = result.substr(i + 1, result.size());
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
// Use a static function to pass as BFFCALLBACK for legacy folder select
|
|
|
|
|
inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg,
|
|
|
|
|
LPARAM, LPARAM pData)
|
|
|
|
|
{
|
|
|
|
|
auto inst = (file_dialog *)pData;
|
|
|
|
|
switch (uMsg)
|
|
|
|
|
{
|
|
|
|
|
case BFFM_INITIALIZED:
|
|
|
|
|
SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if PFD_HAS_IFILEDIALOG
|
|
|
|
|
inline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, bool force_path)
|
|
|
|
|
{
|
|
|
|
|
std::string result;
|
|
|
|
|
|
|
|
|
|
IShellItem *folder;
|
|
|
|
|
|
|
|
|
|
// Load library at runtime so app doesn't link it at load time (which will fail on windows XP)
|
|
|
|
|
dll shell32("shell32.dll");
|
|
|
|
|
dll::proc<HRESULT WINAPI (PCWSTR, IBindCtx*, REFIID, void**)>
|
|
|
|
|
create_item(shell32, "SHCreateItemFromParsingName");
|
|
|
|
|
|
|
|
|
|
if (!create_item)
|
|
|
|
|
return "";
|
|
|
|
|
|
|
|
|
|
auto hr = create_item(m_wdefault_path.c_str(),
|
|
|
|
|
nullptr,
|
|
|
|
|
IID_PPV_ARGS(&folder));
|
|
|
|
|
|
|
|
|
|
// Set default folder if found. This only sets the default folder. If
|
|
|
|
|
// Windows has any info about the most recently selected folder, it
|
|
|
|
|
// will display it instead. Generally, calling SetFolder() to set the
|
|
|
|
|
// current directory “is not a good or expected user experience and
|
|
|
|
|
// should therefore be avoided”:
|
|
|
|
|
// https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
if (force_path)
|
|
|
|
|
ifd->SetFolder(folder);
|
|
|
|
|
else
|
|
|
|
|
ifd->SetDefaultFolder(folder);
|
|
|
|
|
folder->Release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set the dialog title and option to select folders
|
|
|
|
|
ifd->SetOptions(FOS_PICKFOLDERS);
|
|
|
|
|
ifd->SetTitle(m_wtitle.c_str());
|
|
|
|
|
|
|
|
|
|
hr = ifd->Show(GetActiveWindow());
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
IShellItem* item;
|
|
|
|
|
hr = ifd->GetResult(&item);
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
|
{
|
|
|
|
|
wchar_t* wselected = nullptr;
|
|
|
|
|
item->GetDisplayName(SIGDN_FILESYSPATH, &wselected);
|
|
|
|
|
item->Release();
|
|
|
|
|
|
|
|
|
|
if (wselected)
|
|
|
|
|
{
|
|
|
|
|
result = internal::wstr2str(std::wstring(wselected));
|
|
|
|
|
dll::proc<void WINAPI (LPVOID)>(ole32_dll(), "CoTaskMemFree")(wselected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ifd->Release();
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// notify implementation
|
|
|
|
|
|
2022-04-09 10:48:19 +00:00
|
|
|
|
#if _WIN32
|
|
|
|
|
inline BOOL WINAPI icon_enum_callback(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam)
|
|
|
|
|
{
|
|
|
|
|
((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName);
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-03-14 03:02:50 +00:00
|
|
|
|
inline notify::notify(std::string const &title,
|
|
|
|
|
std::string const &message,
|
|
|
|
|
icon _icon /* = icon::info */)
|
|
|
|
|
{
|
|
|
|
|
if (_icon == icon::question) // Not supported by notifications
|
|
|
|
|
_icon = icon::info;
|
|
|
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
|
// Use a static shared pointer for notify_icon so that we can delete
|
|
|
|
|
// it whenever we need to display a new one, and we can also wait
|
|
|
|
|
// until the program has finished running.
|
|
|
|
|
struct notify_icon_data : public NOTIFYICONDATAW
|
|
|
|
|
{
|
|
|
|
|
~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static std::shared_ptr<notify_icon_data> nid;
|
|
|
|
|
|
|
|
|
|
// Release the previous notification icon, if any, and allocate a new
|
|
|
|
|
// one. Note that std::make_shared() does value initialization, so there
|
|
|
|
|
// is no need to memset the structure.
|
|
|
|
|
nid = nullptr;
|
|
|
|
|
nid = std::make_shared<notify_icon_data>();
|
|
|
|
|
|
|
|
|
|
// For XP support
|
|
|
|
|
nid->cbSize = NOTIFYICONDATAW_V2_SIZE;
|
|
|
|
|
nid->hWnd = nullptr;
|
|
|
|
|
nid->uID = 0;
|
|
|
|
|
|
|
|
|
|
// Flag Description:
|
|
|
|
|
// - NIF_ICON The hIcon member is valid.
|
|
|
|
|
// - NIF_MESSAGE The uCallbackMessage member is valid.
|
|
|
|
|
// - NIF_TIP The szTip member is valid.
|
|
|
|
|
// - NIF_STATE The dwState and dwStateMask members are valid.
|
|
|
|
|
// - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and dwInfoFlags members are valid.
|
|
|
|
|
// - NIF_GUID Reserved.
|
|
|
|
|
nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO;
|
|
|
|
|
|
|
|
|
|
// Flag Description
|
|
|
|
|
// - NIIF_ERROR An error icon.
|
|
|
|
|
// - NIIF_INFO An information icon.
|
|
|
|
|
// - NIIF_NONE No icon.
|
|
|
|
|
// - NIIF_WARNING A warning icon.
|
|
|
|
|
// - NIIF_ICON_MASK Version 6.0. Reserved.
|
|
|
|
|
// - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips
|
|
|
|
|
switch (_icon)
|
|
|
|
|
{
|
|
|
|
|
case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break;
|
|
|
|
|
case icon::error: nid->dwInfoFlags = NIIF_ERROR; break;
|
|
|
|
|
/* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
|
2022-04-09 10:48:19 +00:00
|
|
|
|
::EnumResourceNames(nullptr, RT_GROUP_ICON, (ENUMRESNAMEPROC)icon_enum_callback, (LONG_PTR)nid.get());
|
2022-03-14 03:02:50 +00:00
|
|
|
|
|
|
|
|
|
nid->uTimeout = 5000;
|
|
|
|
|
|
|
|
|
|
StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str());
|
|
|
|
|
StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str());
|
|
|
|
|
|
|
|
|
|
// Display the new icon
|
|
|
|
|
Shell_NotifyIconW(NIM_ADD, nid.get());
|
|
|
|
|
#else
|
|
|
|
|
auto command = desktop_helper();
|
|
|
|
|
|
|
|
|
|
if (is_osascript())
|
|
|
|
|
{
|
|
|
|
|
command.push_back("-e");
|
|
|
|
|
command.push_back("display notification " + osascript_quote(message) +
|
|
|
|
|
" with title " + osascript_quote(title));
|
|
|
|
|
}
|
|
|
|
|
else if (is_zenity())
|
|
|
|
|
{
|
|
|
|
|
command.push_back("--notification");
|
|
|
|
|
command.push_back("--window-icon");
|
|
|
|
|
command.push_back(get_icon_name(_icon));
|
|
|
|
|
command.push_back("--text");
|
|
|
|
|
command.push_back(title + "\n" + message);
|
|
|
|
|
}
|
|
|
|
|
else if (is_kdialog())
|
|
|
|
|
{
|
|
|
|
|
command.push_back("--icon");
|
|
|
|
|
command.push_back(get_icon_name(_icon));
|
|
|
|
|
command.push_back("--title");
|
|
|
|
|
command.push_back(title);
|
|
|
|
|
command.push_back("--passivepopup");
|
|
|
|
|
command.push_back(message);
|
|
|
|
|
command.push_back("5");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags(flag::is_verbose))
|
|
|
|
|
std::cerr << "pfd: " << command << std::endl;
|
|
|
|
|
|
|
|
|
|
m_async->start_process(command);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// message implementation
|
|
|
|
|
|
|
|
|
|
inline message::message(std::string const &title,
|
|
|
|
|
std::string const &text,
|
|
|
|
|
choice _choice /* = choice::ok_cancel */,
|
|
|
|
|
icon _icon /* = icon::info */)
|
|
|
|
|
{
|
|
|
|
|
#if _WIN32
|
|
|
|
|
// Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought
|
|
|
|
|
// to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52
|
|
|
|
|
UINT style = MB_SYSTEMMODAL;
|
|
|
|
|
switch (_icon)
|
|
|
|
|
{
|
|
|
|
|
case icon::warning: style |= MB_ICONWARNING; break;
|
|
|
|
|
case icon::error: style |= MB_ICONERROR; break;
|
|
|
|
|
case icon::question: style |= MB_ICONQUESTION; break;
|
|
|
|
|
/* case icon::info: */ default: style |= MB_ICONINFORMATION; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (_choice)
|
|
|
|
|
{
|
|
|
|
|
case choice::ok_cancel: style |= MB_OKCANCEL; break;
|
|
|
|
|
case choice::yes_no: style |= MB_YESNO; break;
|
|
|
|
|
case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break;
|
|
|
|
|
case choice::retry_cancel: style |= MB_RETRYCANCEL; break;
|
|
|
|
|
case choice::abort_retry_ignore: style |= MB_ABORTRETRYIGNORE; break;
|
|
|
|
|
/* case choice::ok: */ default: style |= MB_OK; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_mappings[IDCANCEL] = button::cancel;
|
|
|
|
|
m_mappings[IDOK] = button::ok;
|
|
|
|
|
m_mappings[IDYES] = button::yes;
|
|
|
|
|
m_mappings[IDNO] = button::no;
|
|
|
|
|
m_mappings[IDABORT] = button::abort;
|
|
|
|
|
m_mappings[IDRETRY] = button::retry;
|
|
|
|
|
m_mappings[IDIGNORE] = button::ignore;
|
|
|
|
|
|
|
|
|
|
m_async->start_func([text, title, style](int* exit_code) -> std::string
|
|
|
|
|
{
|
|
|
|
|
auto wtext = internal::str2wstr(text);
|
|
|
|
|
auto wtitle = internal::str2wstr(title);
|
|
|
|
|
// Apply new visual style (required for all Windows versions)
|
|
|
|
|
new_style_context ctx;
|
|
|
|
|
*exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style);
|
|
|
|
|
return "";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
#elif __EMSCRIPTEN__
|
|
|
|
|
std::string full_message;
|
|
|
|
|
switch (_icon)
|
|
|
|
|
{
|
|
|
|
|
case icon::warning: full_message = "⚠️"; break;
|
|
|
|
|
case icon::error: full_message = "⛔"; break;
|
|
|
|
|
case icon::question: full_message = "❓"; break;
|
|
|
|
|
/* case icon::info: */ default: full_message = "ℹ"; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
full_message += ' ' + title + "\n\n" + text;
|
|
|
|
|
|
|
|
|
|
// This does not really start an async task; it just passes the
|
|
|
|
|
// EM_ASM_INT return value to a fake start() function.
|
|
|
|
|
m_async->start(EM_ASM_INT(
|
|
|
|
|
{
|
|
|
|
|
if ($1)
|
|
|
|
|
return window.confirm(UTF8ToString($0)) ? 0 : -1;
|
|
|
|
|
alert(UTF8ToString($0));
|
|
|
|
|
return 0;
|
|
|
|
|
}, full_message.c_str(), _choice == choice::ok_cancel));
|
|
|
|
|
#else
|
|
|
|
|
auto command = desktop_helper();
|
|
|
|
|
|
|
|
|
|
if (is_osascript())
|
|
|
|
|
{
|
|
|
|
|
std::string script = "display dialog " + osascript_quote(text) +
|
|
|
|
|
" with title " + osascript_quote(title);
|
|
|
|
|
switch (_choice)
|
|
|
|
|
{
|
|
|
|
|
case choice::ok_cancel:
|
|
|
|
|
script += "buttons {\"OK\", \"Cancel\"}"
|
|
|
|
|
" default button \"OK\""
|
|
|
|
|
" cancel button \"Cancel\"";
|
|
|
|
|
m_mappings[256] = button::cancel;
|
|
|
|
|
break;
|
|
|
|
|
case choice::yes_no:
|
|
|
|
|
script += "buttons {\"Yes\", \"No\"}"
|
|
|
|
|
" default button \"Yes\""
|
|
|
|
|
" cancel button \"No\"";
|
|
|
|
|
m_mappings[256] = button::no;
|
|
|
|
|
break;
|
|
|
|
|
case choice::yes_no_cancel:
|
|
|
|
|
script += "buttons {\"Yes\", \"No\", \"Cancel\"}"
|
|
|
|
|
" default button \"Yes\""
|
|
|
|
|
" cancel button \"Cancel\"";
|
|
|
|
|
m_mappings[256] = button::cancel;
|
|
|
|
|
break;
|
|
|
|
|
case choice::retry_cancel:
|
|
|
|
|
script += "buttons {\"Retry\", \"Cancel\"}"
|
|
|
|
|
" default button \"Retry\""
|
|
|
|
|
" cancel button \"Cancel\"";
|
|
|
|
|
m_mappings[256] = button::cancel;
|
|
|
|
|
break;
|
|
|
|
|
case choice::abort_retry_ignore:
|
|
|
|
|
script += "buttons {\"Abort\", \"Retry\", \"Ignore\"}"
|
|
|
|
|
" default button \"Retry\""
|
|
|
|
|
" cancel button \"Retry\"";
|
|
|
|
|
m_mappings[256] = button::cancel;
|
|
|
|
|
break;
|
|
|
|
|
case choice::ok: default:
|
|
|
|
|
script += "buttons {\"OK\"}"
|
|
|
|
|
" default button \"OK\""
|
|
|
|
|
" cancel button \"OK\"";
|
|
|
|
|
m_mappings[256] = button::ok;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
script += " with icon ";
|
|
|
|
|
switch (_icon)
|
|
|
|
|
{
|
|
|
|
|
#define PFD_OSX_ICON(n) "alias ((path to library folder from system domain) as text " \
|
|
|
|
|
"& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")"
|
|
|
|
|
case icon::info: default: script += PFD_OSX_ICON("ToolBarInfo"); break;
|
|
|
|
|
case icon::warning: script += "caution"; break;
|
|
|
|
|
case icon::error: script += "stop"; break;
|
|
|
|
|
case icon::question: script += PFD_OSX_ICON("GenericQuestionMarkIcon"); break;
|
|
|
|
|
#undef PFD_OSX_ICON
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command.push_back("-e");
|
|
|
|
|
command.push_back(script);
|
|
|
|
|
}
|
|
|
|
|
else if (is_zenity())
|
|
|
|
|
{
|
|
|
|
|
switch (_choice)
|
|
|
|
|
{
|
|
|
|
|
case choice::ok_cancel:
|
|
|
|
|
command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" }); break;
|
|
|
|
|
case choice::yes_no:
|
|
|
|
|
// Do not use standard --question because it causes “No” to return -1,
|
|
|
|
|
// which is inconsistent with the “Yes/No/Cancel” mode below.
|
|
|
|
|
command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" }); break;
|
|
|
|
|
case choice::yes_no_cancel:
|
|
|
|
|
command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No", "--extra-button=Yes" }); break;
|
|
|
|
|
case choice::retry_cancel:
|
|
|
|
|
command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" }); break;
|
|
|
|
|
case choice::abort_retry_ignore:
|
|
|
|
|
command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore", "--extra-button=Abort", "--extra-button=Retry" }); break;
|
|
|
|
|
case choice::ok:
|
|
|
|
|
default:
|
|
|
|
|
switch (_icon)
|
|
|
|
|
{
|
|
|
|
|
case icon::error: command.push_back("--error"); break;
|
|
|
|
|
case icon::warning: command.push_back("--warning"); break;
|
|
|
|
|
default: command.push_back("--info"); break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command.insert(command.end(), { "--title", title,
|
|
|
|
|
"--width=300", "--height=0", // sensible defaults
|
|
|
|
|
"--text", text,
|
|
|
|
|
"--icon-name=dialog-" + get_icon_name(_icon) });
|
|
|
|
|
}
|
|
|
|
|
else if (is_kdialog())
|
|
|
|
|
{
|
|
|
|
|
if (_choice == choice::ok)
|
|
|
|
|
{
|
|
|
|
|
switch (_icon)
|
|
|
|
|
{
|
|
|
|
|
case icon::error: command.push_back("--error"); break;
|
|
|
|
|
case icon::warning: command.push_back("--sorry"); break;
|
|
|
|
|
default: command.push_back("--msgbox"); break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
std::string flag = "--";
|
|
|
|
|
if (_icon == icon::warning || _icon == icon::error)
|
|
|
|
|
flag += "warning";
|
|
|
|
|
flag += "yesno";
|
|
|
|
|
if (_choice == choice::yes_no_cancel)
|
|
|
|
|
flag += "cancel";
|
|
|
|
|
command.push_back(flag);
|
|
|
|
|
if (_choice == choice::yes_no || _choice == choice::yes_no_cancel)
|
|
|
|
|
{
|
|
|
|
|
m_mappings[0] = button::yes;
|
|
|
|
|
m_mappings[256] = button::no;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command.push_back(text);
|
|
|
|
|
command.push_back("--title");
|
|
|
|
|
command.push_back(title);
|
|
|
|
|
|
|
|
|
|
// Must be after the above part
|
|
|
|
|
if (_choice == choice::ok_cancel)
|
|
|
|
|
command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags(flag::is_verbose))
|
|
|
|
|
std::cerr << "pfd: " << command << std::endl;
|
|
|
|
|
|
|
|
|
|
m_async->start_process(command);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline button message::result()
|
|
|
|
|
{
|
|
|
|
|
int exit_code;
|
|
|
|
|
auto ret = m_async->result(&exit_code);
|
|
|
|
|
// osascript will say "button returned:Cancel\n"
|
|
|
|
|
// and others will just say "Cancel\n"
|
|
|
|
|
if (exit_code < 0 || // this means cancel
|
|
|
|
|
internal::ends_with(ret, "Cancel\n"))
|
|
|
|
|
return button::cancel;
|
|
|
|
|
if (internal::ends_with(ret, "OK\n"))
|
|
|
|
|
return button::ok;
|
|
|
|
|
if (internal::ends_with(ret, "Yes\n"))
|
|
|
|
|
return button::yes;
|
|
|
|
|
if (internal::ends_with(ret, "No\n"))
|
|
|
|
|
return button::no;
|
|
|
|
|
if (internal::ends_with(ret, "Abort\n"))
|
|
|
|
|
return button::abort;
|
|
|
|
|
if (internal::ends_with(ret, "Retry\n"))
|
|
|
|
|
return button::retry;
|
|
|
|
|
if (internal::ends_with(ret, "Ignore\n"))
|
|
|
|
|
return button::ignore;
|
|
|
|
|
if (m_mappings.count(exit_code) != 0)
|
|
|
|
|
return m_mappings[exit_code];
|
|
|
|
|
return exit_code == 0 ? button::ok : button::cancel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// open_file implementation
|
|
|
|
|
|
|
|
|
|
inline open_file::open_file(std::string const &title,
|
|
|
|
|
std::string const &default_path /* = "" */,
|
|
|
|
|
std::vector<std::string> const &filters /* = { "All Files", "*" } */,
|
|
|
|
|
opt options /* = opt::none */)
|
|
|
|
|
: file_dialog(type::open, title, default_path, filters, options)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline open_file::open_file(std::string const &title,
|
|
|
|
|
std::string const &default_path,
|
|
|
|
|
std::vector<std::string> const &filters,
|
|
|
|
|
bool allow_multiselect)
|
|
|
|
|
: open_file(title, default_path, filters,
|
|
|
|
|
(allow_multiselect ? opt::multiselect : opt::none))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::vector<std::string> open_file::result()
|
|
|
|
|
{
|
|
|
|
|
return vector_result();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// save_file implementation
|
|
|
|
|
|
|
|
|
|
inline save_file::save_file(std::string const &title,
|
|
|
|
|
std::string const &default_path /* = "" */,
|
|
|
|
|
std::vector<std::string> const &filters /* = { "All Files", "*" } */,
|
|
|
|
|
opt options /* = opt::none */)
|
|
|
|
|
: file_dialog(type::save, title, default_path, filters, options)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline save_file::save_file(std::string const &title,
|
|
|
|
|
std::string const &default_path,
|
|
|
|
|
std::vector<std::string> const &filters,
|
|
|
|
|
bool confirm_overwrite)
|
|
|
|
|
: save_file(title, default_path, filters,
|
|
|
|
|
(confirm_overwrite ? opt::none : opt::force_overwrite))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::string save_file::result()
|
|
|
|
|
{
|
|
|
|
|
return string_result();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// select_folder implementation
|
|
|
|
|
|
|
|
|
|
inline select_folder::select_folder(std::string const &title,
|
|
|
|
|
std::string const &default_path /* = "" */,
|
|
|
|
|
opt options /* = opt::none */)
|
|
|
|
|
: file_dialog(type::folder, title, default_path, {}, options)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::string select_folder::result()
|
|
|
|
|
{
|
|
|
|
|
return string_result();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif // PFD_SKIP_IMPLEMENTATION
|
|
|
|
|
|
|
|
|
|
} // namespace pfd
|
|
|
|
|
|