MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
COMBackend.cpp
Go to the documentation of this file.
1#ifdef MAYAFLUX_PLATFORM_WINDOWS
2
3#include "COMBackend.hpp"
4
6
7#include <windows.h>
8#include <shobjidl_core.h>
9
10#ifdef ERROR
11#undef ERROR
12#endif
13
14namespace MayaFlux::Core {
15
16namespace {
17
18 /**
19 * @brief Convert a UTF-8 std::string to a UTF-16 std::wstring.
20 */
21 std::wstring to_wide(const std::string& s)
22 {
23 if (s.empty()) {
24 return {};
25 }
26 const int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
27 std::wstring result(len - 1, L'\0');
28 MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, result.data(), len);
29 return result;
30 }
31
32 /**
33 * @brief Convert a UTF-16 std::wstring to a UTF-8 std::string.
34 */
35 std::string to_narrow(const std::wstring& ws)
36 {
37 if (ws.empty()) {
38 return {};
39 }
40 const int len = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, nullptr, 0, nullptr, nullptr);
41 std::string result(len - 1, '\0');
42 WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), -1, result.data(), len, nullptr, nullptr);
43 return result;
44 }
45
46 /**
47 * @brief Build a COMDLG_FILTERSPEC array from SystemFileFilter entries.
48 *
49 * Both the name and pattern wstrings must outlive the COMDLG_FILTERSPEC array,
50 * so they are returned as parallel vectors that the caller keeps alive.
51 */
52 void build_filter_specs(
53 const std::vector<SystemFileFilter>& filters,
54 std::vector<std::wstring>& names,
55 std::vector<std::wstring>& patterns,
56 std::vector<COMDLG_FILTERSPEC>& specs)
57 {
58 names.reserve(filters.size());
59 patterns.reserve(filters.size());
60 specs.reserve(filters.size());
61
62 for (const auto& f : filters) {
63 names.push_back(to_wide(f.name));
64
65 std::wstring pattern;
66 for (size_t i = 0; i < f.extensions.size(); ++i) {
67 if (i > 0) {
68 pattern += L';';
69 }
70 pattern += (f.extensions[i] == "*") ? L"*.*" : L"*." + to_wide(f.extensions[i]);
71 }
72 patterns.push_back(std::move(pattern));
73
74 specs.push_back({ names.back().c_str(), patterns.back().c_str() });
75 }
76 }
77
78 /**
79 * @brief Run one COM file dialog on the calling thread.
80 *
81 * CoInitializeEx / CoUninitialize are called here so the dispatch
82 * thread owns its own COM apartment. Fires @p callback before returning.
83 */
84 void run_dialog(
85 bool is_save,
87 const std::vector<SystemFileFilter>& filters,
88 const std::filesystem::path& start_dir,
89 const std::string& suggested_name)
90 {
91 HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
92 if (FAILED(hr)) {
94 "COMBackend: CoInitializeEx failed: 0x{:08X}", static_cast<uint32_t>(hr));
96 return;
97 }
98
99 IFileDialog* dialog = nullptr;
100 const CLSID clsid = is_save ? CLSID_FileSaveDialog : CLSID_FileOpenDialog;
101 const IID iid = is_save ? IID_IFileSaveDialog : IID_IFileOpenDialog;
102
103 hr = CoCreateInstance(clsid, nullptr, CLSCTX_ALL, iid,
104 reinterpret_cast<void**>(&dialog));
105
106 if (FAILED(hr) || !dialog) {
108 "COMBackend: CoCreateInstance failed: 0x{:08X}", static_cast<uint32_t>(hr));
109 CoUninitialize();
111 return;
112 }
113
114 std::vector<std::wstring> names, pattern_strings;
115 std::vector<COMDLG_FILTERSPEC> specs;
116 build_filter_specs(filters, names, pattern_strings, specs);
117 if (!specs.empty()) {
118 dialog->SetFileTypes(static_cast<UINT>(specs.size()), specs.data());
119 }
120
121 if (is_save && !suggested_name.empty()) {
122 dialog->SetFileName(to_wide(suggested_name).c_str());
123 }
124
125 if (!start_dir.empty()) {
126 IShellItem* folder = nullptr;
127 hr = SHCreateItemFromParsingName(start_dir.wstring().c_str(), nullptr,
128 IID_IShellItem, reinterpret_cast<void**>(&folder));
129 if (SUCCEEDED(hr) && folder) {
130 dialog->SetFolder(folder);
131 folder->Release();
132 }
133 }
134
135 hr = dialog->Show(nullptr);
136
137 if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
138 dialog->Release();
139 CoUninitialize();
140 callback(std::unexpected(SystemDialogError::Cancelled));
141 return;
142 }
143
144 if (FAILED(hr)) {
146 "COMBackend: dialog Show failed: 0x{:08X}", static_cast<uint32_t>(hr));
147 dialog->Release();
148 CoUninitialize();
150 return;
151 }
152
153 IShellItem* result = nullptr;
154 hr = dialog->GetResult(&result);
155
156 if (FAILED(hr) || !result) {
157 dialog->Release();
158 CoUninitialize();
160 return;
161 }
162
163 PWSTR path_raw = nullptr;
164 hr = result->GetDisplayName(SIGDN_FILESYSPATH, &path_raw);
165 result->Release();
166 dialog->Release();
167
168 if (FAILED(hr) || !path_raw) {
169 CoUninitialize();
171 return;
172 }
173
174 std::filesystem::path chosen_path(path_raw);
175 CoTaskMemFree(path_raw);
176 CoUninitialize();
177
178 callback(std::move(chosen_path));
179 }
180
181} // namespace
182
183// =============================================================================
184// Lifecycle
185// =============================================================================
186
187bool COMBackend::initialize()
188{
189 if (m_initialized) {
190 return true;
191 }
192 m_initialized = true;
194 "COMBackend initialized");
195 return true;
196}
197
198void COMBackend::shutdown()
199{
200 if (!m_initialized) {
201 return;
202 }
203 m_initialized = false;
205 "COMBackend shutdown");
206}
207
208// =============================================================================
209// Dialog operations
210// =============================================================================
211
212void COMBackend::open_file(
214 std::vector<SystemFileFilter> filters,
215 std::filesystem::path start_dir)
216{
217 invoke_dialog(false, std::move(callback), filters, start_dir, {});
218}
219
220void COMBackend::save_file(
222 std::string suggested_name,
223 std::vector<SystemFileFilter> filters,
224 std::filesystem::path start_dir)
225{
226 invoke_dialog(true, std::move(callback), filters, start_dir, suggested_name);
227}
228
229void COMBackend::invoke_dialog(
230 bool is_save,
232 const std::vector<SystemFileFilter>& filters,
233 const std::filesystem::path& start_dir,
234 const std::string& suggested_name)
235{
236 if (!m_initialized) {
238 return;
239 }
240
241 std::thread(run_dialog, is_save, std::move(callback), filters, start_dir, suggested_name).detach();
242}
243
244} // namespace MayaFlux::Core
245
246#endif // MAYAFLUX_PLATFORM_WINDOWS
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
PickCallback callback
Definition Picker.cpp:22
@ BackendError
Platform backend failed to open or communicate.
@ Cancelled
User dismissed the dialog without completing it.
std::function< void(FileDialogResult)> FileDialogCallback
Callback type for all file dialog operations.
@ API
API calls from external code.
@ Core
Core engine, backend, subsystems.
std::shared_ptr< Vruta::Routine > pattern(std::function< std::any(uint64_t)> pattern_func, std::function< void(std::any)> callback, std::function< bool(uint64_t)> ready, Vruta::ProcessingToken token)
The std::any, uint64_t form of the condition driven pattern.
Definition Pattern.cpp:5