diff --git a/doc/classes/NativeFileDialog.xml b/doc/classes/NativeFileDialog.xml
new file mode 100644
index 000000000000..7b96e8684650
--- /dev/null
+++ b/doc/classes/NativeFileDialog.xml
@@ -0,0 +1,110 @@
+
+
+
+ Wrapper around operating system-specific file dialogs.
+
+
+ NativeFileDialog is a wrapper over OS-specific methods for choosing files and directories. With it, you can request paths from the user using their system's native interface. It supports filter masks and a customizable title, but since it's not a Godot interface object it cannot be customized or themed.
+
+
+
+
+
+
+
+
+ Adds [code]filter[/code] to the list of filters, which restricts what files can be picked.
+ A [code]filter[/code] should be of the form [code]"filename.extension ; Description"[/code], where filename and extension can be [code]*[/code] to match any string. Filters starting with [code].[/code] (i.e. empty filenames) are not allowed.
+ Example filters: [code]"*.png ; PNG Images"[/code], [code]"project.godot ; Godot Project"[/code].
+
+
+
+
+
+ Clear all the added filters in the dialog.
+
+
+
+
+
+ Returns whether the dialog has been dismissed by the user. Due to the way native file dialogs work, attempting to get results immediately via [code]get_results()[/code] will block the thread. You can call this method instead to determine whether there are results ready.
+
+
+
+
+
+ Returns the file(s) or directory the user selected, or an empty PackedStringArray if the dialog was cancelled. Note that this method will block the thread until the dialog is dismissed, so it's a good idea to check instead for results with [code]has_results()[/code] and only obtain them when you know they're there.
+
+
+
+
+
+ Returns a single path corresponding to the file or directory the user selected. If the file dialog allowed multiple selection and the user selected more than one file, this will return only the first. Use [code]get_results()[/code] to get all of them. This method blocks the current thread: see note on [code]get_results()[/code] for more information.
+
+
+
+
+
+ Dismisses the native dialog if it's currently shown.
+
+
+
+
+
+ Displays the native dialog.
+
+
+
+
+
+ Whether the current system supports native file dialogs. You should check for this before showing a dialog to the user and have a fallback (perhaps a [FileDialog]) available.
+
+
+
+
+
+ The type of file dialog to display.
+
+
+ The available file type filters. For example, this shows only [code].png[/code] and [code].gd[/code] files: [code]set_filters(PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]))[/code]. Multiple file types can also be specified in a single filter. [code]"*.png, *.jpg, *.jpeg ; Supported Images"[/code] will show both PNG and JPEG files when selected.
+
+
+
+ The directory at which to open the file dialog.
+
+
+
+
+
+
+ Emitted when the user selects a directory.
+
+
+
+
+
+ Emitted when the user selects a file by double-clicking it or pressing the [b]OK[/b] button.
+
+
+
+
+
+ Emitted when the user selects multiple files.
+
+
+
+
+
+ The dialog allows selecting one, and only one file.
+
+
+ The dialog allows selecting multiple files.
+
+
+ The dialog only allows selecting a directory, disallowing the selection of any file.
+
+
+ The dialog allows selecting a file path that may or may not exist.
+
+
+
diff --git a/editor/icons/NativeFileDialog.svg b/editor/icons/NativeFileDialog.svg
new file mode 100644
index 000000000000..b6d154f0ee7c
--- /dev/null
+++ b/editor/icons/NativeFileDialog.svg
@@ -0,0 +1 @@
+
diff --git a/scene/gui/native_file_dialog.cpp b/scene/gui/native_file_dialog.cpp
new file mode 100644
index 000000000000..7e1ae16a84ba
--- /dev/null
+++ b/scene/gui/native_file_dialog.cpp
@@ -0,0 +1,342 @@
+/*************************************************************************/
+/* native_file_dialog.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "native_file_dialog.h"
+#include "thirdparty/portable-file-dialogs/portable-file-dialogs.h"
+
+inline std::string NativeFileDialog::_std_string(const String &gd_string) {
+ return std::string(gd_string.utf8().get_data());
+}
+
+inline std::string NativeFileDialog::_std_string_with_fallback(const String &gd_string, const String &fallback) {
+ if (gd_string.is_empty()) {
+ return std::string(fallback.utf8().get_data());
+ } else {
+ return std::string(gd_string.utf8().get_data());
+ }
+}
+
+void NativeFileDialog::_build_std_filters() {
+ // pfd takes filters in the form of a paired std::vector, so we need to
+ // convert prior to calling up the dialog
+ std_filters.clear();
+
+ if (filters.size() > 1) {
+ std::string all_filters_desc;
+ std::string all_filters_flt;
+ const int max_desc_filters = 5;
+ for (int i = 0; i < filters.size(); i++) {
+ String flt = filters[i].get_slice(";", 0).strip_edges();
+ std::string std_flt = std::string(flt.utf8().get_data());
+ if (i > 0) {
+ all_filters_flt += " ";
+ }
+ if (i < max_desc_filters) {
+ if (i > 0) {
+ all_filters_desc += ", ";
+ }
+ all_filters_desc += std_flt;
+ }
+ all_filters_flt += std_flt;
+ }
+ if (max_desc_filters < filters.size()) {
+ all_filters_desc += ", ...";
+ }
+ all_filters_desc = _std_string(RTR("All Recognized")) + " (" + all_filters_desc + ")";
+ std_filters.push_back(all_filters_desc);
+ std_filters.push_back(all_filters_flt);
+ }
+
+ for (int i = 0; i < filters.size(); i++) {
+ String flt = filters[i].get_slice(";", 0).strip_edges();
+ String desc = filters[i].get_slice(";", 1).strip_edges();
+ if (desc.length()) {
+ std_filters.push_back(_std_string(tr(desc) + " (" + flt + ")"));
+ std_filters.push_back(_std_string(flt.replace(",", " ")));
+ } else {
+ std_filters.push_back(_std_string(flt));
+ std_filters.push_back(_std_string(flt.replace(",", " ")));
+ }
+ }
+}
+
+void NativeFileDialog::_update_std_filters_if_necessary() {
+ if (!std_filters_built) {
+ std_filters_built = true;
+ _build_std_filters();
+ }
+}
+
+bool NativeFileDialog::_has_results() {
+ if (!supported || !waiting) {
+ return false;
+ }
+ switch (active_mode) {
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE:
+ [[fallthrough]];
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILES: {
+ return open_file_dialog->ready();
+ }
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_DIR: {
+ return open_dir_dialog->ready();
+ }
+ case NativeFileMode::NATIVE_FILE_MODE_SAVE_FILE: {
+ return save_file_dialog->ready();
+ }
+ }
+ return false;
+}
+
+void NativeFileDialog::_fetch_results() {
+ if (!supported || !waiting) {
+ return;
+ }
+ stored_results.clear();
+ switch (active_mode) {
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE:
+ [[fallthrough]];
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILES: {
+ std::vector results = open_file_dialog->result();
+ if (!results.empty()) {
+ for (auto r : results) {
+ stored_results.push_back(r.c_str());
+ }
+ }
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_DIR: {
+ stored_results.push_back(open_dir_dialog->result().c_str());
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_SAVE_FILE: {
+ stored_results.push_back(open_dir_dialog->result().c_str());
+ } break;
+ }
+}
+
+inline String NativeFileDialog::_get_first_result() {
+ if (stored_results.is_empty()) {
+ return String();
+ } else {
+ return stored_results[0];
+ }
+}
+
+void NativeFileDialog::_emit_signals_if_necessary() {
+ if (waiting && !sent_signal) {
+ switch (active_mode) {
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE:
+ emit_signal("file_selected", _get_first_result());
+ [[fallthrough]];
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILES: {
+ emit_signal("files_selected", stored_results);
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_DIR: {
+ emit_signal("dir_selected", _get_first_result());
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_SAVE_FILE: {
+ emit_signal("file_selected", _get_first_result());
+ } break;
+ }
+ sent_signal = true;
+ }
+}
+
+void NativeFileDialog::add_filter(const String &p_filter) {
+ ERR_FAIL_COND_MSG(p_filter.begins_with("."), "Filter must be \"filename.extension\", can't start with dot.");
+ filters.push_back(p_filter);
+ std_filters_built = false;
+}
+
+void NativeFileDialog::clear_filters() {
+ filters.clear();
+ std_filters_built = false;
+}
+
+void NativeFileDialog::set_filters(const Vector &p_filters) {
+ filters = p_filters;
+ std_filters_built = false;
+}
+
+bool NativeFileDialog::has_results() {
+ if (!supported) {
+ return false;
+ }
+ if (waiting && _has_results()) {
+ _fetch_results();
+ _emit_signals_if_necessary();
+ }
+ return _has_results();
+}
+
+String NativeFileDialog::get_result() {
+ if (!supported) {
+ return String();
+ }
+ if (waiting) {
+ _fetch_results();
+ _emit_signals_if_necessary();
+ }
+ return _get_first_result();
+}
+
+Vector NativeFileDialog::get_results() {
+ if (!supported) {
+ return Vector();
+ }
+ if (waiting) {
+ _fetch_results();
+ _emit_signals_if_necessary();
+ }
+ return stored_results;
+}
+
+void NativeFileDialog::show() {
+ if (!supported) {
+ return;
+ }
+ if (waiting) {
+ hide();
+ }
+
+ sent_signal = false;
+ active_mode = assigned_mode;
+ waiting = true;
+
+ switch (active_mode) {
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE:
+ [[fallthrough]];
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILES: {
+ _update_std_filters_if_necessary();
+ open_file_dialog = new pfd::open_file(
+ _std_string_with_fallback(title, RTR(active_mode == NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE ? "Open file" : "Open files")),
+ _std_string(start_directory),
+ std_filters,
+ active_mode == NativeFileMode::NATIVE_FILE_MODE_OPEN_FILES ? pfd::opt::multiselect : pfd::opt::none);
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_DIR: {
+ open_dir_dialog = new pfd::select_folder(
+ _std_string_with_fallback(title, RTR("Open folder")),
+ _std_string(start_directory));
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_SAVE_FILE: {
+ _update_std_filters_if_necessary();
+ save_file_dialog = new pfd::save_file(
+ _std_string_with_fallback(title, RTR("Save file")),
+ _std_string(start_directory),
+ std_filters);
+ } break;
+ }
+}
+
+void NativeFileDialog::hide() {
+ if (!supported || !waiting) {
+ return;
+ }
+ waiting = false;
+ switch (active_mode) {
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE:
+ [[fallthrough]];
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_FILES: {
+ if (!open_file_dialog->ready()) {
+ open_file_dialog->kill();
+ }
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_OPEN_DIR: {
+ if (!open_dir_dialog->ready()) {
+ open_dir_dialog->kill();
+ }
+ } break;
+ case NativeFileMode::NATIVE_FILE_MODE_SAVE_FILE: {
+ if (!save_file_dialog->ready()) {
+ save_file_dialog->kill();
+ }
+ } break;
+ }
+}
+
+void NativeFileDialog::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ set_process_internal(true);
+ } break;
+ case NOTIFICATION_INTERNAL_PROCESS: {
+ if (waiting && _has_results()) {
+ _fetch_results();
+ _emit_signals_if_necessary();
+ }
+ } break;
+ }
+}
+
+void NativeFileDialog::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("show"), &NativeFileDialog::show);
+ ClassDB::bind_method(D_METHOD("hide"), &NativeFileDialog::hide);
+
+ ClassDB::bind_method(D_METHOD("has_results"), &NativeFileDialog::has_results);
+ ClassDB::bind_method(D_METHOD("get_result"), &NativeFileDialog::get_result);
+ ClassDB::bind_method(D_METHOD("get_results"), &NativeFileDialog::get_results);
+
+ ClassDB::bind_method(D_METHOD("set_start_directory"), &NativeFileDialog::set_start_directory);
+ ClassDB::bind_method(D_METHOD("get_start_directory"), &NativeFileDialog::get_start_directory);
+ ClassDB::bind_method(D_METHOD("set_file_mode"), &NativeFileDialog::set_file_mode);
+ ClassDB::bind_method(D_METHOD("get_file_mode"), &NativeFileDialog::get_file_mode);
+ ClassDB::bind_method(D_METHOD("set_title", "title"), &NativeFileDialog::set_title);
+ ClassDB::bind_method(D_METHOD("get_title"), &NativeFileDialog::get_title);
+
+ ClassDB::bind_method(D_METHOD("clear_filters"), &NativeFileDialog::clear_filters);
+ ClassDB::bind_method(D_METHOD("add_filter", "filter"), &NativeFileDialog::add_filter);
+ ClassDB::bind_method(D_METHOD("set_filters", "filters"), &NativeFileDialog::set_filters);
+ ClassDB::bind_method(D_METHOD("get_filters"), &NativeFileDialog::get_filters);
+
+ ClassDB::bind_static_method(SNAME("NativeFileDialog"), D_METHOD("is_supported"), &NativeFileDialog::is_supported);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "start_directory"), "set_start_directory", "get_start_directory");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Save File"), "set_file_mode", "get_file_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters");
+
+ ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path")));
+ ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths")));
+ ADD_SIGNAL(MethodInfo("dir_selected", PropertyInfo(Variant::STRING, "dir")));
+
+ BIND_ENUM_CONSTANT(NATIVE_FILE_MODE_OPEN_FILE);
+ BIND_ENUM_CONSTANT(NATIVE_FILE_MODE_OPEN_FILES);
+ BIND_ENUM_CONSTANT(NATIVE_FILE_MODE_OPEN_DIR);
+ BIND_ENUM_CONSTANT(NATIVE_FILE_MODE_SAVE_FILE);
+}
+
+NativeFileDialog::NativeFileDialog() {
+ supported = pfd::settings::available();
+}
+
+NativeFileDialog::~NativeFileDialog() {
+ if (open_file_dialog != nullptr) {
+ delete open_file_dialog;
+ }
+}
diff --git a/scene/gui/native_file_dialog.h b/scene/gui/native_file_dialog.h
new file mode 100644
index 000000000000..99dad361598b
--- /dev/null
+++ b/scene/gui/native_file_dialog.h
@@ -0,0 +1,119 @@
+/*************************************************************************/
+/* native_file_dialog.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef NATIVE_FILE_DIALOG_H
+#define NATIVE_FILE_DIALOG_H
+
+#include "scene/main/node.h"
+#include "thirdparty/portable-file-dialogs/portable-file-dialogs.h"
+
+class NativeFileDialog : public Node {
+ GDCLASS(NativeFileDialog, Node);
+
+public:
+ enum NativeFileMode {
+ NATIVE_FILE_MODE_OPEN_FILE,
+ NATIVE_FILE_MODE_OPEN_FILES,
+ NATIVE_FILE_MODE_OPEN_DIR,
+ NATIVE_FILE_MODE_SAVE_FILE,
+ };
+
+private:
+ NativeFileMode assigned_mode = NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE;
+ NativeFileMode active_mode = NativeFileMode::NATIVE_FILE_MODE_OPEN_FILE;
+
+ bool waiting = false;
+ bool supported = true;
+ bool sent_signal = false;
+
+ String title = "";
+ String start_directory = "";
+
+ Vector stored_results = Vector();
+ Vector filters;
+
+ // PFD uses classes instead of normal function calls so need separate members
+ union {
+ pfd::open_file *open_file_dialog = nullptr;
+ pfd::select_folder *open_dir_dialog;
+ pfd::save_file *save_file_dialog;
+ };
+
+ inline std::string _std_string(const String &gd_string);
+ inline std::string _std_string_with_fallback(const String &gd_string, const String &fallback);
+
+ void _build_std_filters();
+ void _update_std_filters_if_necessary();
+
+ bool _has_results();
+ void _fetch_results();
+ void _emit_signals_if_necessary();
+
+ inline String _get_first_result();
+
+ bool std_filters_built = false;
+ std::vector std_filters;
+
+ static void _bind_methods();
+
+protected:
+ void _notification(int p_what);
+
+public:
+ static bool is_supported() { return pfd::settings::available(); }
+
+ void clear_filters();
+ void add_filter(const String &p_filter);
+ void set_filters(const Vector &p_filters);
+ Vector get_filters() const { return filters; };
+
+ void set_file_mode(NativeFileMode p_mode) { assigned_mode = p_mode; }
+ NativeFileMode get_file_mode() const { return assigned_mode; }
+
+ void set_start_directory(String p_start_directory) { start_directory = p_start_directory; }
+ String get_start_directory() { return start_directory; }
+
+ void set_title(String p_title) { title = p_title; }
+ String get_title() { return title; }
+
+ bool has_results();
+ String get_result();
+ Vector get_results();
+
+ void show();
+ void hide();
+
+ NativeFileDialog();
+ ~NativeFileDialog();
+};
+
+VARIANT_ENUM_CAST(NativeFileDialog::NativeFileMode);
+
+#endif // NATIVE_FILE_DIALOG_H
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 6c0192cf44ae..d1a8a03a31b2 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -101,6 +101,7 @@
#include "scene/gui/link_button.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/menu_button.h"
+#include "scene/gui/native_file_dialog.h"
#include "scene/gui/nine_patch_rect.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel.h"
@@ -340,6 +341,7 @@ void register_scene_types() {
GDREGISTER_CLASS(CheckButton);
GDREGISTER_CLASS(LinkButton);
GDREGISTER_CLASS(Panel);
+ GDREGISTER_CLASS(NativeFileDialog);
GDREGISTER_VIRTUAL_CLASS(Range);
OS::get_singleton()->yield(); // may take time to init
diff --git a/thirdparty/portable-file-dialogs/COPYING b/thirdparty/portable-file-dialogs/COPYING
new file mode 100644
index 000000000000..8b014d64ae1d
--- /dev/null
+++ b/thirdparty/portable-file-dialogs/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/thirdparty/portable-file-dialogs/portable-file-dialogs.h b/thirdparty/portable-file-dialogs/portable-file-dialogs.h
new file mode 100644
index 000000000000..54783fd04308
--- /dev/null
+++ b/thirdparty/portable-file-dialogs/portable-file-dialogs.h
@@ -0,0 +1,1744 @@
+//
+// Portable File Dialogs
+//
+// Copyright © 2018—2020 Sam Hocevar
+//
+// 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
+#include
+#include
+#include // IFileDialog
+#include
+#include
+#include // std::async
+
+#elif __EMSCRIPTEN__
+#include
+
+#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 // popen()
+#include // std::getenv()
+#include // fcntl()
+#include // read(), pipe(), dup2()
+#include // ::kill, std::signal
+#include // waitpid()
+#endif
+
+#include // std::string
+#include // std::shared_ptr
+#include // std::ostream
+#include