Line data Source code
1 : // =============================================================================
2 : // Native file dialog implementations (Windows, macOS, Linux)
3 : // Save 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 : #include <shlobj.h>
16 : // clang-format on
17 : #endif
18 :
19 : #ifdef __APPLE__
20 : #include <TargetConditionals.h>
21 : #include <fcntl.h>
22 : #include <sys/wait.h>
23 : #include <unistd.h>
24 :
25 : #include <cstdio>
26 : #endif
27 :
28 : #ifndef _WIN32
29 : #include <sys/wait.h>
30 : #endif
31 :
32 : namespace Amplitron {
33 :
34 : #ifdef AMPLITRON_HEADLESS
35 6 : std::string show_save_dialog(const std::string&, const std::string&, const std::string&) {
36 8 : return "";
37 : }
38 : #else
39 :
40 : #ifdef _WIN32
41 0 : std::string show_save_dialog(const std::string& default_name, const std::string& filter_desc,
42 : const std::string& filter_ext) {
43 0 : char filename[MAX_PATH];
44 0 : std::strncpy(filename, default_name.c_str(), MAX_PATH - 1);
45 0 : filename[MAX_PATH - 1] = '\0';
46 :
47 : // Build filter string: "WAV Audio (*.wav)\0*.wav\0All Files (*.*)\0*.*\0\0"
48 0 : char filter[256];
49 0 : std::memset(filter, 0, sizeof(filter));
50 0 : int pos = 0;
51 0 : pos += snprintf(filter + pos, 256 - pos, "%s (*.%s)", filter_desc.c_str(), filter_ext.c_str());
52 0 : pos++; // null separator
53 0 : pos += snprintf(filter + pos, 256 - pos, "*.%s", filter_ext.c_str());
54 0 : pos++; // null separator
55 0 : pos += snprintf(filter + pos, 256 - pos, "All Files (*.*)");
56 0 : pos++;
57 0 : pos += snprintf(filter + pos, 256 - pos, "*.*");
58 : // double null terminator is already there from memset
59 :
60 : // Get desktop/documents as initial dir
61 0 : char initial_dir[MAX_PATH] = "";
62 0 : SHGetFolderPathA(NULL, CSIDL_DESKTOP, NULL, 0, initial_dir);
63 :
64 0 : OPENFILENAMEA ofn;
65 0 : std::memset(&ofn, 0, sizeof(ofn));
66 0 : ofn.lStructSize = sizeof(ofn);
67 0 : ofn.hwndOwner = NULL;
68 0 : ofn.lpstrFilter = filter;
69 0 : ofn.lpstrFile = filename;
70 0 : ofn.nMaxFile = MAX_PATH;
71 0 : ofn.lpstrInitialDir = initial_dir;
72 0 : ofn.lpstrTitle = "Save Recording As";
73 0 : ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR;
74 0 : ofn.lpstrDefExt = filter_ext.c_str();
75 :
76 0 : if (GetSaveFileNameA(&ofn)) {
77 0 : return std::string(filename);
78 : }
79 0 : return "";
80 : }
81 :
82 : #elif defined(__APPLE__) && !TARGET_OS_IOS
83 0 : std::string show_save_dialog(const std::string& default_name, const std::string& /*filter_desc*/,
84 : const std::string& filter_ext) {
85 : // Use osascript to show a native NSSavePanel
86 : std::string cmd =
87 0 : "osascript -e 'set theFile to POSIX path of (choose file name "
88 : "with prompt \"Save Recording As\" "
89 0 : "default name \"" +
90 0 : default_name + "\")' 2>/dev/null";
91 :
92 0 : FILE* pipe = popen(cmd.c_str(), "r");
93 0 : if (!pipe) return "";
94 :
95 : char buf[1024];
96 0 : std::string result;
97 0 : while (fgets(buf, sizeof(buf), pipe)) {
98 0 : result += buf;
99 : }
100 0 : pclose(pipe);
101 :
102 : // Trim trailing newline
103 0 : while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
104 :
105 0 : if (result.empty()) return "";
106 :
107 : // Ensure it ends with the correct extension
108 0 : if (result.size() < filter_ext.size() + 1 ||
109 0 : result.substr(result.size() - filter_ext.size() - 1) != "." + filter_ext) {
110 0 : result += "." + filter_ext;
111 0 : }
112 :
113 0 : return result;
114 0 : }
115 :
116 : #else // Linux
117 0 : std::string show_save_dialog(const std::string& default_name, const std::string& filter_desc,
118 : const std::string& filter_ext) {
119 : // Try zenity first, then kdialog
120 : std::string cmd =
121 : "zenity --file-selection --save --confirm-overwrite "
122 : "--title='Save Recording As' "
123 0 : "--filename='" +
124 0 : default_name +
125 : "' "
126 0 : "--file-filter='" +
127 0 : filter_desc + " (*." + filter_ext + ")|*." + filter_ext +
128 : "' "
129 0 : "--file-filter='All Files (*)|*' 2>/dev/null";
130 :
131 0 : FILE* pipe = popen(cmd.c_str(), "r");
132 0 : if (!pipe) return "";
133 :
134 : char buf[1024];
135 0 : std::string result;
136 0 : while (fgets(buf, sizeof(buf), pipe)) {
137 0 : result += buf;
138 : }
139 0 : int status = pclose(pipe);
140 :
141 : // If zenity failed (not installed), try kdialog
142 0 : if (status != 0) {
143 0 : cmd = "kdialog --getsavefilename ~/ '*." + filter_ext + "|" + filter_desc +
144 : "' "
145 0 : "--title 'Save Recording As' 2>/dev/null";
146 0 : pipe = popen(cmd.c_str(), "r");
147 0 : if (!pipe) return "";
148 0 : result.clear();
149 0 : while (fgets(buf, sizeof(buf), pipe)) {
150 0 : result += buf;
151 : }
152 0 : pclose(pipe);
153 : }
154 :
155 : // Trim trailing newline
156 0 : while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) result.pop_back();
157 :
158 0 : if (result.empty()) return "";
159 :
160 : // Ensure it ends with the correct extension
161 0 : if (result.size() < filter_ext.size() + 1 ||
162 0 : result.substr(result.size() - filter_ext.size() - 1) != "." + filter_ext) {
163 0 : result += "." + filter_ext;
164 : }
165 :
166 0 : return result;
167 0 : }
168 : #endif
169 :
170 : #endif // AMPLITRON_HEADLESS
171 :
172 : } // namespace Amplitron
|