feat(platform): add core Windows system utilities

This commit is contained in:
2026-06-18 19:24:39 -05:00
parent 6fe42963fb
commit 27b90b0840
2 changed files with 234 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ target_sources(izo
src/directory_watcher.cpp
src/time.cpp
$<$<PLATFORM_ID:Windows>:src/interaction_windows.cpp>
$<$<PLATFORM_ID:Windows>:src/platform_windows.cpp>
$<$<PLATFORM_ID:Windows>:src/dialogs_windows.cpp>
$<$<PLATFORM_ID:Linux>:src/dialogs_linux.cpp>
)

233
src/platform_windows.cpp Normal file
View File

@@ -0,0 +1,233 @@
#include <izo/debug.hpp>
#include <izo/dynamic_library.hpp>
#include <izo/environment.hpp>
#include <izo/paths.hpp>
#include <izo/process.hpp>
#include <izo/system.hpp>
#include <windows.h>
#include <shlobj.h>
#include <atomic>
#include <cstdlib>
#include <string>
#include <thread>
namespace izo {
namespace {
std::wstring widen(std::string_view value) {
if (value.empty()) return {};
const int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(),
static_cast<int>(value.size()), nullptr, 0);
if (count <= 0) return {};
std::wstring result(static_cast<std::size_t>(count), L'\0');
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(),
static_cast<int>(value.size()), result.data(), count);
return result;
}
std::string narrow(std::wstring_view value) {
if (value.empty()) return {};
const int count = WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast<int>(value.size()),
nullptr, 0, nullptr, nullptr);
std::string result(static_cast<std::size_t>(count), '\0');
WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), result.data(),
count, nullptr, nullptr);
return result;
}
std::string error_text(const char* operation, DWORD code = GetLastError()) {
wchar_t* buffer = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, code, 0, reinterpret_cast<wchar_t*>(&buffer), 0, nullptr);
std::string result = std::string(operation) + " failed (" + std::to_string(code) + ")";
if (buffer) {
std::wstring detail(buffer);
LocalFree(buffer);
while (!detail.empty() && (detail.back() == L'\r' || detail.back() == L'\n')) detail.pop_back();
result += ": " + narrow(detail);
}
return result;
}
std::filesystem::path shell_folder(REFKNOWNFOLDERID id, std::string* error_message) {
wchar_t* value = nullptr;
const HRESULT result = SHGetKnownFolderPath(id, KF_FLAG_DEFAULT, nullptr, &value);
if (FAILED(result)) {
if (error_message) *error_message = error_text("SHGetKnownFolderPath", result);
return {};
}
std::filesystem::path path(value);
CoTaskMemFree(value);
return path;
}
std::wstring quote(std::wstring value) {
if (value.find_first_of(L" \t\"") == std::wstring::npos) return value;
std::wstring result = L"\"";
std::size_t slashes = 0;
for (wchar_t c : value) {
if (c == L'\\') { ++slashes; continue; }
if (c == L'\"') result.append(slashes * 2 + 1, L'\\');
else result.append(slashes, L'\\');
slashes = 0;
result += c;
}
result.append(slashes * 2, L'\\');
return result + L'\"';
}
std::atomic<crash_callback> crash_handler{nullptr};
std::atomic<void*> crash_user_data{nullptr};
LONG WINAPI unhandled_exception(EXCEPTION_POINTERS* info) {
if (auto callback = crash_handler.load()) {
const std::string reason = "Unhandled exception 0x" +
std::to_string(info->ExceptionRecord->ExceptionCode);
callback(reason.c_str(), crash_user_data.load());
}
return EXCEPTION_CONTINUE_SEARCH;
}
} // namespace
std::filesystem::path get_known_path(known_path path, std::string* error_message) {
switch (path) {
case known_path::app_data: return shell_folder(FOLDERID_RoamingAppData, error_message);
case known_path::local_app_data: return shell_folder(FOLDERID_LocalAppData, error_message);
case known_path::config: return shell_folder(FOLDERID_RoamingAppData, error_message);
case known_path::cache: return shell_folder(FOLDERID_LocalAppData, error_message);
case known_path::documents: return shell_folder(FOLDERID_Documents, error_message);
case known_path::downloads: return shell_folder(FOLDERID_Downloads, error_message);
case known_path::desktop: return shell_folder(FOLDERID_Desktop, error_message);
case known_path::temporary: {
std::wstring value(MAX_PATH + 1, L'\0');
const DWORD count = GetTempPathW(static_cast<DWORD>(value.size()), value.data());
if (!count || count >= value.size()) {
if (error_message) *error_message = error_text("GetTempPathW");
return {};
}
value.resize(count);
return value;
}
case known_path::executable_directory: {
std::wstring value(32768, L'\0');
const DWORD count = GetModuleFileNameW(nullptr, value.data(), static_cast<DWORD>(value.size()));
if (!count || count == value.size()) {
if (error_message) *error_message = error_text("GetModuleFileNameW");
return {};
}
value.resize(count);
return std::filesystem::path(value).parent_path();
}
case known_path::current_directory: {
std::error_code ec;
auto result = std::filesystem::current_path(ec);
if (ec && error_message) *error_message = ec.message();
return result;
}
}
return {};
}
process_result launch_process(const process_options& options) {
if (options.executable.empty()) return {false, 0, "Executable path is required"};
std::wstring command = quote(options.executable.wstring());
for (const auto& argument : options.arguments) command += L' ' + quote(widen(argument));
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
PROCESS_INFORMATION process{};
const auto working_directory = options.working_directory.wstring();
DWORD flags = options.detached ? DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP : 0;
if (!CreateProcessW(options.executable.c_str(), command.data(), nullptr, nullptr, FALSE, flags,
nullptr, working_directory.empty() ? nullptr : working_directory.c_str(),
&startup, &process)) {
return {false, 0, error_text("CreateProcessW")};
}
const auto id = static_cast<std::uint32_t>(process.dwProcessId);
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
return {true, id, {}};
}
dynamic_library::~dynamic_library() { reset(); }
dynamic_library::dynamic_library(dynamic_library&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;
}
dynamic_library& dynamic_library::operator=(dynamic_library&& other) noexcept {
if (this != &other) { reset(); handle_ = other.handle_; other.handle_ = nullptr; }
return *this;
}
void dynamic_library::reset() noexcept {
if (handle_) FreeLibrary(static_cast<HMODULE>(handle_));
handle_ = nullptr;
}
void* dynamic_library::symbol(std::string_view name, std::string* error_message) const {
if (!handle_) {
if (error_message) *error_message = "Dynamic library is not loaded";
return nullptr;
}
std::string terminated(name);
auto result = reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle_), terminated.c_str()));
if (!result && error_message) *error_message = error_text("GetProcAddress");
return result;
}
dynamic_library load_dynamic_library(const std::filesystem::path& path, std::string* error_message) {
auto handle = LoadLibraryW(path.c_str());
if (!handle && error_message) *error_message = error_text("LoadLibraryW");
return dynamic_library(handle);
}
std::optional<std::string> get_environment_variable(std::string_view name) {
const auto key = widen(name);
const DWORD count = GetEnvironmentVariableW(key.c_str(), nullptr, 0);
if (!count) return std::nullopt;
std::wstring value(count, L'\0');
const DWORD written = GetEnvironmentVariableW(key.c_str(), value.data(), count);
value.resize(written);
return narrow(value);
}
bool set_environment_variable(std::string_view name, std::string_view value,
std::string* error_message) {
if (SetEnvironmentVariableW(widen(name).c_str(), widen(value).c_str())) return true;
if (error_message) *error_message = error_text("SetEnvironmentVariableW");
return false;
}
bool unset_environment_variable(std::string_view name, std::string* error_message) {
if (SetEnvironmentVariableW(widen(name).c_str(), nullptr)) return true;
if (error_message) *error_message = error_text("SetEnvironmentVariableW");
return false;
}
system_info get_system_info() {
SYSTEM_INFO native{};
GetNativeSystemInfo(&native);
MEMORYSTATUSEX memory{};
memory.dwLength = sizeof(memory);
GlobalMemoryStatusEx(&memory);
RTL_OSVERSIONINFOW version{};
version.dwOSVersionInfoSize = sizeof(version);
using version_fn = LONG(WINAPI*)(PRTL_OSVERSIONINFOW);
auto module = GetModuleHandleW(L"ntdll.dll");
auto get_version = reinterpret_cast<version_fn>(GetProcAddress(module, "RtlGetVersion"));
if (get_version) get_version(&version);
return {native.dwNumberOfProcessors, memory.ullTotalPhys, memory.ullAvailPhys, "Windows",
std::to_string(version.dwMajorVersion) + '.' + std::to_string(version.dwMinorVersion) +
'.' + std::to_string(version.dwBuildNumber)};
}
void install_crash_handler(crash_callback callback, void* user_data) {
crash_user_data.store(user_data);
crash_handler.store(callback);
SetUnhandledExceptionFilter(callback ? unhandled_exception : nullptr);
}
bool is_debugger_attached() noexcept { return IsDebuggerPresent() != FALSE; }
void debug_break() noexcept { if (is_debugger_attached()) DebugBreak(); }
void debug_output(std::string_view message) {
const auto value = widen(message);
OutputDebugStringW(value.c_str());
}
} // namespace izo