├── .gitignore ├── .lgtm.yml ├── examples ├── .gitignore ├── Makefile ├── kill.cpp ├── kill.vcxproj ├── example.vcxproj └── example.cpp ├── CMakeLists.txt ├── COPYING ├── doc ├── notify.md ├── select_folder.md ├── save_file.md ├── open_file.md ├── message.md └── pfd.md ├── README.md └── portable-file-dialogs.h /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | Makefile 4 | cmake_install.cmake 5 | -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | cpp: 3 | index: 4 | build_command: 5 | - make -C examples 6 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | example 2 | example.exe 3 | kill 4 | kill.exe 5 | 6 | Debug 7 | Release 8 | *.vcxproj.user 9 | 10 | .idea 11 | cmake-build-* 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.0) 2 | 3 | project(portable_file_dialogs VERSION 1.00 LANGUAGES CXX) 4 | 5 | add_library(${PROJECT_NAME} INTERFACE) 6 | target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | 2 | BINARIES = example kill 3 | 4 | CXXFLAGS = -I.. -std=c++11 -g -ggdb -Wall -Wextra 5 | LDFLAGS = $(subst MINGW,-luuid -lucrt,$(findstring MINGW,$(shell uname))) 6 | 7 | all: $(BINARIES) 8 | 9 | example: example.cpp ../portable-file-dialogs.h 10 | $(CXX) $(CXXFLAGS) $(filter %.cpp, $^) $(LDFLAGS) -o $@ 11 | 12 | kill: kill.cpp ../portable-file-dialogs.h 13 | $(CXX) $(CXXFLAGS) $(filter %.cpp, $^) $(LDFLAGS) -o $@ 14 | 15 | clean: 16 | rm -f $(BINARIES) 17 | 18 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /doc/notify.md: -------------------------------------------------------------------------------- 1 | ## Notification API 2 | 3 | Displaying a desktop notification is done using the `pfd::notify` class. It can be provided a 4 | title, a message text, and an `icon` for the notification style: 5 | 6 | ```cpp 7 | pfd::notify::notify(std::string const &title, 8 | std::string const &text, 9 | pfd::icon icon = pfd::icon::info); 10 | 11 | enum class pfd::icon { info, warning, error }; 12 | ``` 13 | 14 | ## Example 15 | 16 | Displaying a notification is straightforward. Emoji are supported: 17 | 18 | ```cpp 19 | pfd::notify("System event", "Something might be on fire 🔥", 20 | pfd::icon::warning); 21 | ``` 22 | 23 | The `pfd::notify` object needs not be kept around, letting the object clean up itself is enough. 24 | 25 | ## Screenshots 26 | 27 | Windows 10: 28 | ![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png) 29 | 30 | Mac OS X (dark theme): 31 | ![image](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png) 32 | 33 | Mac OS X (light theme): 34 | ![image](https://user-images.githubusercontent.com/245089/56053137-92ea2d00-5d53-11e9-8cf2-049486c45713.png) 35 | 36 | Linux (GNOME desktop): 37 | ![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png) 38 | 39 | Linux (KDE desktop): 40 | ![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png) 41 | -------------------------------------------------------------------------------- /examples/kill.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Portable File Dialogs 3 | // 4 | // Copyright © 2018—2020 Sam Hocevar 5 | // 6 | // This program 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 | #include "portable-file-dialogs.h" 14 | 15 | #include 16 | 17 | int main() 18 | { 19 | // Set verbosity to true 20 | pfd::settings::verbose(true); 21 | 22 | // Message box with nice message 23 | auto m = pfd::message("Upgrade software?", 24 | "Press OK to upgrade this software.\n" 25 | "\n" 26 | "By default, the software will update itself\n" 27 | "automatically in 10 seconds.", 28 | pfd::choice::ok_cancel, 29 | pfd::icon::warning); 30 | 31 | // Wait for an answer for up to 10 seconds 32 | for (int i = 0; i < 10 && !m.ready(1000); ++i) 33 | ; 34 | 35 | // Upgrade software if user clicked OK, or if user didn’t interact 36 | bool upgrade = m.ready() ? m.result() == pfd::button::ok : m.kill(); 37 | if (upgrade) 38 | std::cout << "Upgrading software!\n"; 39 | else 40 | std::cout << "Not upgrading software.\n"; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /doc/select_folder.md: -------------------------------------------------------------------------------- 1 | ## Folder Selection API 2 | 3 | The `pfd::select_folder` class handles folder opening dialogs. It can be provided a title, and an 4 | optional starting directory: 5 | 6 | ```cpp 7 | pfd::select_folder::select_folder(std::string const &title, 8 | std::string const &default_path = "", 9 | pfd::opt option = pfd::opt::none); 10 | ``` 11 | 12 | The `option` parameter can be `pfd::opt::force_path` to force the operating system to use the 13 | provided path. Some systems default to the most recently used path, if applicable. 14 | 15 | The selected folder is queried using `pfd::select_folder::result()`. If the user canceled the 16 | operation, the returned string is empty: 17 | 18 | ```cpp 19 | std::string pfd::select_folder::result(); 20 | ``` 21 | 22 | It is possible to ask the folder selection dialog whether the user took action using the 23 | `pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate 24 | the dialog within `timeout` milliseconds, the function will return `false`: 25 | 26 | ```cpp 27 | bool pfd::select_folder::ready(int timeout = pfd::default_wait_timeout); 28 | ``` 29 | 30 | ## Example 1: simple folder selection 31 | 32 | Using `pfd::select_folder::result()` will wait for user action before returning. This operation 33 | will block and return the user choice: 34 | 35 | ```cpp 36 | auto selection = pfd::select_folder("Select a folder").result(); 37 | if (!selection.empty()) 38 | std::cout << "User selected folder " << selection << "\n"; 39 | ``` 40 | 41 | ## Example 2: asynchronous folder open 42 | 43 | Using `pfd::select_folder::ready()` allows the application to perform other tasks while waiting for user input: 44 | 45 | ```cpp 46 | // Folder selection dialog 47 | auto dialog = pfd::select_folder("Select folder to open"); 48 | 49 | // Do something while waiting for user input 50 | while (!dialog.ready(1000)) 51 | std::cout << "Waited 1 second for user input...\n"; 52 | 53 | // Act depending on the user choice 54 | std::cout << "Selected folder: " << dialog.result() << "\n"; 55 | ``` 56 | -------------------------------------------------------------------------------- /doc/save_file.md: -------------------------------------------------------------------------------- 1 | ## File Save API 2 | 3 | The `pfd::save_file` class handles file saving dialogs. It can be provided a title, a starting 4 | directory and/or pre-selected file, an optional filter for recognised file types, and an optional 5 | flag to allow multiple selection: 6 | 7 | ```cpp 8 | pfd::save_file::save_file(std::string const &title, 9 | std::string const &initial_path, 10 | std::vector filters = { "All Files", "*" }, 11 | pfd::opt option = pfd::opt::none); 12 | ``` 13 | 14 | The `option` parameter can be `pfd::opt::force_overwrite` to disable a potential warning when 15 | saving to an existing file. 16 | 17 | The selected file is queried using `pfd::save_file::result()`. If the user canceled the 18 | operation, the returned file name is empty: 19 | 20 | ```cpp 21 | std::string pfd::save_file::result(); 22 | ``` 23 | 24 | It is possible to ask the file save dialog whether the user took action using the 25 | `pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate 26 | the dialog within `timeout` milliseconds, the function will return `false`: 27 | 28 | ```cpp 29 | bool pfd::save_file::ready(int timeout = pfd::default_wait_timeout); 30 | ``` 31 | 32 | ## Example 1: simple file selection 33 | 34 | Using `pfd::save_file::result()` will wait for user action before returning. This operation will 35 | block and return the user choice: 36 | 37 | ```cpp 38 | auto destination = pfd::save_file("Select a file").result(); 39 | if (!destination.empty()) 40 | std::cout << "User selected file " << destination << "\n"; 41 | ``` 42 | 43 | ## Example 2: filters 44 | 45 | The filter list enumerates filter names and corresponded space-separated wildcard lists. It 46 | defaults to `{ "All Files", "*" }`, but here is how to use other options: 47 | 48 | ```cpp 49 | auto destination = pfd::save_file("Select a file", ".", 50 | { "Image Files", "*.png *.jpg *.jpeg *.bmp", 51 | "Audio Files", "*.wav *.mp3", 52 | "All Files", "*" }, 53 | pfd::opt::force_overwrite).result(); 54 | // Do something with destination 55 | std::cout << "Selected file: " << destination << "\n"; 56 | ``` 57 | 58 | ## Example 3: asynchronous file save 59 | 60 | Using `pfd::save_file::ready()` allows the application to perform other tasks while waiting for 61 | user input: 62 | 63 | ```cpp 64 | // File save dialog 65 | auto dialog = pfd::save_file("Select file to save"); 66 | 67 | // Do something while waiting for user input 68 | while (!dialog.ready(1000)) 69 | std::cout << "Waited 1 second for user input...\n"; 70 | 71 | // Act depending on the user choice 72 | std::cout << "User selected file: " << dialog.result() << "\n"; 73 | ``` 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Portable File Dialogs 2 | 3 | A free C++11 file dialog library. 4 | 5 | - works on Windows, Mac OS X, Linux 6 | - **single-header**, no extra library dependencies 7 | - **synchronous *or* asynchronous** (does not block the rest of your program!) 8 | - **cancelable** (kill asynchronous dialogues without user interaction) 9 | - **secure** (immune to shell-quote vulnerabilities) 10 | 11 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/a25d3fd6959a4333871f630ac70b6e09)](https://www.codacy.com/manual/samhocevar/portable-file-dialogs?utm_source=github.com&utm_medium=referral&utm_content=samhocevar/portable-file-dialogs&utm_campaign=Badge_Grade) 12 | 13 | ## Status 14 | 15 | The library is now pretty robust. It is not as feature-complete as 16 | [Tiny File Dialogs](https://sourceforge.net/projects/tinyfiledialogs/), 17 | but has asynchonous dialogs, more maintainable code, and fewer potential 18 | security issues. 19 | 20 | The currently available backends are: 21 | 22 | - Win32 API (all known versions of Windows) 23 | - Mac OS X (using AppleScript) 24 | - GNOME desktop (using [Zenity](https://en.wikipedia.org/wiki/Zenity) or its clones Matedialog and Qarma) 25 | - KDE desktop (using [KDialog](https://github.com/KDE/kdialog)) 26 | 27 | Experimental support for Emscripten is on its way. 28 | 29 | ## Documentation 30 | 31 | - [`pfd`](doc/pfd.md) general documentation 32 | - [`pfd::message`](doc/message.md) message box 33 | - [`pfd::notify`](doc/notify.md) notification 34 | - [`pfd::open_file`](doc/open_file.md) file open 35 | - [`pfd::save_file`](doc/save_file.md) file save 36 | - [`pfd::select_folder`](doc/select_folder.md) folder selection 37 | 38 | ## History 39 | 40 | - 0.1.0 (July 16, 2020): first public release 41 | 42 | ## Screenshots (Windows 10) 43 | 44 | ![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png) 45 | ![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png) 46 | ![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png) 47 | 48 | ## Screenshots (Mac OS X, dark theme) 49 | 50 | ![warning-osxdark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) 51 | ![notify-osxdark](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png) 52 | ![open-osxdark](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png) 53 | 54 | ## Screenshots (Linux, GNOME desktop) 55 | 56 | ![warning-gnome](https://user-images.githubusercontent.com/245089/47136608-772a3080-d2b4-11e8-9e1d-60a7e743e908.png) 57 | ![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png) 58 | ![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png) 59 | 60 | ## Screenshots (Linux, KDE Plasma desktop) 61 | 62 | ![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png) 63 | ![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png) 64 | ![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png) 65 | -------------------------------------------------------------------------------- /doc/open_file.md: -------------------------------------------------------------------------------- 1 | ## File Open API 2 | 3 | The `pfd::open_file` class handles file opening dialogs. It can be provided a title, a starting 4 | directory and/or pre-selected file, an optional filter for recognised file types, and an optional 5 | flag to allow multiple selection: 6 | 7 | ```cpp 8 | pfd::open_file::open_file(std::string const &title, 9 | std::string const &initial_path, 10 | std::vector filters = { "All Files", "*" }, 11 | pfd::opt option = pfd::opt::none); 12 | ``` 13 | 14 | The `option` parameter can be `pfd::opt::multiselect` to allow selecting multiple files. 15 | 16 | The selected files are queried using `pfd::open_file::result()`. If the user canceled the 17 | operation, the returned list is empty: 18 | 19 | ```cpp 20 | std::vector pfd::open_file::result(); 21 | ``` 22 | 23 | It is possible to ask the file open dialog whether the user took action using the 24 | `pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate 25 | the dialog within `timeout` milliseconds, the function will return `false`: 26 | 27 | ```cpp 28 | bool pfd::open_file::ready(int timeout = pfd::default_wait_timeout); 29 | ``` 30 | 31 | ## Example 1: simple file selection 32 | 33 | Using `pfd::open_file::result()` will wait for user action before returning. This operation will 34 | block and return the user choice: 35 | 36 | ```cpp 37 | auto selection = pfd::open_file("Select a file").result(); 38 | if (!selection.empty()) 39 | std::cout << "User selected file " << selection[0] << "\n"; 40 | ``` 41 | 42 | ## Example 2: filters 43 | 44 | The filter list enumerates filter names and corresponded space-separated wildcard lists. It 45 | defaults to `{ "All Files", "*" }`, but here is how to use other options: 46 | 47 | ```cpp 48 | auto selection = pfd::open_file("Select a file", ".", 49 | { "Image Files", "*.png *.jpg *.jpeg *.bmp", 50 | "Audio Files", "*.wav *.mp3", 51 | "All Files", "*" }, 52 | pfd::opt::multiselect).result(); 53 | // Do something with selection 54 | for (auto const &filename : dialog.result()) 55 | std::cout << "Selected file: " << filename << "\n"; 56 | ``` 57 | 58 | ## Example 3: asynchronous file open 59 | 60 | Using `pfd::open_file::ready()` allows the application to perform other tasks while waiting for 61 | user input: 62 | 63 | ```cpp 64 | // File open dialog 65 | auto dialog = pfd::open_file("Select file to open"); 66 | 67 | // Do something while waiting for user input 68 | while (!dialog.ready(1000)) 69 | std::cout << "Waited 1 second for user input...\n"; 70 | 71 | // Act depending on the user choice 72 | std::cout << "Number of selected files: " << dialog.result().size() << "\n"; 73 | ``` 74 | 75 | ## Screenshots 76 | 77 | Windows 10: 78 | ![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png) 79 | 80 | Mac OS X (dark theme): 81 | ![image](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png) 82 | 83 | Mac OS X (light theme): 84 | ![image](https://user-images.githubusercontent.com/245089/56053413-4fdc8980-5d54-11e9-85e3-e9e5d0e10772.png) 85 | 86 | Linux (GNOME desktop): 87 | ![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png) 88 | 89 | Linux (KDE desktop): 90 | ![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png) 91 | -------------------------------------------------------------------------------- /doc/message.md: -------------------------------------------------------------------------------- 1 | ## Message Box API 2 | 3 | Displaying a message box is done using the `pfd::message` class. It can be provided a title, a 4 | message text, a `choice` representing which buttons need to be rendered, and an `icon` for the 5 | message: 6 | 7 | ```cpp 8 | pfd::message::message(std::string const &title, 9 | std::string const &text, 10 | pfd::choice choice = pfd::choice::ok_cancel, 11 | pfd::icon icon = pfd::icon::info); 12 | 13 | enum class pfd::choice { ok, ok_cancel, yes_no, yes_no_cancel }; 14 | 15 | enum class pfd::icon { info, warning, error, question }; 16 | ``` 17 | 18 | The pressed button is queried using `pfd::message::result()`. If the dialog box is closed by any 19 | other means, the `pfd::button::cancel` is assumed: 20 | 21 | ```cpp 22 | pfd::button pfd::message::result(); 23 | 24 | enum class pfd::button { ok, cancel, yes, no }; 25 | ``` 26 | 27 | It is possible to ask the dialog box whether the user took action using the `pfd::message::ready()` 28 | method, with an optional `timeout` argument. If the user did not press a button within `timeout` 29 | milliseconds, the function will return `false`: 30 | 31 | ```cpp 32 | bool pfd::message::ready(int timeout = pfd::default_wait_timeout); 33 | ``` 34 | 35 | ## Example 1: simple notification 36 | 37 | The `pfd::message` destructor waits for user action, so this operation will block until the user 38 | closes the message box: 39 | 40 | ```cpp 41 | pfd::message("Problem", "An error occurred while doing things", 42 | pfd::choice::ok, pfd::icon::error); 43 | ``` 44 | 45 | ## Example 2: retrieving the pressed button 46 | 47 | Using `pfd::message::result()` will also wait for user action before returning. This operation will block and return the user choice: 48 | 49 | ```cpp 50 | // Ask for user opinion 51 | auto button = pfd::message("Action requested", "Do you want to proceed with things?", 52 | pfd::choice::yes_no, pfd::icon::question).result(); 53 | // Do something with button… 54 | ``` 55 | 56 | ## Example 3: asynchronous message box 57 | 58 | Using `pfd::message::ready()` allows the application to perform other tasks while waiting for 59 | user input: 60 | 61 | ```cpp 62 | // Message box with nice message 63 | auto box = pfd::message("Unsaved Files", "Do you want to save the current " 64 | "document before closing the application?", 65 | pfd::choice::yes_no_cancel, 66 | pfd::icon::warning); 67 | 68 | // Do something while waiting for user input 69 | while (!box.ready(1000)) 70 | std::cout << "Waited 1 second for user input...\n"; 71 | 72 | // Act depending on the selected button 73 | switch (box.result()) 74 | { 75 | case pfd::button::yes: std::cout << "User agreed.\n"; break; 76 | case pfd::button::no: std::cout << "User disagreed.\n"; break; 77 | case pfd::button::cancel: std::cout << "User freaked out.\n"; break; 78 | } 79 | ``` 80 | 81 | ## Screenshots 82 | 83 | #### Windows 10 84 | 85 | ![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png) 86 | 87 | #### Mac OS X 88 | 89 | ![warning-osx-dark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) ![warning-osx-light](https://user-images.githubusercontent.com/245089/56053055-49014700-5d53-11e9-8306-e9a03a25e044.png) 90 | 91 | #### Linux (GNOME desktop) 92 | 93 | ![warning-gnome](https://user-images.githubusercontent.com/245089/47140824-8662ab80-d2bf-11e8-9c87-2742dd5b58af.png) 94 | 95 | #### Linux (KDE desktop) 96 | 97 | ![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png) 98 | -------------------------------------------------------------------------------- /doc/pfd.md: -------------------------------------------------------------------------------- 1 | ## Portable File Dialogs documentation 2 | 3 | The library can be used either as a [header-only library](https://en.wikipedia.org/wiki/Header-only), 4 | or as a [single file library](https://github.com/nothings/single_file_libs). 5 | 6 | ### Use as header-only library 7 | 8 | Just include the main header file wherever needed: 9 | 10 | ```cpp 11 | #include "portable-file-dialogs.h" 12 | 13 | /* ... */ 14 | 15 | pfd::message::message("Hello", "This is a test"); 16 | 17 | /* ... */ 18 | ``` 19 | 20 | ### Use as a single-file library 21 | 22 | Defining the `PFD_SKIP_IMPLEMENTATION` macro before including `portable-file-dialogs.h` will 23 | skip all the implementation code and reduce compilation times. You still need to include the 24 | header without the macro at least once, typically in a `pfd-impl.cpp` file. 25 | 26 | ```cpp 27 | // In pfd-impl.cpp 28 | #include "portable-file-dialogs.h" 29 | ``` 30 | 31 | ```cpp 32 | // In all other files 33 | #define PFD_SKIP_IMPLEMENTATION 1 34 | #include "portable-file-dialogs.h" 35 | ``` 36 | 37 | ### General concepts 38 | 39 | Dialogs inherit from `pfd::dialog` and are created by calling their class constructor. Their 40 | destructor will block until the window is closed by user interaction. So for instance this 41 | will block until the end of the line: 42 | 43 | ```cpp 44 | pfd::message::message("Hi", "there"); 45 | ``` 46 | 47 | Whereas this will only block until the end of the scope, allowing the program to perform 48 | additional operations while the dialog is open: 49 | 50 | ```cpp 51 | { 52 | auto m = pfd::message::message("Hi", "there"); 53 | 54 | // ... perform asynchronous operations here 55 | } 56 | ``` 57 | 58 | It is possible to call `bool pfd::dialog::ready(timeout)` on the dialog in order to query its 59 | status and perform asynchronous operations as long as the user has not interacted: 60 | 61 | ```cpp 62 | { 63 | auto m = pfd::message::message("Hi", "there"); 64 | 65 | while (!m.ready()) 66 | { 67 | // ... perform asynchronous operations here 68 | } 69 | } 70 | ``` 71 | 72 | If necessary, a dialog can be forcibly closed using `bool pfd::dialog::kill()`. Note that this 73 | may be confusing to the user and should only be used in very specific situations. It is also not 74 | possible to close a Windows message box that provides no _Cancel_ button. 75 | 76 | ```cpp 77 | { 78 | auto m = pfd::message::message("Hi", "there"); 79 | 80 | while (!m.ready()) 81 | { 82 | // ... perform asynchronous operations here 83 | 84 | if (too_much_time_has_passed()) 85 | m.kill(); 86 | } 87 | } 88 | ``` 89 | 90 | Finally, the user response can be retrieved using `pfd::dialog::result()`. The return value of 91 | this function depends on which dialog is being used. See their respective documentation for more 92 | information: 93 | 94 | * [`pfd::message`](message.md) (message box) 95 | * [`pfd::notify`](notify.md) (notification) 96 | * [`pfd::open_file`](open_file.md) (file open) 97 | * [`pfd::save_file`](save_file.md) (file save) 98 | * [`pfd::select_folder`](select_folder.md) (folder selection) 99 | 100 | ### Settings 101 | 102 | The library can be queried and configured through the `pfd::settings` class. 103 | 104 | ```cpp 105 | bool pfd::settings::available(); 106 | void pfd::settings::verbose(bool value); 107 | void pfd::settings::rescan(); 108 | ``` 109 | 110 | The return value of `pfd::settings::available()` indicates whether a suitable dialog backend (such 111 | as Zenity or KDialog on Linux) has been found. If not, the library will not work and all dialog 112 | invocations will be no-ops. The program will not crash but you should account for this situation 113 | and add a fallback mechanism or exit gracefully. 114 | 115 | Calling `pfd::settings::rescan()` will force a rescan of available backends. This may change the 116 | result of `pfd::settings::available()` if a backend was installed on the system in the meantime. 117 | This is probably only useful for debugging purposes. 118 | 119 | Calling `pfd::settings::verbose(true)` may help debug the library. It will output debug information 120 | to `std::cout` about some operations being performed. 121 | -------------------------------------------------------------------------------- /examples/kill.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 15.0 29 | {B94D26B1-7EF7-43A2-A973-9A96A08E2E17} 30 | Win32Proj 31 | kill 32 | 10.0 33 | 34 | 35 | 36 | Application 37 | v142 38 | Unicode 39 | 40 | 41 | true 42 | 43 | 44 | false 45 | true 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | true 58 | 59 | 60 | false 61 | 62 | 63 | 64 | .. 65 | NotUsing 66 | Level3 67 | true 68 | true 69 | 70 | 71 | Console 72 | true 73 | 74 | 75 | 76 | 77 | Disabled 78 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 79 | 80 | 81 | 82 | 83 | MaxSpeed 84 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 85 | true 86 | true 87 | 88 | 89 | true 90 | true 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /examples/example.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 15.0 29 | {10F4364D-27C4-4C74-8079-7C42971E81E7} 30 | Win32Proj 31 | example 32 | 10.0 33 | 34 | 35 | 36 | Application 37 | v142 38 | Unicode 39 | 40 | 41 | true 42 | 43 | 44 | false 45 | true 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | true 58 | 59 | 60 | false 61 | 62 | 63 | 64 | .. 65 | NotUsing 66 | MultiThreaded 67 | Level3 68 | true 69 | true 70 | 71 | 72 | Console 73 | true 74 | 75 | 76 | 77 | 78 | Disabled 79 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 80 | 81 | 82 | 83 | 84 | MaxSpeed 85 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 86 | true 87 | true 88 | 89 | 90 | true 91 | true 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /examples/example.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Portable File Dialogs 3 | // 4 | // Copyright © 2018–2022 Sam Hocevar 5 | // 6 | // This program 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 | #include "portable-file-dialogs.h" 14 | 15 | #include 16 | 17 | void test_notify(); 18 | void test_message(); 19 | void test_select_folder(); 20 | void test_open_file(); 21 | void test_save_file(); 22 | 23 | int main() 24 | { 25 | // Check that a backend is available 26 | if (!pfd::settings::available()) 27 | { 28 | std::cout << "Portable File Dialogs are not available on this platform.\n"; 29 | return 1; 30 | } 31 | 32 | // Set verbosity to true 33 | pfd::settings::verbose(true); 34 | 35 | test_notify(); 36 | test_message(); 37 | test_select_folder(); 38 | test_open_file(); 39 | test_save_file(); 40 | } 41 | 42 | void test_notify() 43 | { 44 | // Notification 45 | pfd::notify("Important Notification", 46 | "This is ' a message, pay \" attention \\ to it!", 47 | pfd::icon::info); 48 | 49 | } 50 | 51 | void test_message() 52 | { 53 | // Message box with nice message 54 | auto m = pfd::message("Personal Message", 55 | "You are an amazing person, don’t let anyone make you think otherwise.", 56 | pfd::choice::yes_no_cancel, 57 | pfd::icon::warning); 58 | 59 | // Optional: do something while waiting for user action 60 | for (int i = 0; i < 10 && !m.ready(1000); ++i) 61 | std::cout << "Waited 1 second for user input...\n"; 62 | 63 | // Do something according to the selected button 64 | switch (m.result()) 65 | { 66 | case pfd::button::yes: std::cout << "User agreed.\n"; break; 67 | case pfd::button::no: std::cout << "User disagreed.\n"; break; 68 | case pfd::button::cancel: std::cout << "User freaked out.\n"; break; 69 | default: break; // Should not happen 70 | } 71 | } 72 | 73 | void test_select_folder() 74 | { 75 | // Directory selection 76 | auto dir = pfd::select_folder("Select any directory", pfd::path::home()).result(); 77 | std::cout << "Selected dir: " << dir << "\n"; 78 | } 79 | 80 | void test_open_file() 81 | { 82 | // File open 83 | auto f = pfd::open_file("Choose files to read", pfd::path::home(), 84 | { "Text Files (.txt .text)", "*.txt *.text", 85 | "All Files", "*" }, 86 | pfd::opt::multiselect); 87 | std::cout << "Selected files:"; 88 | for (auto const &name : f.result()) 89 | std::cout << " " + name; 90 | std::cout << "\n"; 91 | } 92 | 93 | void test_save_file() 94 | { 95 | // File save 96 | auto f = pfd::save_file("Choose file to save", 97 | pfd::path::home() + pfd::path::separator() + "readme.txt", 98 | { "Text Files (.txt .text)", "*.txt *.text" }, 99 | pfd::opt::force_overwrite); 100 | std::cout << "Selected file: " << f.result() << "\n"; 101 | } 102 | 103 | // Unused function that just tests the whole API 104 | void test_api() 105 | { 106 | // pfd::settings 107 | pfd::settings::verbose(true); 108 | pfd::settings::rescan(); 109 | 110 | // pfd::notify 111 | pfd::notify("", ""); 112 | pfd::notify("", "", pfd::icon::info); 113 | pfd::notify("", "", pfd::icon::warning); 114 | pfd::notify("", "", pfd::icon::error); 115 | pfd::notify("", "", pfd::icon::question); 116 | 117 | pfd::notify a("", ""); 118 | (void)a.ready(); 119 | (void)a.ready(42); 120 | 121 | // pfd::message 122 | pfd::message("", ""); 123 | pfd::message("", "", pfd::choice::ok); 124 | pfd::message("", "", pfd::choice::ok_cancel); 125 | pfd::message("", "", pfd::choice::yes_no); 126 | pfd::message("", "", pfd::choice::yes_no_cancel); 127 | pfd::message("", "", pfd::choice::retry_cancel); 128 | pfd::message("", "", pfd::choice::abort_retry_ignore); 129 | pfd::message("", "", pfd::choice::ok, pfd::icon::info); 130 | pfd::message("", "", pfd::choice::ok, pfd::icon::warning); 131 | pfd::message("", "", pfd::choice::ok, pfd::icon::error); 132 | pfd::message("", "", pfd::choice::ok, pfd::icon::question); 133 | 134 | pfd::message b("", ""); 135 | (void)b.ready(); 136 | (void)b.ready(42); 137 | (void)b.result(); 138 | } 139 | 140 | -------------------------------------------------------------------------------- /portable-file-dialogs.h: -------------------------------------------------------------------------------- 1 | // 2 | // Portable File Dialogs 3 | // 4 | // Copyright © 2018–2022 Sam Hocevar 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 20 | #include 21 | #include 22 | #include // IFileDialog 23 | #include 24 | #include 25 | #include // std::async 26 | #include // GetUserProfileDirectory() 27 | 28 | #elif __EMSCRIPTEN__ 29 | #include 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 // popen() 41 | #include // std::getenv() 42 | #include // fcntl() 43 | #include // read(), pipe(), dup2(), getuid() 44 | #include // ::kill, std::signal 45 | #include // stat() 46 | #include // waitpid() 47 | #include // getpwnam() 48 | #endif 49 | 50 | #include // std::string 51 | #include // std::shared_ptr 52 | #include // std::ostream 53 | #include // std::map 54 | #include // std::set 55 | #include // std::regex 56 | #include // std::mutex, std::this_thread 57 | #include // 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 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 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 m_future; 198 | std::set 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 class proc 222 | { 223 | public: 224 | proc(dll const &lib, std::string const &sym) 225 | : m_proc(reinterpret_cast((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 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 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 const &filters = {}, 300 | opt options = opt::none); 301 | 302 | protected: 303 | std::string string_result(); 304 | std::vector 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 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 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 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 const &filters, 384 | bool allow_multiselect); 385 | 386 | std::vector 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 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 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(static_cast(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 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 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 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 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 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 args; 792 | std::transform(command.cbegin(), command.cend(), std::back_inserter(args), 793 | [](std::string const &s) { return const_cast(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(*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(*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()) 977 | { 978 | } 979 | 980 | inline std::vector 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 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 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(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(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 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 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 internal::file_dialog::vector_result() 1374 | { 1375 | #if _WIN32 1376 | m_async->result(); 1377 | return m_vector_result; 1378 | #else 1379 | std::vector 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 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(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(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 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(); 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 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 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 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 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 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 | --------------------------------------------------------------------------------