some more updates and stuff

This commit is contained in:
Mars 2025-04-21 01:42:24 -04:00
parent 96c6e79780
commit 203d56e06b
Signed by: pupbrained
GPG key ID: 874E22DF2F9DFCB5
17 changed files with 231 additions and 255 deletions

12
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1744536153, "lastModified": 1744868846,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", "narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", "rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -59,11 +59,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1743748085, "lastModified": 1744961264,
"narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=", "narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d", "rev": "8d404a69efe76146368885110f29a2ca3700bee6",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -18,10 +18,7 @@
system: let system: let
pkgs = import nixpkgs {inherit system;}; pkgs = import nixpkgs {inherit system;};
llvmPackages = with pkgs; llvmPackages = pkgs.llvmPackages_20;
if hostPlatform.isLinux
then llvmPackages_20
else llvmPackages_19;
stdenv = with pkgs; stdenv = with pkgs;
( (
@ -38,18 +35,20 @@
++ (with pkgsStatic; [ ++ (with pkgsStatic; [
curl curl
ftxui ftxui
libiconv
sqlitecpp
tomlplusplus tomlplusplus
]) ])
++ darwinPkgs
++ linuxPkgs; ++ linuxPkgs;
darwinPkgs = nixpkgs.lib.optionals stdenv.isDarwin (with pkgs.pkgsStatic; [libiconv]);
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs; linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
[ [
valgrind valgrind
] ]
++ (with pkgsStatic; [ ++ (with pkgsStatic; [
dbus dbus
sqlitecpp
xorg.libX11 xorg.libX11
wayland wayland
])); ]));

View file

@ -58,7 +58,7 @@ if host_system == 'darwin'
objcpp = meson.get_compiler('objcpp') objcpp = meson.get_compiler('objcpp')
objcpp_flags = common_warning_flags + [ objcpp_flags = common_warning_flags + [
'-Wno-switch-default', '-Wno-switch-default',
'-std=c++2b', '-std=c++23',
'-fvisibility=hidden', '-fvisibility=hidden',
'-fvisibility-inlines-hidden', '-fvisibility-inlines-hidden',
] ]

View file

@ -14,24 +14,19 @@ namespace {
std::vector<fs::path> possiblePaths; std::vector<fs::path> possiblePaths;
#ifdef _WIN32 #ifdef _WIN32
// Windows possible paths in order of preference
if (auto result = GetEnv("LOCALAPPDATA"); result) if (auto result = GetEnv("LOCALAPPDATA"); result)
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
if (auto result = GetEnv("USERPROFILE"); result) { if (auto result = GetEnv("USERPROFILE"); result) {
// Support for .config style on Windows (some users prefer this)
possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml");
// Traditional Windows location alternative
possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml");
} }
if (auto result = GetEnv("APPDATA"); result) if (auto result = GetEnv("APPDATA"); result)
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
// Portable app option - config in same directory as executable
possiblePaths.push_back(fs::path(".") / "config.toml"); possiblePaths.push_back(fs::path(".") / "config.toml");
#else #else
// Unix/Linux paths in order of preference
if (auto result = GetEnv("XDG_CONFIG_HOME"); result) if (auto result = GetEnv("XDG_CONFIG_HOME"); result)
possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml");
@ -40,18 +35,14 @@ namespace {
possiblePaths.emplace_back(fs::path(*result) / ".draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / ".draconis++" / "config.toml");
} }
// System-wide config
possiblePaths.emplace_back("/etc/draconis++/config.toml"); possiblePaths.emplace_back("/etc/draconis++/config.toml");
#endif #endif
// Check if any of these configs already exist
for (const auto& path : possiblePaths) for (const auto& path : possiblePaths)
if (std::error_code errc; exists(path, errc) && !errc) if (std::error_code errc; exists(path, errc) && !errc)
return path; return path;
// If no config exists yet, return the default (first in priority)
if (!possiblePaths.empty()) { if (!possiblePaths.empty()) {
// Create directory structure for the default path
const fs::path defaultDir = possiblePaths[0].parent_path(); const fs::path defaultDir = possiblePaths[0].parent_path();
if (std::error_code errc; !exists(defaultDir, errc) && !errc) { if (std::error_code errc; !exists(defaultDir, errc) && !errc) {
@ -63,13 +54,11 @@ namespace {
return possiblePaths[0]; return possiblePaths[0];
} }
// Ultimate fallback if somehow we have no paths
throw std::runtime_error("Could not determine a valid config path"); throw std::runtime_error("Could not determine a valid config path");
} }
fn CreateDefaultConfig(const fs::path& configPath) -> bool { fn CreateDefaultConfig(const fs::path& configPath) -> bool {
try { try {
// Ensure the directory exists
std::error_code errc; std::error_code errc;
create_directories(configPath.parent_path(), errc); create_directories(configPath.parent_path(), errc);
if (errc) { if (errc) {
@ -77,11 +66,9 @@ namespace {
return false; return false;
} }
// Create a default TOML document
toml::table root; toml::table root;
// Get default username for General section String defaultName;
std::string defaultName;
#ifdef _WIN32 #ifdef _WIN32
std::array<char, 256> username; std::array<char, 256> username;
DWORD size = sizeof(username); DWORD size = sizeof(username);
@ -95,15 +82,12 @@ namespace {
defaultName = "User"; defaultName = "User";
#endif #endif
// General section
toml::table* general = root.insert("general", toml::table {}).first->second.as_table(); toml::table* general = root.insert("general", toml::table {}).first->second.as_table();
general->insert("name", defaultName); general->insert("name", defaultName);
// Now Playing section
toml::table* nowPlaying = root.insert("now_playing", toml::table {}).first->second.as_table(); toml::table* nowPlaying = root.insert("now_playing", toml::table {}).first->second.as_table();
nowPlaying->insert("enabled", false); nowPlaying->insert("enabled", false);
// Weather section
toml::table* weather = root.insert("weather", toml::table {}).first->second.as_table(); toml::table* weather = root.insert("weather", toml::table {}).first->second.as_table();
weather->insert("enabled", false); weather->insert("enabled", false);
weather->insert("show_town_name", false); weather->insert("show_town_name", false);
@ -111,7 +95,6 @@ namespace {
weather->insert("units", "metric"); weather->insert("units", "metric");
weather->insert("location", "London"); weather->insert("location", "London");
// Write to file (using a stringstream for comments + TOML)
std::ofstream file(configPath); std::ofstream file(configPath);
if (!file) { if (!file) {
ERROR_LOG("Failed to open config file for writing: {}", configPath.string()); ERROR_LOG("Failed to open config file for writing: {}", configPath.string());
@ -154,18 +137,15 @@ fn Config::getInstance() -> Config {
try { try {
const fs::path configPath = GetConfigPath(); const fs::path configPath = GetConfigPath();
// Check if the config file exists
if (!exists(configPath)) { if (!exists(configPath)) {
INFO_LOG("Config file not found, creating defaults at {}", configPath.string()); INFO_LOG("Config file not found, creating defaults at {}", configPath.string());
// Create default config
if (!CreateDefaultConfig(configPath)) { if (!CreateDefaultConfig(configPath)) {
WARN_LOG("Failed to create default config, using in-memory defaults"); WARN_LOG("Failed to create default config, using in-memory defaults");
return {}; return {};
} }
} }
// Now we should have a config file to read
const toml::parse_result config = toml::parse_file(configPath.string()); const toml::parse_result config = toml::parse_file(configPath.string());
return fromToml(config); return fromToml(config);
} catch (const std::exception& e) { } catch (const std::exception& e) {

View file

@ -3,8 +3,8 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#else #else
#include <pwd.h> // For getpwuid #include <pwd.h>
#include <unistd.h> // For getuid #include <unistd.h>
#endif #endif
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
@ -12,10 +12,10 @@
#include "src/util/macros.h" #include "src/util/macros.h"
#include "weather.h" #include "weather.h"
using Location = std::variant<string, Coords>; using Location = std::variant<String, Coords>;
struct General { struct General {
string name = []() -> string { String name = []() -> String {
#ifdef _WIN32 #ifdef _WIN32
std::array<char, 256> username; std::array<char, 256> username;
DWORD size = sizeof(username); DWORD size = sizeof(username);
@ -34,7 +34,7 @@ struct General {
static fn fromToml(const toml::table& tbl) -> General { static fn fromToml(const toml::table& tbl) -> General {
General gen; General gen;
if (const std::optional<string> name = tbl["name"].value<string>()) if (const std::optional<String> name = tbl["name"].value<String>())
gen.name = *name; gen.name = *name;
return gen; return gen;
@ -55,15 +55,15 @@ struct Weather {
bool enabled = false; bool enabled = false;
bool show_town_name = false; bool show_town_name = false;
Location location; Location location;
string api_key; String api_key;
string units; String units;
static fn fromToml(const toml::table& tbl) -> Weather { static fn fromToml(const toml::table& tbl) -> Weather {
Weather weather; Weather weather;
weather.enabled = tbl["enabled"].value_or<bool>(false); weather.enabled = tbl["enabled"].value_or<bool>(false);
if (auto apiKey = tbl["api_key"].value<string>()) { if (auto apiKey = tbl["api_key"].value<String>()) {
const string& keyVal = apiKey.value(); const String& keyVal = apiKey.value();
if (keyVal.empty()) if (keyVal.empty())
weather.enabled = false; weather.enabled = false;
@ -77,11 +77,11 @@ struct Weather {
return weather; return weather;
weather.show_town_name = tbl["show_town_name"].value_or<bool>(false); weather.show_town_name = tbl["show_town_name"].value_or<bool>(false);
weather.units = tbl["units"].value<string>().value_or("metric"); weather.units = tbl["units"].value<String>().value_or("metric");
if (const toml::node_view<const toml::node> location = tbl["location"]) { if (const toml::node_view<const toml::node> location = tbl["location"]) {
if (location.is_string()) { if (location.is_string()) {
weather.location = location.value<string>().value(); weather.location = location.value<String>().value();
} else if (location.is_table()) { } else if (location.is_table()) {
const auto* coord = location.as_table(); const auto* coord = location.as_table();
Coords coords; Coords coords;

View file

@ -15,7 +15,7 @@ using namespace std::string_literals;
namespace { namespace {
constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false }; constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false };
fn GetCachePath() -> std::expected<fs::path, string> { fn GetCachePath() -> std::expected<fs::path, String> {
std::error_code errc; std::error_code errc;
fs::path cachePath = fs::temp_directory_path(errc); fs::path cachePath = fs::temp_directory_path(errc);
@ -26,8 +26,8 @@ namespace {
return cachePath; return cachePath;
} }
fn ReadCacheFromFile() -> std::expected<WeatherOutput, string> { fn ReadCacheFromFile() -> std::expected<WeatherOutput, String> {
std::expected<fs::path, string> cachePath = GetCachePath(); std::expected<fs::path, String> cachePath = GetCachePath();
if (!cachePath) if (!cachePath)
return std::unexpected(cachePath.error()); return std::unexpected(cachePath.error());
@ -40,7 +40,7 @@ namespace {
DEBUG_LOG("Reading from cache file..."); DEBUG_LOG("Reading from cache file...");
try { try {
const string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
WeatherOutput result; WeatherOutput result;
if (const glz::error_ctx errc = glz::read<glaze_opts>(result, content); errc.ec != glz::error_code::none) if (const glz::error_ctx errc = glz::read<glaze_opts>(result, content); errc.ec != glz::error_code::none)
@ -51,8 +51,8 @@ namespace {
} catch (const std::exception& e) { return std::unexpected("Error reading cache: "s + e.what()); } } catch (const std::exception& e) { return std::unexpected("Error reading cache: "s + e.what()); }
} }
fn WriteCacheToFile(const WeatherOutput& data) -> std::expected<void, string> { fn WriteCacheToFile(const WeatherOutput& data) -> std::expected<void, String> {
std::expected<fs::path, string> cachePath = GetCachePath(); std::expected<fs::path, String> cachePath = GetCachePath();
if (!cachePath) if (!cachePath)
return std::unexpected(cachePath.error()); return std::unexpected(cachePath.error());
@ -67,7 +67,7 @@ namespace {
if (!ofs.is_open()) if (!ofs.is_open())
return std::unexpected("Failed to open temp file: " + tempPath.string()); return std::unexpected("Failed to open temp file: " + tempPath.string());
string jsonStr; String jsonStr;
if (const glz::error_ctx errc = glz::write_json(data, jsonStr); errc.ec != glz::error_code::none) if (const glz::error_ctx errc = glz::write_json(data, jsonStr); errc.ec != glz::error_code::none)
return std::unexpected("JSON serialization error: " + glz::format_error(errc, jsonStr)); return std::unexpected("JSON serialization error: " + glz::format_error(errc, jsonStr));
@ -89,16 +89,16 @@ namespace {
} catch (const std::exception& e) { return std::unexpected("File operation error: "s + e.what()); } } catch (const std::exception& e) { return std::unexpected("File operation error: "s + e.what()); }
} }
fn WriteCallback(void* contents, const size_t size, const size_t nmemb, string* str) -> size_t { fn WriteCallback(void* contents, const size_t size, const size_t nmemb, String* str) -> size_t {
const size_t totalSize = size * nmemb; const size_t totalSize = size * nmemb;
str->append(static_cast<char*>(contents), totalSize); str->append(static_cast<char*>(contents), totalSize);
return totalSize; return totalSize;
} }
fn MakeApiRequest(const string& url) -> std::expected<WeatherOutput, string> { fn MakeApiRequest(const String& url) -> std::expected<WeatherOutput, String> {
DEBUG_LOG("Making API request to URL: {}", url); DEBUG_LOG("Making API request to URL: {}", url);
CURL* curl = curl_easy_init(); CURL* curl = curl_easy_init();
string responseBuffer; String responseBuffer;
if (!curl) if (!curl)
return std::unexpected("Failed to initialize cURL"); return std::unexpected("Failed to initialize cURL");
@ -127,7 +127,7 @@ namespace {
fn Weather::getWeatherInfo() const -> WeatherOutput { fn Weather::getWeatherInfo() const -> WeatherOutput {
using namespace std::chrono; using namespace std::chrono;
if (std::expected<WeatherOutput, string> data = ReadCacheFromFile()) { if (std::expected<WeatherOutput, String> data = ReadCacheFromFile()) {
const WeatherOutput& dataVal = *data; const WeatherOutput& dataVal = *data;
if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt)); if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
@ -141,24 +141,24 @@ fn Weather::getWeatherInfo() const -> WeatherOutput {
DEBUG_LOG("Cache error: {}", data.error()); DEBUG_LOG("Cache error: {}", data.error());
} }
fn handleApiResult = [](const std::expected<WeatherOutput, string>& result) -> WeatherOutput { fn handleApiResult = [](const std::expected<WeatherOutput, String>& result) -> WeatherOutput {
if (!result) { if (!result) {
ERROR_LOG("API request failed: {}", result.error()); ERROR_LOG("API request failed: {}", result.error());
return WeatherOutput {}; return WeatherOutput {};
} }
if (std::expected<void, string> writeResult = WriteCacheToFile(*result); !writeResult) if (std::expected<void, String> writeResult = WriteCacheToFile(*result); !writeResult)
ERROR_LOG("Failed to write cache: {}", writeResult.error()); ERROR_LOG("Failed to write cache: {}", writeResult.error());
return *result; return *result;
}; };
if (std::holds_alternative<string>(location)) { if (std::holds_alternative<String>(location)) {
const auto& city = std::get<string>(location); const auto& city = std::get<String>(location);
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<int>(city.length())); char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<int>(city.length()));
DEBUG_LOG("Requesting city: {}", escaped); DEBUG_LOG("Requesting city: {}", escaped);
const string apiUrl = const String apiUrl =
std::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, api_key, units); std::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, api_key, units);
curl_free(escaped); curl_free(escaped);
@ -168,7 +168,7 @@ fn Weather::getWeatherInfo() const -> WeatherOutput {
const auto& [lat, lon] = std::get<Coords>(location); const auto& [lat, lon] = std::get<Coords>(location);
DEBUG_LOG("Requesting coordinates: lat={:.3f}, lon={:.3f}", lat, lon); DEBUG_LOG("Requesting coordinates: lat={:.3f}, lon={:.3f}", lat, lon);
const string apiUrl = std::format( const String apiUrl = std::format(
"https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, api_key, units "https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, api_key, units
); );

View file

@ -6,7 +6,7 @@
// NOLINTBEGIN(readability-identifier-naming) // NOLINTBEGIN(readability-identifier-naming)
struct Condition { struct Condition {
string description; String description;
struct glaze { struct glaze {
using T = Condition; using T = Condition;
@ -30,7 +30,7 @@ struct Coords {
struct WeatherOutput { struct WeatherOutput {
Main main; Main main;
string name; String name;
std::vector<Condition> weather; std::vector<Condition> weather;
usize dt; usize dt;

View file

@ -101,19 +101,19 @@ namespace ui {
namespace { namespace {
template <typename T, typename E, typename ValueFunc, typename ErrorFunc> template <typename T, typename E, typename ValueFunc, typename ErrorFunc>
auto expected_visit(const std::expected<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) { fn expected_visit(const std::expected<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) {
if (exp.has_value()) if (exp.has_value())
return value_func(*exp); return value_func(*exp);
return error_func(exp.error()); return error_func(exp.error());
} }
fn GetDate() -> string { fn GetDate() -> String {
using namespace std::chrono; using namespace std::chrono;
const year_month_day ymd = year_month_day { floor<days>(system_clock::now()) }; const year_month_day ymd = year_month_day { floor<days>(system_clock::now()) };
string month = std::format("{:%B}", ymd); String month = std::format("{:%B}", ymd);
u32 day = static_cast<u32>(ymd.day()); u32 day = static_cast<u32>(ymd.day());
@ -129,18 +129,18 @@ namespace {
} }
struct SystemData { struct SystemData {
std::string date; String date;
std::string host; String host;
std::string kernel_version; String kernel_version;
std::expected<string, string> os_version; std::expected<String, String> os_version;
std::expected<u64, std::string> mem_info; std::expected<u64, String> mem_info;
std::optional<string> desktop_environment; std::optional<String> desktop_environment;
std::string window_manager; String window_manager;
std::optional<std::expected<string, NowPlayingError>> now_playing; std::optional<std::expected<String, NowPlayingError>> now_playing;
std::optional<WeatherOutput> weather_info; std::optional<WeatherOutput> weather_info;
u64 disk_used; u64 disk_used;
u64 disk_total; u64 disk_total;
std::string shell; String shell;
static fn fetchSystemData(const Config& config) -> SystemData { static fn fetchSystemData(const Config& config) -> SystemData {
SystemData data; SystemData data;
@ -164,7 +164,7 @@ namespace {
// Conditional tasks // Conditional tasks
std::future<WeatherOutput> weather; std::future<WeatherOutput> weather;
std::future<std::expected<string, NowPlayingError>> nowPlaying; std::future<std::expected<String, NowPlayingError>> nowPlaying;
if (config.weather.enabled) if (config.weather.enabled)
weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); }); weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); });
@ -200,7 +200,7 @@ namespace {
fn SystemInfoBox(const Config& config, const SystemData& data) -> Element { fn SystemInfoBox(const Config& config, const SystemData& data) -> Element {
// Fetch data // Fetch data
const string& name = config.general.name; const String& name = config.general.name;
const Weather weather = config.weather; const Weather weather = config.weather;
const bool nowPlayingEnabled = config.now_playing.enabled; const bool nowPlayingEnabled = config.now_playing.enabled;
@ -209,23 +209,27 @@ namespace {
Elements content; Elements content;
content.push_back(text(string(userIcon) + "Hello " + name + "! ") | bold | color(Color::Cyan)); content.push_back(text(String(userIcon) + "Hello " + name + "! ") | bold | color(Color::Cyan));
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
content.push_back(hbox({ content.push_back(hbox(
text(string(paletteIcon)) | color(ui::DEFAULT_THEME.icon), {
CreateColorCircles(), text(String(paletteIcon)) | color(ui::DEFAULT_THEME.icon),
})); CreateColorCircles(),
}
));
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
// Helper function for aligned rows // Helper function for aligned rows
fn createRow = [&](const auto& icon, const auto& label, const auto& value) { fn createRow = [&](const auto& icon, const auto& label, const auto& value) {
return hbox({ return hbox(
text(string(icon)) | color(ui::DEFAULT_THEME.icon), {
text(string(static_cast<const char*>(label))) | color(ui::DEFAULT_THEME.label), text(String(icon)) | color(ui::DEFAULT_THEME.icon),
filler(), text(String(static_cast<const char*>(label))) | color(ui::DEFAULT_THEME.label),
text(string(value)) | color(ui::DEFAULT_THEME.value), filler(),
text(" "), text(String(value)) | color(ui::DEFAULT_THEME.value),
}); text(" "),
}
);
}; };
// System info rows // System info rows
@ -236,31 +240,39 @@ namespace {
const WeatherOutput& weatherInfo = data.weather_info.value(); const WeatherOutput& weatherInfo = data.weather_info.value();
if (weather.show_town_name) if (weather.show_town_name)
content.push_back(hbox({ content.push_back(hbox(
text(string(weatherIcon)) | color(ui::DEFAULT_THEME.icon), {
text("Weather") | color(ui::DEFAULT_THEME.label), text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
filler(), text("Weather") | color(ui::DEFAULT_THEME.label),
filler(),
hbox({ hbox(
text(std::format("{}°F ", std::lround(weatherInfo.main.temp))), {
text("in "), text(std::format("{}°F ", std::lround(weatherInfo.main.temp))),
text(weatherInfo.name), text("in "),
text(" "), text(weatherInfo.name),
}) | text(" "),
color(ui::DEFAULT_THEME.value), }
})); ) |
color(ui::DEFAULT_THEME.value),
}
));
else else
content.push_back(hbox({ content.push_back(hbox(
text(string(weatherIcon)) | color(ui::DEFAULT_THEME.icon), {
text("Weather") | color(ui::DEFAULT_THEME.label), text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
filler(), text("Weather") | color(ui::DEFAULT_THEME.label),
filler(),
hbox({ hbox(
text(std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), {
text(" "), text(std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)),
}) | text(" "),
color(ui::DEFAULT_THEME.value), }
})); ) |
color(ui::DEFAULT_THEME.value),
}
));
} }
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
@ -273,14 +285,14 @@ namespace {
expected_visit( expected_visit(
data.os_version, data.os_version,
[&](const string& version) { content.push_back(createRow(string(osIcon), "OS", version)); }, [&](const String& version) { content.push_back(createRow(String(osIcon), "OS", version)); },
[](const string& error) { ERROR_LOG("Failed to get OS version: {}", error); } [](const String& error) { ERROR_LOG("Failed to get OS version: {}", error); }
); );
expected_visit( expected_visit(
data.mem_info, data.mem_info,
[&](const u64& mem) { content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { mem }))); }, [&](const u64& mem) { content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { mem }))); },
[](const string& error) { ERROR_LOG("Failed to get memory info: {}", error); } [](const String& error) { ERROR_LOG("Failed to get memory info: {}", error); }
); );
// Add Disk usage row // Add Disk usage row
@ -300,19 +312,21 @@ namespace {
// Now Playing row // Now Playing row
if (nowPlayingEnabled && data.now_playing.has_value()) { if (nowPlayingEnabled && data.now_playing.has_value()) {
if (const std::expected<string, NowPlayingError>& nowPlayingResult = *data.now_playing; if (const std::expected<String, NowPlayingError>& nowPlayingResult = *data.now_playing;
nowPlayingResult.has_value()) { nowPlayingResult.has_value()) {
const std::string& npText = *nowPlayingResult; const String& npText = *nowPlayingResult;
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
content.push_back(hbox({ content.push_back(hbox(
text(string(musicIcon)) | color(ui::DEFAULT_THEME.icon), {
text("Playing") | color(ui::DEFAULT_THEME.label), text(String(musicIcon)) | color(ui::DEFAULT_THEME.icon),
text(" "), text("Playing") | color(ui::DEFAULT_THEME.label),
filler(), text(" "),
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, ui::MAX_PARAGRAPH_LENGTH), filler(),
text(" "), paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, ui::MAX_PARAGRAPH_LENGTH),
})); text(" "),
}
));
} else { } else {
const NowPlayingError& error = nowPlayingResult.error(); const NowPlayingError& error = nowPlayingResult.error();
@ -340,6 +354,11 @@ namespace {
if (std::holds_alternative<WindowsError>(error)) if (std::holds_alternative<WindowsError>(error))
DEBUG_LOG("WinRT error: {}", to_string(std::get<WindowsError>(error).message())); DEBUG_LOG("WinRT error: {}", to_string(std::get<WindowsError>(error).message()));
#endif #endif
#ifdef __APPLE__
if (std::holds_alternative<MacError>(error))
DEBUG_LOG("CoreAudio error: {}", std::get<MacError>(error));
#endif
} }
} }

View file

@ -1,6 +1,5 @@
#ifdef __FreeBSD__ #ifdef __FreeBSD__
#include <fmt/format.h>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <sdbus-c++/sdbus-c++.h> #include <sdbus-c++/sdbus-c++.h>
@ -48,8 +47,7 @@ fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector<string> {
const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus"); const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus");
const char* dbusMethodListNames = "ListNames"; const char* dbusMethodListNames = "ListNames";
const std::unique_ptr<sdbus::IProxy> dbusProxy = const std::unique_ptr<sdbus::IProxy> dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath);
createProxy(connection, dbusInterface, dbusObjectPath);
std::vector<string> names; std::vector<string> names;
@ -58,8 +56,7 @@ fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector<string> {
std::vector<string> mprisPlayers; std::vector<string> mprisPlayers;
for (const std::basic_string<char>& name : names) for (const std::basic_string<char>& name : names)
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != String::npos)
name.find(mprisInterfaceName) != std::string::npos)
mprisPlayers.push_back(name); mprisPlayers.push_back(name);
return mprisPlayers; return mprisPlayers;
@ -74,8 +71,7 @@ fn GetActivePlayer(const std::vector<string>& mprisPlayers) -> string {
fn GetNowPlaying() -> string { fn GetNowPlaying() -> string {
try { try {
const char *playerObjectPath = "/org/mpris/MediaPlayer2", const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player";
*playerInterfaceName = "org.mpris.MediaPlayer2.Player";
std::unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection(); std::unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
@ -89,24 +85,22 @@ fn GetNowPlaying() -> string {
if (activePlayer.empty()) if (activePlayer.empty())
return ""; return "";
auto playerProxy = sdbus::createProxy( auto playerProxy =
*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath) sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath));
);
sdbus::Variant metadataVariant = sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
if (metadataVariant.containsValueOfType<std::map<std::string, sdbus::Variant>>()) { if (metadataVariant.containsValueOfType<std::map<String, sdbus::Variant>>()) {
const auto& metadata = metadataVariant.get<std::map<std::string, sdbus::Variant>>(); const auto& metadata = metadataVariant.get<std::map<String, sdbus::Variant>>();
auto iter = metadata.find("xesam:title"); auto iter = metadata.find("xesam:title");
if (iter != metadata.end() && iter->second.containsValueOfType<std::string>()) if (iter != metadata.end() && iter->second.containsValueOfType<String>())
return iter->second.get<std::string>(); return iter->second.get<String>();
} }
} catch (const sdbus::Error& e) { } catch (const sdbus::Error& e) {
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer")
return fmt::format("Error: {}", e.what()); return std::format("Error: {}", e.what());
return "No active player"; return "No active player";
} }

View file

@ -8,7 +8,6 @@
#include <dirent.h> #include <dirent.h>
#include <expected> #include <expected>
#include <filesystem> #include <filesystem>
#include <fmt/format.h>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <mutex> #include <mutex>
@ -24,7 +23,6 @@
#include "os.h" #include "os.h"
#include "src/util/macros.h" #include "src/util/macros.h"
// Minimal global using declarations needed for function signatures
using std::expected; using std::expected;
using std::optional; using std::optional;
@ -32,7 +30,6 @@ namespace fs = std::filesystem;
using namespace std::literals::string_view_literals; using namespace std::literals::string_view_literals;
namespace { namespace {
// Local using declarations for the anonymous namespace
using std::array; using std::array;
using std::bit_cast; using std::bit_cast;
using std::getenv; using std::getenv;
@ -121,7 +118,7 @@ namespace {
XCloseDisplay(display); XCloseDisplay(display);
return "Unknown (X11)"; // Changed to empty string return "Unknown (X11)";
} }
fn TrimHyprlandWrapper(const string& input) -> string { fn TrimHyprlandWrapper(const string& input) -> string {

View file

@ -1,7 +1,8 @@
#ifdef __APPLE__ #ifdef __APPLE__
#include <expected> #include <expected>
#include <map> #include <flat_map>
#include <span>
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
@ -9,42 +10,41 @@
#include "os.h" #include "os.h"
#include "src/util/types.h" #include "src/util/types.h"
fn GetMemInfo() -> expected<u64, string> { fn GetMemInfo() -> expected<u64, String> {
u64 mem = 0; u64 mem = 0;
usize size = sizeof(mem); usize size = sizeof(mem);
if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1) if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1)
return std::unexpected(string("sysctlbyname failed: ") + strerror(errno)); return std::unexpected(std::format("sysctlbyname failed: {}", strerror(errno)));
return mem; return mem;
} }
fn GetNowPlaying() -> expected<string, NowPlayingError> { return GetCurrentPlayingInfo(); } fn GetNowPlaying() -> expected<String, NowPlayingError> { return GetCurrentPlayingInfo(); }
fn GetOSVersion() -> expected<string, string> { return GetMacOSVersion(); } fn GetOSVersion() -> expected<String, String> { return GetMacOSVersion(); }
fn GetDesktopEnvironment() -> optional<string> { return std::nullopt; } fn GetDesktopEnvironment() -> optional<String> { return std::nullopt; }
fn GetWindowManager() -> string { return "Yabai"; } fn GetWindowManager() -> String { return "Yabai"; }
fn GetKernelVersion() -> string { fn GetKernelVersion() -> String {
std::array<char, 256> kernelVersion; std::array<char, 256> kernelVersion {};
usize kernelVersionLen = sizeof(kernelVersion); usize kernelVersionLen = sizeof(kernelVersion);
sysctlbyname("kern.osrelease", kernelVersion.data(), &kernelVersionLen, nullptr, 0); sysctlbyname("kern.osrelease", std::span(kernelVersion).data(), &kernelVersionLen, nullptr, 0);
return kernelVersion.data(); return kernelVersion.data();
} }
fn GetHost() -> string { fn GetHost() -> String {
std::array<char, 256> hwModel; std::array<char, 256> hwModel {};
size_t hwModelLen = sizeof(hwModel); size_t hwModelLen = sizeof(hwModel);
sysctlbyname("hw.model", hwModel.data(), &hwModelLen, nullptr, 0); sysctlbyname("hw.model", hwModel.data(), &hwModelLen, nullptr, 0);
// taken from https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/host/host_mac.c // taken from https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/host/host_mac.c
// shortened a lot of the entries to remove unnecessary info // shortened a lot of the entries to remove unnecessary info
std::map<std::string, std::string> modelNameByHwModel = { std::flat_map<std::string_view, std::string_view> modelNameByHwModel = {
// MacBook Pro // MacBook Pro
{ "MacBookPro18,3", "MacBook Pro (14-inch, 2021)" }, { "MacBookPro18,3", "MacBook Pro (14-inch, 2021)" },
{ "MacBookPro18,4", "MacBook Pro (14-inch, 2021)" }, { "MacBookPro18,4", "MacBook Pro (14-inch, 2021)" },
@ -193,7 +193,7 @@ fn GetHost() -> string {
{ "iMac9,1", "iMac (24/20-inch, 2009)" }, { "iMac9,1", "iMac (24/20-inch, 2009)" },
}; };
return modelNameByHwModel[hwModel.data()]; return String(modelNameByHwModel[hwModel.data()]);
} }
// returns free/total // returns free/total
@ -206,6 +206,6 @@ fn GetDiskUsage() -> std::pair<u64, u64> {
return { (vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize, vfs.f_blocks * vfs.f_frsize }; return { (vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize, vfs.f_blocks * vfs.f_frsize };
} }
fn GetShell() -> string { return ""; } fn GetShell() -> String { return ""; }
#endif #endif

View file

@ -3,7 +3,6 @@
#ifdef __APPLE__ #ifdef __APPLE__
#include <expected> #include <expected>
#include <string>
#include "../../util/macros.h" #include "../../util/macros.h"
@ -13,14 +12,14 @@
@interface Bridge : NSObject @interface Bridge : NSObject
+ (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion; + (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion;
+ (std::expected<string, string>)macOSVersion; + (std::expected<String, String>)macOSVersion;
@end @end
#else #else
extern "C++" { extern "C++" {
fn GetCurrentPlayingInfo() -> std::expected<std::string, NowPlayingError>; fn GetCurrentPlayingInfo() -> std::expected<String, NowPlayingError>;
fn GetMacOSVersion() -> std::expected<std::string, const char*>; fn GetMacOSVersion() -> std::expected<String, String>;
} }
#endif #endif

View file

@ -2,8 +2,11 @@
#import <dispatch/dispatch.h> #import <dispatch/dispatch.h>
#include <expected> #include <expected>
#include <functional>
#include <memory>
#import <objc/runtime.h> #import <objc/runtime.h>
#include <string> #include <string>
#include <utility>
#import "bridge.h" #import "bridge.h"
@ -12,47 +15,51 @@ using MRMediaRemoteGetNowPlayingInfoFunction =
@implementation Bridge @implementation Bridge
+ (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion { + (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion {
CFURLRef ref = CFURLCreateWithFileSystemPath( CFURLRef urlRef = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault, CFSTR("/System/Library/PrivateFrameworks/MediaRemote.framework"), kCFURLPOSIXPathStyle, false kCFAllocatorDefault, CFSTR("/System/Library/PrivateFrameworks/MediaRemote.framework"), kCFURLPOSIXPathStyle, false
); );
if (!ref) { if (!urlRef) {
completion(std::unexpected("Failed to create CFURL for MediaRemote framework")); completion(std::unexpected("Failed to create CFURL for MediaRemote framework"));
return; return;
} }
CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, ref); CFBundleRef bundleRef = CFBundleCreate(kCFAllocatorDefault, urlRef);
CFRelease(ref);
if (!bundle) { CFRelease(urlRef);
if (!bundleRef) {
completion(std::unexpected("Failed to create bundle for MediaRemote framework")); completion(std::unexpected("Failed to create bundle for MediaRemote framework"));
return; return;
} }
auto mrMediaRemoteGetNowPlayingInfo = std::bit_cast<MRMediaRemoteGetNowPlayingInfoFunction>( auto mrMediaRemoteGetNowPlayingInfo = std::bit_cast<MRMediaRemoteGetNowPlayingInfoFunction>(
CFBundleGetFunctionPointerForName(bundle, CFSTR("MRMediaRemoteGetNowPlayingInfo")) CFBundleGetFunctionPointerForName(bundleRef, CFSTR("MRMediaRemoteGetNowPlayingInfo"))
); );
if (!mrMediaRemoteGetNowPlayingInfo) { if (!mrMediaRemoteGetNowPlayingInfo) {
CFRelease(bundle); CFRelease(bundleRef);
completion(std::unexpected("Failed to get MRMediaRemoteGetNowPlayingInfo function pointer")); completion(std::unexpected("Failed to get MRMediaRemoteGetNowPlayingInfo function pointer"));
return; return;
} }
std::shared_ptr<std::remove_pointer_t<CFBundleRef>> sharedBundle(bundleRef, [](CFBundleRef bundle) {
if (bundle)
CFRelease(bundle);
});
mrMediaRemoteGetNowPlayingInfo( mrMediaRemoteGetNowPlayingInfo(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(NSDictionary* information) { ^(NSDictionary* information) {
NSDictionary* nowPlayingInfo = information; // Immutable, no copy needed
CFRelease(bundle);
completion( completion(
nowPlayingInfo ? std::expected<NSDictionary*, const char*>(nowPlayingInfo) information ? std::expected<NSDictionary*, const char*>(information)
: std::unexpected("No now playing information") : std::unexpected("No now playing information")
); );
} }
); );
} }
+ (std::expected<string, string>)macOSVersion { + (std::expected<String, String>)macOSVersion {
NSProcessInfo* processInfo = [NSProcessInfo processInfo]; NSProcessInfo* processInfo = [NSProcessInfo processInfo];
NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion]; NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion];
@ -71,15 +78,15 @@ using MRMediaRemoteGetNowPlayingInfoFunction =
NSString* versionName = versionNames[majorVersion] ? versionNames[majorVersion] : @"Unknown"; NSString* versionName = versionNames[majorVersion] ? versionNames[majorVersion] : @"Unknown";
NSString* fullVersion = [NSString stringWithFormat:@"macOS %@ %@", versionNumber, versionName]; NSString* fullVersion = [NSString stringWithFormat:@"macOS %@ %@", versionNumber, versionName];
return std::string([fullVersion UTF8String]); return String([fullVersion UTF8String]);
} }
@end @end
extern "C++" { extern "C++" {
// NOLINTBEGIN(misc-use-internal-linkage) // NOLINTBEGIN(misc-use-internal-linkage)
fn GetCurrentPlayingInfo() -> std::expected<std::string, NowPlayingError> { fn GetCurrentPlayingInfo() -> std::expected<String, NowPlayingError> {
__block std::expected<std::string, NowPlayingError> result; __block std::expected<String, NowPlayingError> result;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[Bridge fetchCurrentPlayingMetadata:^(std::expected<NSDictionary*, const char*> metadataResult) { [Bridge fetchCurrentPlayingMetadata:^(std::expected<NSDictionary*, const char*> metadataResult) {
if (!metadataResult) { if (!metadataResult) {
@ -95,17 +102,17 @@ extern "C++" {
return; return;
} }
NSString* title = [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoTitle"]; NSString* title = metadata[@"kMRMediaRemoteNowPlayingInfoTitle"];
NSString* artist = [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoArtist"]; NSString* artist = metadata[@"kMRMediaRemoteNowPlayingInfoArtist"];
if (!title && !artist) if (!title && !artist)
result = std::unexpected("No metadata"); result = std::unexpected(NowPlayingError { "No metadata" });
else if (!title) else if (!title)
result = std::string([artist UTF8String]); result = String([artist UTF8String]);
else if (!artist) else if (!artist)
result = std::string([title UTF8String]); result = String([title UTF8String]);
else else
result = std::string([[NSString stringWithFormat:@"%@ - %@", title, artist] UTF8String]); result = String([[NSString stringWithFormat:@"%@ - %@", title, artist] UTF8String]);
dispatch_semaphore_signal(semaphore); dispatch_semaphore_signal(semaphore);
}]; }];
@ -114,7 +121,7 @@ extern "C++" {
return result; return result;
} }
fn GetMacOSVersion() -> std::expected<string, string> { return [Bridge macOSVersion]; } fn GetMacOSVersion() -> std::expected<String, String> { return [Bridge macOSVersion]; }
// NOLINTEND(misc-use-internal-linkage) // NOLINTEND(misc-use-internal-linkage)
} }

View file

@ -10,42 +10,42 @@ using std::optional, std::expected;
/** /**
* @brief Get the amount of installed RAM in bytes. * @brief Get the amount of installed RAM in bytes.
*/ */
fn GetMemInfo() -> expected<u64, string>; fn GetMemInfo() -> expected<u64, String>;
/** /**
* @brief Get the currently playing song metadata. * @brief Get the currently playing song metadata.
*/ */
fn GetNowPlaying() -> expected<string, NowPlayingError>; fn GetNowPlaying() -> expected<String, NowPlayingError>;
/** /**
* @brief Get the OS version. * @brief Get the OS version.
*/ */
fn GetOSVersion() -> expected<string, string>; fn GetOSVersion() -> expected<String, String>;
/** /**
* @brief Get the current desktop environment. * @brief Get the current desktop environment.
*/ */
fn GetDesktopEnvironment() -> optional<string>; fn GetDesktopEnvironment() -> optional<String>;
/** /**
* @brief Get the current window manager. * @brief Get the current window manager.
*/ */
fn GetWindowManager() -> string; fn GetWindowManager() -> String;
/** /**
* @brief Get the current shell. * @brief Get the current shell.
*/ */
fn GetShell() -> string; fn GetShell() -> String;
/** /**
* @brief Get the product family * @brief Get the product family
*/ */
fn GetHost() -> string; fn GetHost() -> String;
/** /**
* @brief Get the kernel version. * @brief Get the kernel version.
*/ */
fn GetKernelVersion() -> string; fn GetKernelVersion() -> String;
/** /**
* @brief Get the number of installed packages. * @brief Get the number of installed packages.

View file

@ -1,7 +1,9 @@
#pragma once #pragma once
// probably stupid but it fixes the issue with windows.h defining ERROR #ifdef _WIN32
#undef ERROR #undef ERROR
#endif
#include <chrono> #include <chrono>
#include <filesystem> #include <filesystem>
#include <format> #include <format>
@ -11,18 +13,15 @@
#include "types.h" #include "types.h"
#define fn auto // Rust-style function shorthand #define fn auto
// Terminal color implementation to replace fmt::color
namespace term { namespace term {
// Text styles
enum class Emphasis : u8 { none = 0, bold = 1, italic = 2 }; enum class Emphasis : u8 { none = 0, bold = 1, italic = 2 };
constexpr fn operator|(Emphasis emphA, Emphasis emphB)->Emphasis { constexpr fn operator|(Emphasis emphA, Emphasis emphB)->Emphasis {
return static_cast<Emphasis>(static_cast<int>(emphA) | static_cast<int>(emphB)); return static_cast<Emphasis>(static_cast<int>(emphA) | static_cast<int>(emphB));
} }
// Terminal colors
enum class Color : u8 { enum class Color : u8 {
black = 30, black = 30,
red = 31, red = 31,
@ -42,70 +41,57 @@ namespace term {
bright_white = 97 bright_white = 97
}; };
// Style wrapper for foreground color
struct FgColor { struct FgColor {
Color col; Color col;
constexpr explicit FgColor(Color color) : col(color) {} constexpr explicit FgColor(Color color) : col(color) {}
[[nodiscard]] fn ansiCode() const -> std::string { return std::format("\033[{}m", static_cast<int>(col)); } [[nodiscard]] fn ansiCode() const -> String { return std::format("\033[{}m", static_cast<int>(col)); }
}; };
// Create a foreground color modifier
constexpr fn Fg(Color color) -> FgColor { return FgColor(color); } constexpr fn Fg(Color color) -> FgColor { return FgColor(color); }
// Combined style (emphasis + color)
struct Style { struct Style {
Emphasis emph = Emphasis::none; Emphasis emph = Emphasis::none;
FgColor fg_col = FgColor(static_cast<Color>(-1)); // Invalid color FgColor fg_col = FgColor(static_cast<Color>(-1)); // Invalid color
[[nodiscard]] fn ansiCode() const -> std::string { [[nodiscard]] fn ansiCode() const -> String {
std::string result; String result;
if (emph != Emphasis::none) { if (emph != Emphasis::none) {
if ((static_cast<int>(emph) & static_cast<int>(Emphasis::bold)) != 0) { if ((static_cast<int>(emph) & static_cast<int>(Emphasis::bold)) != 0)
result += "\033[1m"; result += "\033[1m";
} if ((static_cast<int>(emph) & static_cast<int>(Emphasis::italic)) != 0)
if ((static_cast<int>(emph) & static_cast<int>(Emphasis::italic)) != 0) {
result += "\033[3m"; result += "\033[3m";
}
} }
if (static_cast<int>(fg_col.col) != -1) { if (static_cast<int>(fg_col.col) != -1)
result += fg_col.ansiCode(); result += fg_col.ansiCode();
}
return result; return result;
} }
}; };
// Combine emphasis and color
constexpr fn operator|(Emphasis emph, FgColor fgColor)->Style { return { .emph = emph, .fg_col = fgColor }; } constexpr fn operator|(Emphasis emph, FgColor fgColor)->Style { return { .emph = emph, .fg_col = fgColor }; }
constexpr fn operator|(FgColor fgColor, Emphasis emph)->Style { return { .emph = emph, .fg_col = fgColor }; }
constexpr fn operator|(FgColor fgColor, Emphasis emph)->Style { return emph | fgColor; }
// Reset all styles
constexpr const char* reset = "\033[0m"; constexpr const char* reset = "\033[0m";
// Print with style
template <typename... Args> template <typename... Args>
fn Print(const Style& style, std::format_string<Args...> fmt, Args&&... args) -> void { fn Print(const Style& style, std::format_string<Args...> fmt, Args&&... args) -> void {
std::print("{}{}{}", style.ansiCode(), std::format(fmt, std::forward<Args>(args)...), reset); std::print("{}{}{}", style.ansiCode(), std::format(fmt, std::forward<Args>(args)...), reset);
} }
// Print with foreground color only
template <typename... Args> template <typename... Args>
fn Print(const FgColor& fgColor, std::format_string<Args...> fmt, Args&&... args) -> void { fn Print(const FgColor& fgColor, std::format_string<Args...> fmt, Args&&... args) -> void {
std::print("{}{}{}", fgColor.ansiCode(), std::format(fmt, std::forward<Args>(args)...), reset); std::print("{}{}{}", fgColor.ansiCode(), std::format(fmt, std::forward<Args>(args)...), reset);
} }
// Print with emphasis only
template <typename... Args> template <typename... Args>
fn Print(Emphasis emph, std::format_string<Args...> fmt, Args&&... args) -> void { fn Print(Emphasis emph, std::format_string<Args...> fmt, Args&&... args) -> void {
Print({ .emph = emph }, fmt, std::forward<Args>(args)...); Print({ .emph = emph }, fmt, std::forward<Args>(args)...);
} }
// Print without styling (plain text)
template <typename... Args> template <typename... Args>
fn Print(std::format_string<Args...> fmt, Args&&... args) -> void { fn Print(std::format_string<Args...> fmt, Args&&... args) -> void {
std::print(fmt, std::forward<Args>(args)...); std::print(fmt, std::forward<Args>(args)...);
@ -114,15 +100,20 @@ namespace term {
namespace log_colors { namespace log_colors {
using term::Color; using term::Color;
constexpr auto debug = Color::cyan, info = Color::green, warn = Color::yellow, error = Color::red,
timestamp = Color::bright_white, file_info = Color::bright_white; constexpr Color debug = Color::cyan, info = Color::green, warn = Color::yellow, error = Color::red,
timestamp = Color::bright_white, file_info = Color::bright_white;
} }
enum class LogLevel : u8 { DEBUG, INFO, WARN, ERROR }; enum class LogLevel : u8 { DEBUG, INFO, WARN, ERROR };
template <typename... Args> template <typename... Args>
void LogImpl(const LogLevel level, const std::source_location& loc, std::format_string<Args...> fmt, Args&&... args) { fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_string<Args...> fmt, Args&&... args)
const auto now = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now()); -> void {
using namespace std::chrono;
const time_point<system_clock, duration<long long, std::ratio<1, 1>>> now =
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
const auto [color, levelStr] = [&] { const auto [color, levelStr] = [&] {
switch (level) { switch (level) {
@ -141,19 +132,21 @@ void LogImpl(const LogLevel level, const std::source_location& loc, std::format_
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }
}(); }();
const string filename = std::filesystem::path(loc.file_name()).lexically_normal().string();
// Timestamp and level - using std::chrono with std::format const String filename = std::filesystem::path(loc.file_name()).lexically_normal().string();
term::Print(term::Fg(log_colors::timestamp), "[{:%H:%M:%S}] ", now);
term::Print(term::Emphasis::bold | term::Fg(color), "{} ", levelStr); using namespace term;
// Message
term::Print(fmt, std::forward<Args>(args)...); Print(Fg(log_colors::timestamp), "[{:%H:%M:%S}] ", now);
// File info (debug builds only) Print(Emphasis::bold | Fg(color), "{} ", levelStr);
Print(fmt, std::forward<Args>(args)...);
#ifndef NDEBUG #ifndef NDEBUG
term::Print(term::Fg(log_colors::file_info), "\n{:>14} ", "╰──"); Print(Fg(log_colors::file_info), "\n{:>14} ", "╰──");
term::Print(term::Emphasis::italic | term::Fg(log_colors::file_info), "{}:{}", filename, loc.line()); Print(Emphasis::italic | Fg(log_colors::file_info), "{}:{}", filename, loc.line());
#endif #endif
term::Print("\n");
Print("\n");
} }
#pragma clang diagnostic push #pragma clang diagnostic push

View file

@ -2,6 +2,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdlib>
#include <expected> #include <expected>
#include <string> #include <string>
@ -133,7 +134,7 @@ using isize = std::ptrdiff_t;
* @typedef string * @typedef string
* @brief Represents a string. * @brief Represents a string.
*/ */
using string = std::string; using String = std::string;
/** /**
* @enum NowPlayingCode * @enum NowPlayingCode
@ -150,13 +151,13 @@ enum class NowPlayingCode : u8 {
* @typedef LinuxError * @typedef LinuxError
* @brief Represents a Linux-specific error. * @brief Represents a Linux-specific error.
*/ */
using LinuxError = std::string; using LinuxError = String;
#elif defined(__APPLE__) #elif defined(__APPLE__)
/** /**
* @typedef MacError * @typedef MacError
* @brief Represents a macOS-specific error. * @brief Represents a macOS-specific error.
*/ */
using MacError = std::string; using MacError = String;
#elif defined(_WIN32) #elif defined(_WIN32)
/** /**
* @typedef WindowsError * @typedef WindowsError
@ -179,7 +180,7 @@ using NowPlayingError = std::variant<
enum class EnvError : u8 { NotFound, AccessError }; enum class EnvError : u8 { NotFound, AccessError };
inline auto GetEnv(const std::string& name) -> std::expected<std::string, EnvError> { inline auto GetEnv(const String& name) -> std::expected<String, EnvError> {
#ifdef _WIN32 #ifdef _WIN32
char* rawPtr = nullptr; char* rawPtr = nullptr;
size_t bufferSize = 0; size_t bufferSize = 0;
@ -190,7 +191,7 @@ inline auto GetEnv(const std::string& name) -> std::expected<std::string, EnvErr
if (!rawPtr) if (!rawPtr)
return std::unexpected(EnvError::NotFound); return std::unexpected(EnvError::NotFound);
const std::string result(rawPtr); const String result(rawPtr);
free(rawPtr); free(rawPtr);
return result; return result;
#else #else
@ -198,6 +199,6 @@ inline auto GetEnv(const std::string& name) -> std::expected<std::string, EnvErr
if (!value) if (!value)
return std::unexpected(EnvError::NotFound); return std::unexpected(EnvError::NotFound);
return std::string(value); return String(value);
#endif #endif
} }

View file

@ -1,13 +0,0 @@
[wrap-file]
directory = fmt-11.1.1
source_url = https://github.com/fmtlib/fmt/archive/11.1.1.tar.gz
source_filename = fmt-11.1.1.tar.gz
source_hash = 482eed9efbc98388dbaee5cb5f368be5eca4893456bb358c18b7ff71f835ae43
patch_filename = fmt_11.1.1-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.1.1-2/get_patch
patch_hash = eee2e90d5d43061a0a1f0b9f8eb188c5b8820ef3e1b15e4b8a4eb791ef82b325
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.1.1-2/fmt-11.1.1.tar.gz
wrapdb_version = 11.1.1-2
[provide]
fmt = fmt_dep