feat(platform): add core Windows system utilities
This commit is contained in:
@@ -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
233
src/platform_windows.cpp
Normal 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
|
||||
Reference in New Issue
Block a user