#include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE *unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } // First, find the length of the string with a safe upper bound (CWE-126). // UNICODE_STRING_MAX_CHARS (32767) is the maximum length of a UNICODE_STRING. int input_length = static_cast(wcsnlen(utf16_string, UNICODE_STRING_MAX_CHARS)); // Now use that bounded length to determine the required buffer size. // When an explicit length is passed, WideCharToMultiByte does not include // the null terminator in its returned size. int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, nullptr, 0, nullptr, nullptr); std::string utf8_string; if (target_length == 0 || static_cast(target_length) > utf8_string.max_size()) { return utf8_string; } utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } namespace { // Must match kAuthCallbackRedirectUrl in // lib/features/auth/data/supabase_auth_service.dart. constexpr wchar_t kAppLinkProtocol[] = L"com.companionglp.glpcompanion"; } // namespace void RegisterAppLinkProtocol() { wchar_t exe_path[MAX_PATH]; const DWORD length = ::GetModuleFileNameW(nullptr, exe_path, MAX_PATH); if (length == 0 || length >= MAX_PATH) { return; } const std::wstring exe(exe_path, length); // "%1" receives the activated link, e.g. // com.companionglp.glpcompanion://login-callback/?code=... const std::wstring command = L"\"" + exe + L"\" \"%1\""; const std::wstring protocol_key = std::wstring(L"Software\\Classes\\") + kAppLinkProtocol; HKEY key = nullptr; if (::RegCreateKeyExW(HKEY_CURRENT_USER, protocol_key.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &key, nullptr) == ERROR_SUCCESS) { const std::wstring description = L"URL:GLP-1 Companion Protocol"; ::RegSetValueExW( key, nullptr, 0, REG_SZ, reinterpret_cast(description.c_str()), static_cast((description.size() + 1) * sizeof(wchar_t))); const std::wstring url_protocol = L""; ::RegSetValueExW( key, L"URL Protocol", 0, REG_SZ, reinterpret_cast(url_protocol.c_str()), static_cast((url_protocol.size() + 1) * sizeof(wchar_t))); ::RegCloseKey(key); } const std::wstring command_key = protocol_key + L"\\shell\\open\\command"; if (::RegCreateKeyExW(HKEY_CURRENT_USER, command_key.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &key, nullptr) == ERROR_SUCCESS) { ::RegSetValueExW( key, nullptr, 0, REG_SZ, reinterpret_cast(command.c_str()), static_cast((command.size() + 1) * sizeof(wchar_t))); ::RegCloseKey(key); } } void LogAppLink(const std::string& message) { wchar_t temp[MAX_PATH]; const DWORD temp_length = ::GetTempPathW(MAX_PATH, temp); if (temp_length == 0 || temp_length >= MAX_PATH) { return; } const std::wstring path = std::wstring(temp, temp_length) + L"glpcompanion-protocol.log"; SYSTEMTIME stamp_source = {}; ::GetLocalTime(&stamp_source); char stamp[32] = {}; snprintf(stamp, sizeof(stamp), "[%02u:%02u:%02u.%03u] ", stamp_source.wHour, stamp_source.wMinute, stamp_source.wSecond, stamp_source.wMilliseconds); FILE* log_file = nullptr; if (_wfopen_s(&log_file, path.c_str(), L"a") == 0 && log_file != nullptr) { fprintf(log_file, "%s%s\n", stamp, message.c_str()); fclose(log_file); } ::OutputDebugStringA((std::string(stamp) + message + "\n").c_str()); } bool HasAppLinkArgument() { int argc = 0; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return false; } // Mirrors applinks::AppLinksPlugin::GetLink(): exactly one extra argument // carrying a URI scheme counts as a protocol activation. bool has_link = false; if (argc == 2) { const std::wstring arg(argv[1]); has_link = arg.find(L"://") != std::wstring::npos; } ::LocalFree(argv); return has_link; } namespace { // Held for the process lifetime once claimed; the OS releases it on exit. HANDLE g_single_instance_mutex = nullptr; } // namespace bool AcquireSingleInstanceMutex() { if (g_single_instance_mutex != nullptr) { return true; } g_single_instance_mutex = ::CreateMutexW(nullptr, TRUE, L"Local\\GLPCompanionSingleInstance"); if (g_single_instance_mutex == nullptr) { // Fail open: never block launch on a mutex problem. return true; } if (::GetLastError() == ERROR_ALREADY_EXISTS) { ::CloseHandle(g_single_instance_mutex); g_single_instance_mutex = nullptr; return false; } return true; }