Line data Source code
1 : // =============================================================================
2 : // Native file dialog implementations (Windows, macOS, Linux)
3 : // Open dialog implementation
4 : // =============================================================================
5 :
6 : #include <cstring>
7 :
8 : #include "gui/dialogs/file_dialog.h"
9 :
10 : #ifdef _WIN32
11 : // clang-format off
12 : #define WIN32_LEAN_AND_MEAN
13 : #include <windows.h>
14 : #include <commdlg.h>
15 : // clang-format on
16 : #endif
17 :
18 : #ifdef __APPLE__
19 : #include <TargetConditionals.h>
20 : #include <fcntl.h>
21 : #include <sys/wait.h>
22 : #include <unistd.h>
23 :
24 : #include <cstdio>
25 : #endif
26 :
27 : #ifndef _WIN32
28 : #include <sys/wait.h>
29 : #endif
30 :
31 : namespace Amplitron {
32 :
33 : #ifdef AMPLITRON_HEADLESS
34 3 : std::string show_open_dialog(const std::string&, const std::string&, const std::string&) {
35 4 : return "";
36 : }
37 : #else
38 :
39 : #ifdef _WIN32
40 0 : std::string show_open_dialog(const std::string& title, const std::string& filter_desc,
41 : const std::string& filter_ext) {
42 0 : char filename[MAX_PATH] = "";
43 :
44 0 : char filter[256];
45 0 : std::memset(filter, 0, sizeof(filter));
46 0 : int pos = 0;
47 0 : pos += snprintf(filter + pos, 256 - pos, "%s (*.%s)", filter_desc.c_str(), filter_ext.c_str());
48 0 : pos++;
49 0 : pos += snprintf(filter + pos, 256 - pos, "*.%s", filter_ext.c_str());
50 0 : pos++;
51 0 : pos += snprintf(filter + pos, 256 - pos, "All Files (*.*)");
52 0 : pos++;
53 0 : pos += snprintf(filter + pos, 256 - pos, "*.*");
54 :
55 0 : OPENFILENAMEA ofn;
56 0 : std::memset(&ofn, 0, sizeof(ofn));
57 0 : ofn.lStructSize = sizeof(ofn);
58 0 : ofn.hwndOwner = NULL;
59 0 : ofn.lpstrFilter = filter;
60 0 : ofn.lpstrFile = filename;
61 0 : ofn.nMaxFile = MAX_PATH;
62 0 : ofn.lpstrTitle = title.c_str();
63 0 : ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
64 :
65 0 : if (GetOpenFileNameA(&ofn)) {
66 0 : return std::string(filename);
67 : }
68 0 : return "";
69 : }
70 :
71 : #elif defined(__APPLE__) && !TARGET_OS_IOS
72 0 : std::string show_open_dialog(const std::string& title, const std::string& /*filter_desc*/,
73 : const std::string& filter_ext) {
74 : // Sanitize title and filter_ext for AppleScript
75 0 : std::string safe_title;
76 0 : for (char c : title) {
77 0 : if (c == '\\') {
78 0 : safe_title += "\\\\";
79 0 : } else if (c == '"') {
80 0 : safe_title += "\\\"";
81 0 : } else {
82 0 : safe_title += c;
83 : }
84 : }
85 :
86 0 : std::string safe_ext;
87 0 : for (char c : filter_ext) {
88 0 : if (c == '\\') {
89 0 : safe_ext += "\\\\";
90 0 : } else if (c == '"') {
91 0 : safe_ext += "\\\"";
92 0 : } else {
93 0 : safe_ext += c;
94 : }
95 : }
96 :
97 0 : std::string script = "POSIX path of (choose file of type {\"" + safe_ext +
98 0 : "\"} with prompt \"" + safe_title + "\")";
99 :
100 : // Use fork+exec to invoke osascript directly
101 : int pipefd[2];
102 0 : if (pipe(pipefd) != 0) return "";
103 :
104 0 : pid_t pid = fork();
105 0 : if (pid < 0) {
106 0 : close(pipefd[0]);
107 0 : close(pipefd[1]);
108 0 : return "";
109 : }
110 :
111 0 : if (pid == 0) {
112 0 : close(pipefd[0]);
113 0 : dup2(pipefd[1], STDOUT_FILENO);
114 0 : close(pipefd[1]);
115 0 : int devnull = open("/dev/null", O_WRONLY);
116 0 : if (devnull >= 0) {
117 0 : dup2(devnull, STDERR_FILENO);
118 0 : close(devnull);
119 0 : }
120 0 : execl("/usr/bin/osascript", "osascript", "-e", script.c_str(), nullptr);
121 0 : _exit(1);
122 : }
123 :
124 0 : close(pipefd[1]);
125 : char buf[1024];
126 0 : std::string result;
127 : ssize_t n;
128 0 : while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) result.append(buf, static_cast<size_t>(n));
129 0 : close(pipefd[0]);
130 :
131 0 : int status = 0;
132 0 : waitpid(pid, &status, 0);
133 :
134 0 : while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
135 :
136 0 : return result;
137 0 : }
138 :
139 : #else // Linux
140 0 : std::string show_open_dialog(const std::string& title, const std::string& filter_desc,
141 : const std::string& filter_ext) {
142 : // Escape single quotes for shell
143 0 : auto escape_single_quotes = [](const std::string& s) {
144 0 : std::string result;
145 0 : for (char c : s) {
146 0 : if (c == '\'') {
147 0 : result += "'\\''";
148 : } else {
149 0 : result += c;
150 : }
151 : }
152 0 : return result;
153 0 : };
154 :
155 0 : std::string safe_title = escape_single_quotes(title);
156 0 : std::string safe_desc = escape_single_quotes(filter_desc);
157 0 : std::string safe_ext = escape_single_quotes(filter_ext);
158 :
159 : std::string cmd =
160 : "zenity --file-selection "
161 0 : "--title='" +
162 0 : safe_title +
163 : "' "
164 0 : "--file-filter='" +
165 0 : safe_desc + " (*." + safe_ext + ")|*." + safe_ext +
166 : "' "
167 0 : "--file-filter='All Files (*)|*' 2>/dev/null";
168 :
169 0 : FILE* pipe = popen(cmd.c_str(), "r");
170 0 : if (!pipe) return "";
171 :
172 : char buf[1024];
173 0 : std::string result;
174 0 : while (fgets(buf, sizeof(buf), pipe)) {
175 0 : result += buf;
176 : }
177 0 : int wait_status = pclose(pipe);
178 :
179 0 : if (WIFEXITED(wait_status) && WEXITSTATUS(wait_status) != 0) {
180 0 : cmd = "kdialog --getopenfilename ~/ '*." + safe_ext + "|" + safe_desc +
181 : "' "
182 0 : "--title '" +
183 0 : safe_title + "' 2>/dev/null";
184 0 : pipe = popen(cmd.c_str(), "r");
185 0 : if (!pipe) return "";
186 0 : result.clear();
187 0 : while (fgets(buf, sizeof(buf), pipe)) {
188 0 : result += buf;
189 : }
190 0 : pclose(pipe);
191 : }
192 :
193 0 : while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
194 :
195 0 : return result;
196 0 : }
197 : #endif
198 :
199 : #endif // AMPLITRON_HEADLESS
200 :
201 : } // namespace Amplitron
|