srctree

Robin Linden parent 42f4c8d5 6dbc0766
os: Split into os/{memory,system_info,xdg}

os/BUILD added: 461, removed: 275, total 186
@@ -3,48 +3,120 @@ load("//bzl:copts.bzl", "HASTUR_COPTS")
 
cc_library(
name = "os",
srcs = select({
"@platforms//os:linux": ["linux.cpp"],
"@platforms//os:windows": ["windows.cpp"],
}),
hdrs = ["os.h"],
visibility = ["//visibility:public"],
deps = [
":memory",
":system_info",
":xdg",
],
)
 
cc_library(
name = "windows_setup",
hdrs = ["windows_setup.h"],
)
 
OS_DEPS = select({
"@platforms//os:linux": [],
"@platforms//os:windows": [":windows_setup"],
})
 
OS_LOCAL_DEFINES = select({
"@platforms//os:linux": [],
"@platforms//os:windows": ["WIN32_LEAN_AND_MEAN"],
})
 
cc_library(
name = "memory",
srcs = select({
"@platforms//os:linux": ["memory_linux.cpp"],
"@platforms//os:windows": ["memory_windows.cpp"],
}),
hdrs = ["memory.h"],
copts = HASTUR_COPTS,
linkopts = select({
"@platforms//os:linux": [],
"@platforms//os:windows": [
"-DEFAULTLIB:Kernel32",
"-DEFAULTLIB:Ole32",
],
}),
local_defines = OS_LOCAL_DEFINES,
visibility = ["//visibility:public"],
deps = OS_DEPS,
)
 
cc_test(
name = "memory_test",
size = "small",
srcs = ["memory_test.cpp"],
copts = HASTUR_COPTS,
deps = [
":memory",
"//etest",
],
)
 
cc_library(
name = "system_info",
srcs = select({
"@platforms//os:linux": ["system_info_linux.cpp"],
"@platforms//os:windows": ["system_info_windows.cpp"],
}),
hdrs = ["system_info.h"],
copts = HASTUR_COPTS,
linkopts = select({
"@platforms//os:linux": [],
"@platforms//os:windows": [
"-DEFAULTLIB:Shcore",
"-DEFAULTLIB:Shell32",
"-DEFAULTLIB:User32",
],
}),
local_defines = select({
"@platforms//os:linux": [],
"@platforms//os:windows": ["WIN32_LEAN_AND_MEAN"],
}),
local_defines = OS_LOCAL_DEFINES,
visibility = ["//visibility:public"],
deps = OS_DEPS,
)
 
cc_test(
name = "os_test",
name = "system_info_linux_test",
size = "small",
srcs = ["os_test.cpp"],
copts = HASTUR_COPTS,
deps = [
":os",
"//etest",
],
)
 
cc_test(
name = "linux_test",
size = "small",
srcs = ["linux_test.cpp"],
srcs = ["system_info_linux_test.cpp"],
copts = HASTUR_COPTS,
target_compatible_with = ["@platforms//os:linux"],
deps = [
":os",
":system_info",
"//etest",
],
)
 
cc_library(
name = "xdg",
srcs = select({
"@platforms//os:linux": ["xdg_linux.cpp"],
"@platforms//os:windows": ["xdg_windows.cpp"],
}),
hdrs = ["xdg.h"],
copts = HASTUR_COPTS,
linkopts = select({
"@platforms//os:linux": [],
"@platforms//os:windows": [
"-DEFAULTLIB:Shell32",
"-DEFAULTLIB:Kernel32",
"-DEFAULTLIB:Ole32",
],
}),
local_defines = OS_LOCAL_DEFINES,
visibility = ["//visibility:public"],
deps = OS_DEPS,
)
 
cc_test(
name = "xdg_test",
size = "small",
srcs = ["xdg_test.cpp"],
copts = HASTUR_COPTS,
deps = [
":xdg",
"//etest",
],
)
 
ev/null added: 461, removed: 275, total 186
@@ -1,85 +0,0 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/os.h"
 
#include <sys/mman.h>
 
#include <array>
#include <charconv>
#include <cstdlib>
#include <cstring>
 
using namespace std::literals;
 
namespace os {
 
// This is okay as long as we don't call e.g. setenv(), unsetenv(), or putenv().
// NOLINTBEGIN(concurrency-mt-unsafe)
 
std::vector<std::string> font_paths() {
std::vector<std::string> paths{};
if (char const *xdg_data_home = std::getenv("XDG_DATA_HOME")) {
paths.push_back(xdg_data_home + "/fonts"s);
}
 
if (char const *home = std::getenv("HOME")) {
if (paths.empty()) {
paths.push_back(home + "/.local/share/fonts"s);
}
paths.push_back(home + "/.fonts"s);
}
 
paths.push_back("/usr/share/fonts"s);
paths.push_back("/usr/local/share/fonts"s);
return paths;
}
 
unsigned active_window_scale_factor() {
// Hastur, Qt, Gnome, and Elementary in that order.
// Environment variables from https://wiki.archlinux.org/title/HiDPI#GUI_toolkits
for (auto const *env_var : std::array{"HST_SCALE", "QT_SCALE_FACTOR", "GDK_SCALE", "ELM_SCALE"}) {
if (char const *scale = std::getenv(env_var)) {
int result{};
if (std::from_chars(scale, scale + std::strlen(scale), result).ec == std::errc{}) {
return result;
}
}
}
 
return 1;
}
 
// NOLINTEND(concurrency-mt-unsafe)
 
ExecutableMemory::~ExecutableMemory() {
if (memory_ != nullptr && munmap(memory_, size_) != 0) {
std::abort();
}
}
 
std::optional<ExecutableMemory> ExecutableMemory::allocate_containing(std::span<std::uint8_t const> data) {
if (data.empty()) {
return std::nullopt;
}
 
auto *memory = mmap(nullptr, data.size(), PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (memory == MAP_FAILED) {
return std::nullopt;
}
 
std::memcpy(memory, data.data(), data.size());
 
if (mprotect(memory, data.size(), PROT_EXEC) != 0) {
if (munmap(memory, data.size()) != 0) {
std::abort();
}
 
return std::nullopt;
}
 
return ExecutableMemory{memory, data.size()};
}
 
} // namespace os
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef OS_MEMORY_H_
#define OS_MEMORY_H_
 
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <utility>
 
namespace os {
 
class ExecutableMemory {
public:
static std::optional<ExecutableMemory> allocate_containing(std::span<std::uint8_t const>);
~ExecutableMemory();
 
ExecutableMemory(ExecutableMemory const &) = delete;
ExecutableMemory &operator=(ExecutableMemory const &) = delete;
 
ExecutableMemory(ExecutableMemory &&other) noexcept {
std::swap(memory_, other.memory_);
std::swap(size_, other.size_);
}
 
ExecutableMemory &operator=(ExecutableMemory &&other) noexcept {
std::swap(memory_, other.memory_);
std::swap(size_, other.size_);
return *this;
}
 
void *ptr() { return memory_; }
 
private:
ExecutableMemory(void *memory, std::size_t size) : memory_{memory}, size_{size} {}
void *memory_{nullptr};
std::size_t size_{};
};
 
} // namespace os
 
#endif
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/memory.h"
 
#include <sys/mman.h>
 
#include <cstdlib>
#include <cstring>
 
namespace os {
 
ExecutableMemory::~ExecutableMemory() {
if (memory_ != nullptr && munmap(memory_, size_) != 0) {
std::abort();
}
}
 
std::optional<ExecutableMemory> ExecutableMemory::allocate_containing(std::span<std::uint8_t const> data) {
if (data.empty()) {
return std::nullopt;
}
 
auto *memory = mmap(nullptr, data.size(), PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (memory == MAP_FAILED) {
return std::nullopt;
}
 
std::memcpy(memory, data.data(), data.size());
 
if (mprotect(memory, data.size(), PROT_EXEC) != 0) {
if (munmap(memory, data.size()) != 0) {
std::abort();
}
 
return std::nullopt;
}
 
return ExecutableMemory{memory, data.size()};
}
 
} // namespace os
 
os/os_test.cpp added: 461, removed: 275, total 186
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/os.h"
#include "os/memory.h"
 
#include "etest/etest2.h"
 
@@ -14,11 +14,7 @@
#endif
 
int main() {
etest::Suite s{"os"};
s.add_test("font_paths", [](etest::IActions &a) {
auto font_paths = os::font_paths();
a.expect(!font_paths.empty());
});
etest::Suite s{"os/memory"};
 
s.add_test("ExecutableMemory, normal use", [](etest::IActions &a) {
// MOV EAX, 42 ; b8 2a 0 0 0
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/memory.h"
 
#include "os/windows_setup.h"
 
#include <Memoryapi.h>
 
#include <cstdlib>
#include <cstring>
 
// Kernel32
namespace os {
 
// VirtualFree has a weird 2nd argument:
// [in] dwSize - The size of the region of memory to be freed, in bytes.
// If the dwFreeType parameter is MEM_RELEASE, this parameter must be 0 (zero).
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
ExecutableMemory::~ExecutableMemory() {
if (memory_ != nullptr && !VirtualFree(memory_, 0, MEM_RELEASE)) {
std::abort();
}
}
 
std::optional<ExecutableMemory> ExecutableMemory::allocate_containing(std::span<std::uint8_t const> data) {
if (data.empty()) {
return std::nullopt;
}
 
auto *memory = VirtualAlloc(nullptr, data.size(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (memory == nullptr) {
return std::nullopt;
}
 
std::memcpy(memory, data.data(), data.size());
 
DWORD old_protect{};
if (!VirtualProtect(memory, data.size(), PAGE_EXECUTE, &old_protect)
|| !FlushInstructionCache(GetCurrentProcess(), memory, data.size())) {
if (!VirtualFree(memory, 0, MEM_RELEASE)) {
std::abort();
}
 
return std::nullopt;
}
 
return ExecutableMemory{memory, data.size()};
}
 
} // namespace os
 
os/os.h added: 461, removed: 275, total 186
@@ -5,46 +5,8 @@
#ifndef OS_OS_H_
#define OS_OS_H_
 
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <vector>
 
namespace os {
 
std::vector<std::string> font_paths();
unsigned active_window_scale_factor();
 
class ExecutableMemory {
public:
static std::optional<ExecutableMemory> allocate_containing(std::span<std::uint8_t const>);
~ExecutableMemory();
 
ExecutableMemory(ExecutableMemory const &) = delete;
ExecutableMemory &operator=(ExecutableMemory const &) = delete;
 
ExecutableMemory(ExecutableMemory &&other) noexcept {
std::swap(memory_, other.memory_);
std::swap(size_, other.size_);
}
 
ExecutableMemory &operator=(ExecutableMemory &&other) noexcept {
std::swap(memory_, other.memory_);
std::swap(size_, other.size_);
return *this;
}
 
void *ptr() { return memory_; }
 
private:
ExecutableMemory(void *memory, std::size_t size) : memory_{memory}, size_{size} {}
void *memory_{nullptr};
std::size_t size_{};
};
 
} // namespace os
#include "os/memory.h"
#include "os/system_info.h"
#include "os/xdg.h"
 
#endif
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2022-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef OS_SYSTEM_INFO_H_
#define OS_SYSTEM_INFO_H_
 
namespace os {
unsigned active_window_scale_factor();
} // namespace os
 
#endif
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/system_info.h"
 
#include <array>
#include <charconv>
#include <cstdlib>
#include <cstring>
 
namespace os {
 
// This is okay as long as we don't call e.g. setenv(), unsetenv(), or putenv().
// NOLINTBEGIN(concurrency-mt-unsafe)
 
unsigned active_window_scale_factor() {
// Hastur, Qt, Gnome, and Elementary in that order.
// Environment variables from https://wiki.archlinux.org/title/HiDPI#GUI_toolkits
for (auto const *env_var : std::array{"HST_SCALE", "QT_SCALE_FACTOR", "GDK_SCALE", "ELM_SCALE"}) {
if (char const *scale = std::getenv(env_var)) {
int result{};
if (std::from_chars(scale, scale + std::strlen(scale), result).ec == std::errc{}) {
return result;
}
}
}
 
return 1;
}
 
// NOLINTEND(concurrency-mt-unsafe)
 
} // namespace os
 
os/linux_test.cpp added: 461, removed: 275, total 186
@@ -9,7 +9,7 @@
// NOLINTNEXTLINE: The only option is to mess with this reserved identifier.
#define _POSIX_C_SOURCE 200112L
 
#include "os/os.h"
#include "os/system_info.h"
 
#include "etest/etest2.h"
 
@@ -20,7 +20,7 @@
// NOLINTBEGIN(concurrency-mt-unsafe): No threads here.
 
int main() {
etest::Suite s{"os/linux"};
etest::Suite s{"os/linux::system_info"};
 
// Ensure that the system's environment doesn't affect the test result.
unsetenv("HST_SCALE");
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2022-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/system_info.h"
 
#include "os/windows_setup.h"
 
#include <shellscalingapi.h>
 
#include <charconv>
#include <cmath>
#include <cstring>
 
namespace os {
 
unsigned active_window_scale_factor() {
// NOLINTNEXTLINE(concurrency-mt-unsafe): We never modify the environment variables.
if (auto const *env_var = std::getenv("HST_SCALE")) {
if (int result{}; std::from_chars(env_var, env_var + std::strlen(env_var), result).ec == std::errc{}) {
return result;
}
}
 
DEVICE_SCALE_FACTOR scale_factor{};
if (GetScaleFactorForMonitor(MonitorFromWindow(GetActiveWindow(), MONITOR_DEFAULTTONEAREST), &scale_factor)
!= S_OK) {
return 1;
}
 
return static_cast<unsigned>(std::lround(static_cast<float>(scale_factor) / 100.f));
}
 
} // namespace os
 
ev/null added: 461, removed: 275, total 186
@@ -1,99 +0,0 @@
// SPDX-FileCopyrightText: 2021-2022 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/os.h"
 
#include <WinSdkVer.h>
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#ifdef WINVER
#undef WINVER
#endif
// GetScaleFactorForMonitor was introduced in Windows 8.1.
#define _WIN32_WINNT _WIN32_WINNT_WINBLUE
#define WINVER _WIN32_WINNT
 
// Must be included first because Windows headers don't include what they use.
#include <Windows.h>
 
#include <Knownfolders.h>
#include <Memoryapi.h>
#include <Objbase.h>
#include <Shlobj.h>
#include <shellscalingapi.h>
 
#include <charconv>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cwchar>
 
namespace os {
 
std::vector<std::string> font_paths() {
PWSTR bad_font_path{nullptr};
SHGetKnownFolderPath(FOLDERID_Fonts, 0, nullptr, &bad_font_path);
auto bad_font_path_len = static_cast<int>(std::wcslen(bad_font_path));
auto chars_needed = WideCharToMultiByte(CP_UTF8, 0, bad_font_path, bad_font_path_len, nullptr, 0, nullptr, nullptr);
std::string font_path;
font_path.resize(chars_needed);
WideCharToMultiByte(CP_UTF8, 0, bad_font_path, bad_font_path_len, font_path.data(), chars_needed, nullptr, nullptr);
CoTaskMemFree(bad_font_path);
return {font_path};
}
 
unsigned active_window_scale_factor() {
// NOLINTNEXTLINE(concurrency-mt-unsafe): We never modify the environment variables.
if (auto const *env_var = std::getenv("HST_SCALE")) {
if (int result{}; std::from_chars(env_var, env_var + std::strlen(env_var), result).ec == std::errc{}) {
return result;
}
}
 
DEVICE_SCALE_FACTOR scale_factor{};
if (GetScaleFactorForMonitor(MonitorFromWindow(GetActiveWindow(), MONITOR_DEFAULTTONEAREST), &scale_factor)
!= S_OK) {
return 1;
}
 
return static_cast<unsigned>(std::lround(static_cast<float>(scale_factor) / 100.f));
}
 
// VirtualFree has a weird 2nd argument:
// [in] dwSize - The size of the region of memory to be freed, in bytes.
// If the dwFreeType parameter is MEM_RELEASE, this parameter must be 0 (zero).
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
ExecutableMemory::~ExecutableMemory() {
if (memory_ != nullptr && !VirtualFree(memory_, 0, MEM_RELEASE)) {
std::abort();
}
}
 
std::optional<ExecutableMemory> ExecutableMemory::allocate_containing(std::span<std::uint8_t const> data) {
if (data.empty()) {
return std::nullopt;
}
 
auto *memory = VirtualAlloc(nullptr, data.size(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (memory == nullptr) {
return std::nullopt;
}
 
std::memcpy(memory, data.data(), data.size());
 
DWORD old_protect{};
if (!VirtualProtect(memory, data.size(), PAGE_EXECUTE, &old_protect)
|| !FlushInstructionCache(GetCurrentProcess(), memory, data.size())) {
if (!VirtualFree(memory, 0, MEM_RELEASE)) {
std::abort();
}
 
return std::nullopt;
}
 
return ExecutableMemory{memory, data.size()};
}
 
} // namespace os
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef OS_WINDOWS_SETUP_H_
#define OS_WINDOWS_SETUP_H_
 
#include <WinSdkVer.h>
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#ifdef WINVER
#undef WINVER
#endif
#define _WIN32_WINNT _WIN32_WINNT_WINBLUE
#define WINVER _WIN32_WINNT
 
// Must be included first because Windows headers don't include what they use.
#include <Windows.h>
 
#endif
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef OS_XDG_H_
#define OS_XDG_H_
 
#include <string>
#include <vector>
 
// TODO(robinlinden): We should probably create a more fully-featured top-level xdg library.
namespace os {
std::vector<std::string> font_paths();
} // namespace os
 
#endif
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/xdg.h"
 
#include <cstdlib>
 
using namespace std::literals;
 
namespace os {
 
// This is okay as long as we don't call e.g. setenv(), unsetenv(), or putenv().
// NOLINTBEGIN(concurrency-mt-unsafe)
 
std::vector<std::string> font_paths() {
std::vector<std::string> paths{};
if (char const *xdg_data_home = std::getenv("XDG_DATA_HOME")) {
paths.push_back(xdg_data_home + "/fonts"s);
}
 
if (char const *home = std::getenv("HOME")) {
if (paths.empty()) {
paths.push_back(home + "/.local/share/fonts"s);
}
paths.push_back(home + "/.fonts"s);
}
 
paths.push_back("/usr/share/fonts"s);
paths.push_back("/usr/local/share/fonts"s);
return paths;
}
 
// NOLINTEND(concurrency-mt-unsafe)
 
} // namespace os
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/xdg.h"
 
#include "etest/etest2.h"
 
int main() {
etest::Suite s{"os::xdg"};
 
s.add_test("font_paths", [](etest::IActions &a) {
auto font_paths = os::font_paths();
a.expect(!font_paths.empty());
});
 
return s.run();
}
 
filename was Deleted added: 461, removed: 275, total 186
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/xdg.h"
 
#include "os/windows_setup.h"
 
#include <Knownfolders.h>
#include <Objbase.h>
#include <Shlobj.h>
 
#include <cwchar>
 
namespace os {
 
std::vector<std::string> font_paths() {
PWSTR bad_font_path{nullptr};
SHGetKnownFolderPath(FOLDERID_Fonts, 0, nullptr, &bad_font_path);
auto bad_font_path_len = static_cast<int>(std::wcslen(bad_font_path));
auto chars_needed = WideCharToMultiByte(CP_UTF8, 0, bad_font_path, bad_font_path_len, nullptr, 0, nullptr, nullptr);
std::string font_path;
font_path.resize(chars_needed);
WideCharToMultiByte(CP_UTF8, 0, bad_font_path, bad_font_path_len, font_path.data(), chars_needed, nullptr, nullptr);
CoTaskMemFree(bad_font_path);
return {font_path};
}
 
} // namespace os