glp-companion/windows/runner/utils.cpp

184 lines
5.9 KiB
C++

#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
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<std::string> 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::string>();
}
std::vector<std::string> 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<int>(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<size_t>(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<const BYTE*>(description.c_str()),
static_cast<DWORD>((description.size() + 1) * sizeof(wchar_t)));
const std::wstring url_protocol = L"";
::RegSetValueExW(
key, L"URL Protocol", 0, REG_SZ,
reinterpret_cast<const BYTE*>(url_protocol.c_str()),
static_cast<DWORD>((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<const BYTE*>(command.c_str()),
static_cast<DWORD>((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;
}