1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "windows.h"
5
6#if defined(CONF_FAMILY_WINDOWS)
7
8#include "system.h"
9
10#include <shlobj.h> // SHChangeNotify
11#include <windows.h>
12
13std::string windows_format_system_message(unsigned long error)
14{
15 WCHAR *wide_message;
16 const DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK;
17 if(FormatMessageW(flags, nullptr, error, 0, (LPWSTR)&wide_message, 0, nullptr) == 0)
18 return "unknown error";
19
20 std::optional<std::string> message = windows_wide_to_utf8(wide_message);
21 LocalFree(wide_message);
22 return message.value_or("(invalid UTF-16 in error message)");
23}
24
25std::wstring windows_args_to_wide(const char **arguments, size_t num_arguments)
26{
27 std::wstring wide_arguments;
28
29 for(size_t i = 0; i < num_arguments; ++i)
30 {
31 if(i > 0)
32 {
33 wide_arguments += L' ';
34 }
35
36 const std::wstring wide_arg = windows_utf8_to_wide(arguments[i]);
37 wide_arguments += L'"';
38
39 size_t backslashes = 0;
40 for(const wchar_t c : wide_arg)
41 {
42 if(c == L'\\')
43 {
44 backslashes++;
45 }
46 else
47 {
48 if(c == L'"')
49 {
50 // Add n+1 backslashes to total 2n+1 before internal '"'
51 for(size_t j = 0; j <= backslashes; ++j)
52 {
53 wide_arguments += L'\\';
54 }
55 }
56 backslashes = 0;
57 }
58 wide_arguments += c;
59 }
60
61 // Add n backslashes to total 2n before ending '"'
62 for(size_t j = 0; j < backslashes; ++j)
63 {
64 wide_arguments += L'\\';
65 }
66 wide_arguments += L'"';
67 }
68
69 return wide_arguments;
70}
71
72std::wstring windows_utf8_to_wide(const char *str)
73{
74 const int orig_length = str_length(str);
75 if(orig_length == 0)
76 return L"";
77 const int size_needed = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, orig_length, nullptr, 0);
78 dbg_assert(size_needed > 0, "Invalid UTF-8 passed to windows_utf8_to_wide");
79 std::wstring wide_string(size_needed, L'\0');
80 dbg_assert(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, orig_length, wide_string.data(), size_needed) == size_needed, "MultiByteToWideChar failure");
81 return wide_string;
82}
83
84std::optional<std::string> windows_wide_to_utf8(const wchar_t *wide_str)
85{
86 const int orig_length = wcslen(wide_str);
87 if(orig_length == 0)
88 return "";
89 const int size_needed = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide_str, orig_length, nullptr, 0, nullptr, nullptr);
90 if(size_needed == 0)
91 return {};
92 std::string string(size_needed, '\0');
93 dbg_assert(WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide_str, orig_length, string.data(), size_needed, nullptr, nullptr) == size_needed, "WideCharToMultiByte failure");
94 return string;
95}
96
97// See https://learn.microsoft.com/en-us/windows/win32/learnwin32/initializing-the-com-library
98CWindowsComLifecycle::CWindowsComLifecycle(bool HasWindow)
99{
100 HRESULT result = CoInitializeEx(nullptr, (HasWindow ? COINIT_APARTMENTTHREADED : COINIT_MULTITHREADED) | COINIT_DISABLE_OLE1DDE);
101 dbg_assert(result != S_FALSE, "COM library already initialized on this thread");
102 dbg_assert(result == S_OK, "COM library initialization failed");
103}
104CWindowsComLifecycle::~CWindowsComLifecycle()
105{
106 CoUninitialize();
107}
108
109static void windows_print_error(const char *system, const char *prefix, HRESULT error)
110{
111 const std::string message = windows_format_system_message(error);
112 dbg_msg(system, "%s: %s", prefix, message.c_str());
113}
114
115static std::wstring filename_from_path(const std::wstring &path)
116{
117 const size_t pos = path.find_last_of(L"/\\");
118 return pos == std::wstring::npos ? path : path.substr(pos + 1);
119}
120
121bool windows_shell_register_protocol(const char *protocol_name, const char *executable, bool *updated)
122{
123 const std::wstring protocol_name_wide = windows_utf8_to_wide(protocol_name);
124 const std::wstring executable_wide = windows_utf8_to_wide(executable);
125
126 // Open registry key for protocol associations of the current user
127 HKEY handle_subkey_classes;
128 const LRESULT result_subkey_classes = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &handle_subkey_classes);
129 if(result_subkey_classes != ERROR_SUCCESS)
130 {
131 windows_print_error("shell_register_protocol", "Error opening registry key", result_subkey_classes);
132 return false;
133 }
134
135 // Create the protocol key
136 HKEY handle_subkey_protocol;
137 const LRESULT result_subkey_protocol = RegCreateKeyExW(handle_subkey_classes, protocol_name_wide.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_protocol, nullptr);
138 RegCloseKey(handle_subkey_classes);
139 if(result_subkey_protocol != ERROR_SUCCESS)
140 {
141 windows_print_error("shell_register_protocol", "Error creating registry key", result_subkey_protocol);
142 return false;
143 }
144
145 // Set the default value for the key, which specifies the name of the display name of the protocol
146 const std::wstring value_protocol = L"URL:" + protocol_name_wide + L" Protocol";
147 const LRESULT result_value_protocol = RegSetValueExW(handle_subkey_protocol, L"", 0, REG_SZ, (BYTE *)value_protocol.c_str(), (value_protocol.length() + 1) * sizeof(wchar_t));
148 if(result_value_protocol != ERROR_SUCCESS)
149 {
150 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_protocol);
151 RegCloseKey(handle_subkey_protocol);
152 return false;
153 }
154
155 // Set the "URL Protocol" value, to specify that this key describes a URL protocol
156 const LRESULT result_value_empty = RegSetValueEx(handle_subkey_protocol, L"URL Protocol", 0, REG_SZ, (BYTE *)L"", sizeof(wchar_t));
157 if(result_value_empty != ERROR_SUCCESS)
158 {
159 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_empty);
160 RegCloseKey(handle_subkey_protocol);
161 return false;
162 }
163
164 // Create the "DefaultIcon" subkey
165 HKEY handle_subkey_icon;
166 const LRESULT result_subkey_icon = RegCreateKeyExW(handle_subkey_protocol, L"DefaultIcon", 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_icon, nullptr);
167 if(result_subkey_icon != ERROR_SUCCESS)
168 {
169 windows_print_error("shell_register_protocol", "Error creating registry key", result_subkey_icon);
170 RegCloseKey(handle_subkey_protocol);
171 return false;
172 }
173
174 // Set the default value for the key, which specifies the icon associated with the protocol
175 const std::wstring value_icon = L"\"" + executable_wide + L"\",0";
176 const LRESULT result_value_icon = RegSetValueExW(handle_subkey_icon, L"", 0, REG_SZ, (BYTE *)value_icon.c_str(), (value_icon.length() + 1) * sizeof(wchar_t));
177 RegCloseKey(handle_subkey_icon);
178 if(result_value_icon != ERROR_SUCCESS)
179 {
180 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_icon);
181 RegCloseKey(handle_subkey_protocol);
182 return false;
183 }
184
185 // Create the "shell\open\command" subkeys
186 HKEY handle_subkey_shell_open_command;
187 const LRESULT result_subkey_shell_open_command = RegCreateKeyExW(handle_subkey_protocol, L"shell\\open\\command", 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_shell_open_command, nullptr);
188 RegCloseKey(handle_subkey_protocol);
189 if(result_subkey_shell_open_command != ERROR_SUCCESS)
190 {
191 windows_print_error("shell_register_protocol", "Error creating registry key", result_subkey_shell_open_command);
192 return false;
193 }
194
195 // Get the previous default value for the key, so we can determine if it changed
196 wchar_t old_value_executable[MAX_PATH + 16];
197 DWORD old_size_executable = sizeof(old_value_executable);
198 const LRESULT result_old_value_executable = RegGetValueW(handle_subkey_shell_open_command, nullptr, L"", RRF_RT_REG_SZ, nullptr, (BYTE *)old_value_executable, &old_size_executable);
199 const std::wstring value_executable = L"\"" + executable_wide + L"\" \"%1\"";
200 if(result_old_value_executable != ERROR_SUCCESS || wcscmp(old_value_executable, value_executable.c_str()) != 0)
201 {
202 // Set the default value for the key, which specifies the executable command associated with the protocol
203 const LRESULT result_value_executable = RegSetValueExW(handle_subkey_shell_open_command, L"", 0, REG_SZ, (BYTE *)value_executable.c_str(), (value_executable.length() + 1) * sizeof(wchar_t));
204 RegCloseKey(handle_subkey_shell_open_command);
205 if(result_value_executable != ERROR_SUCCESS)
206 {
207 windows_print_error("shell_register_protocol", "Error setting registry value", result_value_executable);
208 return false;
209 }
210
211 *updated = true;
212 }
213 else
214 {
215 RegCloseKey(handle_subkey_shell_open_command);
216 }
217
218 return true;
219}
220
221bool windows_shell_register_extension(const char *extension, const char *description, const char *executable_name, const char *executable, bool *updated)
222{
223 const std::wstring extension_wide = windows_utf8_to_wide(extension);
224 const std::wstring executable_name_wide = windows_utf8_to_wide(executable_name);
225 const std::wstring description_wide = executable_name_wide + L" " + windows_utf8_to_wide(description);
226 const std::wstring program_id_wide = executable_name_wide + extension_wide;
227 const std::wstring executable_wide = windows_utf8_to_wide(executable);
228
229 // Open registry key for file associations of the current user
230 HKEY handle_subkey_classes;
231 const LRESULT result_subkey_classes = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &handle_subkey_classes);
232 if(result_subkey_classes != ERROR_SUCCESS)
233 {
234 windows_print_error("shell_register_extension", "Error opening registry key", result_subkey_classes);
235 return false;
236 }
237
238 // Create the program ID key
239 HKEY handle_subkey_program_id;
240 const LRESULT result_subkey_program_id = RegCreateKeyExW(handle_subkey_classes, program_id_wide.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_program_id, nullptr);
241 if(result_subkey_program_id != ERROR_SUCCESS)
242 {
243 windows_print_error("shell_register_extension", "Error creating registry key", result_subkey_program_id);
244 RegCloseKey(handle_subkey_classes);
245 return false;
246 }
247
248 // Set the default value for the key, which specifies the file type description for legacy applications
249 const LRESULT result_description_default = RegSetValueExW(handle_subkey_program_id, L"", 0, REG_SZ, (BYTE *)description_wide.c_str(), (description_wide.length() + 1) * sizeof(wchar_t));
250 if(result_description_default != ERROR_SUCCESS)
251 {
252 windows_print_error("shell_register_extension", "Error setting registry value", result_description_default);
253 RegCloseKey(handle_subkey_program_id);
254 RegCloseKey(handle_subkey_classes);
255 return false;
256 }
257
258 // Set the "FriendlyTypeName" value, which specifies the file type description for modern applications
259 const LRESULT result_description_friendly = RegSetValueExW(handle_subkey_program_id, L"FriendlyTypeName", 0, REG_SZ, (BYTE *)description_wide.c_str(), (description_wide.length() + 1) * sizeof(wchar_t));
260 if(result_description_friendly != ERROR_SUCCESS)
261 {
262 windows_print_error("shell_register_extension", "Error setting registry value", result_description_friendly);
263 RegCloseKey(handle_subkey_program_id);
264 RegCloseKey(handle_subkey_classes);
265 return false;
266 }
267
268 // Create the "DefaultIcon" subkey
269 HKEY handle_subkey_icon;
270 const LRESULT result_subkey_icon = RegCreateKeyExW(handle_subkey_program_id, L"DefaultIcon", 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_icon, nullptr);
271 if(result_subkey_icon != ERROR_SUCCESS)
272 {
273 windows_print_error("register_protocol", "Error creating registry key", result_subkey_icon);
274 RegCloseKey(handle_subkey_program_id);
275 RegCloseKey(handle_subkey_classes);
276 return false;
277 }
278
279 // Set the default value for the key, which specifies the icon associated with the program ID
280 const std::wstring value_icon = L"\"" + executable_wide + L"\",0";
281 const LRESULT result_value_icon = RegSetValueExW(handle_subkey_icon, L"", 0, REG_SZ, (BYTE *)value_icon.c_str(), (value_icon.length() + 1) * sizeof(wchar_t));
282 RegCloseKey(handle_subkey_icon);
283 if(result_value_icon != ERROR_SUCCESS)
284 {
285 windows_print_error("register_protocol", "Error setting registry value", result_value_icon);
286 RegCloseKey(handle_subkey_program_id);
287 RegCloseKey(handle_subkey_classes);
288 return false;
289 }
290
291 // Create the "shell\open\command" subkeys
292 HKEY handle_subkey_shell_open_command;
293 const LRESULT result_subkey_shell_open_command = RegCreateKeyExW(handle_subkey_program_id, L"shell\\open\\command", 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_shell_open_command, nullptr);
294 RegCloseKey(handle_subkey_program_id);
295 if(result_subkey_shell_open_command != ERROR_SUCCESS)
296 {
297 windows_print_error("shell_register_extension", "Error creating registry key", result_subkey_shell_open_command);
298 RegCloseKey(handle_subkey_classes);
299 return false;
300 }
301
302 // Get the previous default value for the key, so we can determine if it changed
303 wchar_t old_value_executable[MAX_PATH + 16];
304 DWORD old_size_executable = sizeof(old_value_executable);
305 const LRESULT result_old_value_executable = RegGetValueW(handle_subkey_shell_open_command, nullptr, L"", RRF_RT_REG_SZ, nullptr, (BYTE *)old_value_executable, &old_size_executable);
306 const std::wstring value_executable = L"\"" + executable_wide + L"\" \"%1\"";
307 if(result_old_value_executable != ERROR_SUCCESS || wcscmp(old_value_executable, value_executable.c_str()) != 0)
308 {
309 // Set the default value for the key, which specifies the executable command associated with the application
310 const LRESULT result_value_executable = RegSetValueExW(handle_subkey_shell_open_command, L"", 0, REG_SZ, (BYTE *)value_executable.c_str(), (value_executable.length() + 1) * sizeof(wchar_t));
311 RegCloseKey(handle_subkey_shell_open_command);
312 if(result_value_executable != ERROR_SUCCESS)
313 {
314 windows_print_error("shell_register_extension", "Error setting registry value", result_value_executable);
315 RegCloseKey(handle_subkey_classes);
316 return false;
317 }
318
319 *updated = true;
320 }
321
322 // Create the file extension key
323 HKEY handle_subkey_extension;
324 const LRESULT result_subkey_extension = RegCreateKeyExW(handle_subkey_classes, extension_wide.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_extension, nullptr);
325 RegCloseKey(handle_subkey_classes);
326 if(result_subkey_extension != ERROR_SUCCESS)
327 {
328 windows_print_error("shell_register_extension", "Error creating registry key", result_subkey_extension);
329 return false;
330 }
331
332 // Get the previous default value for the key, so we can determine if it changed
333 wchar_t old_value_application[128];
334 DWORD old_size_application = sizeof(old_value_application);
335 const LRESULT result_old_value_application = RegGetValueW(handle_subkey_extension, nullptr, L"", RRF_RT_REG_SZ, nullptr, (BYTE *)old_value_application, &old_size_application);
336 if(result_old_value_application != ERROR_SUCCESS || wcscmp(old_value_application, program_id_wide.c_str()) != 0)
337 {
338 // Set the default value for the key, which associates the file extension with the program ID
339 const LRESULT result_value_application = RegSetValueExW(handle_subkey_extension, L"", 0, REG_SZ, (BYTE *)program_id_wide.c_str(), (program_id_wide.length() + 1) * sizeof(wchar_t));
340 RegCloseKey(handle_subkey_extension);
341 if(result_value_application != ERROR_SUCCESS)
342 {
343 windows_print_error("shell_register_extension", "Error setting registry value", result_value_application);
344 return false;
345 }
346
347 *updated = true;
348 }
349 else
350 {
351 RegCloseKey(handle_subkey_extension);
352 }
353
354 return true;
355}
356
357bool windows_shell_register_application(const char *name, const char *executable, bool *updated)
358{
359 const std::wstring name_wide = windows_utf8_to_wide(name);
360 const std::wstring executable_filename = filename_from_path(windows_utf8_to_wide(executable));
361
362 // Open registry key for application registrations
363 HKEY handle_subkey_applications;
364 const LRESULT result_subkey_applications = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\Applications", 0, KEY_ALL_ACCESS, &handle_subkey_applications);
365 if(result_subkey_applications != ERROR_SUCCESS)
366 {
367 windows_print_error("shell_register_application", "Error opening registry key", result_subkey_applications);
368 return false;
369 }
370
371 // Create the program key
372 HKEY handle_subkey_program;
373 const LRESULT result_subkey_program = RegCreateKeyExW(handle_subkey_applications, executable_filename.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &handle_subkey_program, nullptr);
374 RegCloseKey(handle_subkey_applications);
375 if(result_subkey_program != ERROR_SUCCESS)
376 {
377 windows_print_error("shell_register_application", "Error creating registry key", result_subkey_program);
378 return false;
379 }
380
381 // Get the previous default value for the key, so we can determine if it changed
382 wchar_t old_value_executable[MAX_PATH];
383 DWORD old_size_executable = sizeof(old_value_executable);
384 const LRESULT result_old_value_executable = RegGetValueW(handle_subkey_program, nullptr, L"FriendlyAppName", RRF_RT_REG_SZ, nullptr, (BYTE *)old_value_executable, &old_size_executable);
385 if(result_old_value_executable != ERROR_SUCCESS || wcscmp(old_value_executable, name_wide.c_str()) != 0)
386 {
387 // Set the "FriendlyAppName" value, which specifies the displayed name of the application
388 const LRESULT result_program_name = RegSetValueExW(handle_subkey_program, L"FriendlyAppName", 0, REG_SZ, (BYTE *)name_wide.c_str(), (name_wide.length() + 1) * sizeof(wchar_t));
389 RegCloseKey(handle_subkey_program);
390 if(result_program_name != ERROR_SUCCESS)
391 {
392 windows_print_error("shell_register_application", "Error setting registry value", result_program_name);
393 return false;
394 }
395
396 *updated = true;
397 }
398 else
399 {
400 RegCloseKey(handle_subkey_program);
401 }
402
403 return true;
404}
405
406bool windows_shell_unregister_class(const char *shell_class, bool *updated)
407{
408 const std::wstring class_wide = windows_utf8_to_wide(shell_class);
409
410 // Open registry key for protocol and file associations of the current user
411 HKEY handle_subkey_classes;
412 const LRESULT result_subkey_classes = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &handle_subkey_classes);
413 if(result_subkey_classes != ERROR_SUCCESS)
414 {
415 windows_print_error("shell_unregister_class", "Error opening registry key", result_subkey_classes);
416 return false;
417 }
418
419 // Delete the registry keys for the shell class (protocol or program ID)
420 LRESULT result_delete = RegDeleteTreeW(handle_subkey_classes, class_wide.c_str());
421 RegCloseKey(handle_subkey_classes);
422 if(result_delete == ERROR_SUCCESS)
423 {
424 *updated = true;
425 }
426 else if(result_delete != ERROR_FILE_NOT_FOUND)
427 {
428 windows_print_error("shell_unregister_class", "Error deleting registry key", result_delete);
429 return false;
430 }
431
432 return true;
433}
434
435bool windows_shell_unregister_application(const char *executable, bool *updated)
436{
437 const std::wstring executable_filename = filename_from_path(windows_utf8_to_wide(executable));
438
439 // Open registry key for application registrations
440 HKEY handle_subkey_applications;
441 const LRESULT result_subkey_applications = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\Applications", 0, KEY_ALL_ACCESS, &handle_subkey_applications);
442 if(result_subkey_applications != ERROR_SUCCESS)
443 {
444 windows_print_error("shell_unregister_application", "Error opening registry key", result_subkey_applications);
445 return false;
446 }
447
448 // Delete the registry keys for the application description
449 LRESULT result_delete = RegDeleteTreeW(handle_subkey_applications, executable_filename.c_str());
450 RegCloseKey(handle_subkey_applications);
451 if(result_delete == ERROR_SUCCESS)
452 {
453 *updated = true;
454 }
455 else if(result_delete != ERROR_FILE_NOT_FOUND)
456 {
457 windows_print_error("shell_unregister_application", "Error deleting registry key", result_delete);
458 return false;
459 }
460
461 return true;
462}
463
464void windows_shell_update()
465{
466 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
467}
468
469#endif
470