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