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