some more updates and stuff
This commit is contained in:
parent
96c6e79780
commit
203d56e06b
17 changed files with 231 additions and 255 deletions
12
flake.lock
generated
12
flake.lock
generated
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1744536153,
|
||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
||||
"lastModified": 1744868846,
|
||||
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
||||
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -59,11 +59,11 @@
|
|||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743748085,
|
||||
"narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=",
|
||||
"lastModified": 1744961264,
|
||||
"narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d",
|
||||
"rev": "8d404a69efe76146368885110f29a2ca3700bee6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
11
flake.nix
11
flake.nix
|
@ -18,10 +18,7 @@
|
|||
system: let
|
||||
pkgs = import nixpkgs {inherit system;};
|
||||
|
||||
llvmPackages = with pkgs;
|
||||
if hostPlatform.isLinux
|
||||
then llvmPackages_20
|
||||
else llvmPackages_19;
|
||||
llvmPackages = pkgs.llvmPackages_20;
|
||||
|
||||
stdenv = with pkgs;
|
||||
(
|
||||
|
@ -38,18 +35,20 @@
|
|||
++ (with pkgsStatic; [
|
||||
curl
|
||||
ftxui
|
||||
libiconv
|
||||
sqlitecpp
|
||||
tomlplusplus
|
||||
])
|
||||
++ darwinPkgs
|
||||
++ linuxPkgs;
|
||||
|
||||
darwinPkgs = nixpkgs.lib.optionals stdenv.isDarwin (with pkgs.pkgsStatic; [libiconv]);
|
||||
|
||||
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
|
||||
[
|
||||
valgrind
|
||||
]
|
||||
++ (with pkgsStatic; [
|
||||
dbus
|
||||
sqlitecpp
|
||||
xorg.libX11
|
||||
wayland
|
||||
]));
|
||||
|
|
|
@ -58,7 +58,7 @@ if host_system == 'darwin'
|
|||
objcpp = meson.get_compiler('objcpp')
|
||||
objcpp_flags = common_warning_flags + [
|
||||
'-Wno-switch-default',
|
||||
'-std=c++2b',
|
||||
'-std=c++23',
|
||||
'-fvisibility=hidden',
|
||||
'-fvisibility-inlines-hidden',
|
||||
]
|
||||
|
|
|
@ -14,24 +14,19 @@ namespace {
|
|||
std::vector<fs::path> possiblePaths;
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows possible paths in order of preference
|
||||
if (auto result = GetEnv("LOCALAPPDATA"); result)
|
||||
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
|
||||
|
||||
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");
|
||||
// Traditional Windows location alternative
|
||||
possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml");
|
||||
}
|
||||
|
||||
if (auto result = GetEnv("APPDATA"); result)
|
||||
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");
|
||||
#else
|
||||
// Unix/Linux paths in order of preference
|
||||
if (auto result = GetEnv("XDG_CONFIG_HOME"); result)
|
||||
possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml");
|
||||
|
||||
|
@ -40,18 +35,14 @@ namespace {
|
|||
possiblePaths.emplace_back(fs::path(*result) / ".draconis++" / "config.toml");
|
||||
}
|
||||
|
||||
// System-wide config
|
||||
possiblePaths.emplace_back("/etc/draconis++/config.toml");
|
||||
#endif
|
||||
|
||||
// Check if any of these configs already exist
|
||||
for (const auto& path : possiblePaths)
|
||||
if (std::error_code errc; exists(path, errc) && !errc)
|
||||
return path;
|
||||
|
||||
// If no config exists yet, return the default (first in priority)
|
||||
if (!possiblePaths.empty()) {
|
||||
// Create directory structure for the default path
|
||||
const fs::path defaultDir = possiblePaths[0].parent_path();
|
||||
|
||||
if (std::error_code errc; !exists(defaultDir, errc) && !errc) {
|
||||
|
@ -63,13 +54,11 @@ namespace {
|
|||
return possiblePaths[0];
|
||||
}
|
||||
|
||||
// Ultimate fallback if somehow we have no paths
|
||||
throw std::runtime_error("Could not determine a valid config path");
|
||||
}
|
||||
|
||||
fn CreateDefaultConfig(const fs::path& configPath) -> bool {
|
||||
try {
|
||||
// Ensure the directory exists
|
||||
std::error_code errc;
|
||||
create_directories(configPath.parent_path(), errc);
|
||||
if (errc) {
|
||||
|
@ -77,11 +66,9 @@ namespace {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Create a default TOML document
|
||||
toml::table root;
|
||||
|
||||
// Get default username for General section
|
||||
std::string defaultName;
|
||||
String defaultName;
|
||||
#ifdef _WIN32
|
||||
std::array<char, 256> username;
|
||||
DWORD size = sizeof(username);
|
||||
|
@ -95,15 +82,12 @@ namespace {
|
|||
defaultName = "User";
|
||||
#endif
|
||||
|
||||
// General section
|
||||
toml::table* general = root.insert("general", toml::table {}).first->second.as_table();
|
||||
general->insert("name", defaultName);
|
||||
|
||||
// Now Playing section
|
||||
toml::table* nowPlaying = root.insert("now_playing", toml::table {}).first->second.as_table();
|
||||
nowPlaying->insert("enabled", false);
|
||||
|
||||
// Weather section
|
||||
toml::table* weather = root.insert("weather", toml::table {}).first->second.as_table();
|
||||
weather->insert("enabled", false);
|
||||
weather->insert("show_town_name", false);
|
||||
|
@ -111,7 +95,6 @@ namespace {
|
|||
weather->insert("units", "metric");
|
||||
weather->insert("location", "London");
|
||||
|
||||
// Write to file (using a stringstream for comments + TOML)
|
||||
std::ofstream file(configPath);
|
||||
if (!file) {
|
||||
ERROR_LOG("Failed to open config file for writing: {}", configPath.string());
|
||||
|
@ -154,18 +137,15 @@ fn Config::getInstance() -> Config {
|
|||
try {
|
||||
const fs::path configPath = GetConfigPath();
|
||||
|
||||
// Check if the config file exists
|
||||
if (!exists(configPath)) {
|
||||
INFO_LOG("Config file not found, creating defaults at {}", configPath.string());
|
||||
|
||||
// Create default config
|
||||
if (!CreateDefaultConfig(configPath)) {
|
||||
WARN_LOG("Failed to create default config, using in-memory defaults");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Now we should have a config file to read
|
||||
const toml::parse_result config = toml::parse_file(configPath.string());
|
||||
return fromToml(config);
|
||||
} catch (const std::exception& e) {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pwd.h> // For getpwuid
|
||||
#include <unistd.h> // For getuid
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
|
@ -12,10 +12,10 @@
|
|||
#include "src/util/macros.h"
|
||||
#include "weather.h"
|
||||
|
||||
using Location = std::variant<string, Coords>;
|
||||
using Location = std::variant<String, Coords>;
|
||||
|
||||
struct General {
|
||||
string name = []() -> string {
|
||||
String name = []() -> String {
|
||||
#ifdef _WIN32
|
||||
std::array<char, 256> username;
|
||||
DWORD size = sizeof(username);
|
||||
|
@ -34,7 +34,7 @@ struct General {
|
|||
static fn fromToml(const toml::table& tbl) -> General {
|
||||
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;
|
||||
|
||||
return gen;
|
||||
|
@ -55,15 +55,15 @@ struct Weather {
|
|||
bool enabled = false;
|
||||
bool show_town_name = false;
|
||||
Location location;
|
||||
string api_key;
|
||||
string units;
|
||||
String api_key;
|
||||
String units;
|
||||
|
||||
static fn fromToml(const toml::table& tbl) -> Weather {
|
||||
Weather weather;
|
||||
weather.enabled = tbl["enabled"].value_or<bool>(false);
|
||||
|
||||
if (auto apiKey = tbl["api_key"].value<string>()) {
|
||||
const string& keyVal = apiKey.value();
|
||||
if (auto apiKey = tbl["api_key"].value<String>()) {
|
||||
const String& keyVal = apiKey.value();
|
||||
|
||||
if (keyVal.empty())
|
||||
weather.enabled = false;
|
||||
|
@ -77,11 +77,11 @@ struct Weather {
|
|||
return weather;
|
||||
|
||||
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 (location.is_string()) {
|
||||
weather.location = location.value<string>().value();
|
||||
weather.location = location.value<String>().value();
|
||||
} else if (location.is_table()) {
|
||||
const auto* coord = location.as_table();
|
||||
Coords coords;
|
||||
|
|
|
@ -15,7 +15,7 @@ using namespace std::string_literals;
|
|||
namespace {
|
||||
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;
|
||||
fs::path cachePath = fs::temp_directory_path(errc);
|
||||
|
||||
|
@ -26,8 +26,8 @@ namespace {
|
|||
return cachePath;
|
||||
}
|
||||
|
||||
fn ReadCacheFromFile() -> std::expected<WeatherOutput, string> {
|
||||
std::expected<fs::path, string> cachePath = GetCachePath();
|
||||
fn ReadCacheFromFile() -> std::expected<WeatherOutput, String> {
|
||||
std::expected<fs::path, String> cachePath = GetCachePath();
|
||||
|
||||
if (!cachePath)
|
||||
return std::unexpected(cachePath.error());
|
||||
|
@ -40,7 +40,7 @@ namespace {
|
|||
DEBUG_LOG("Reading from cache file...");
|
||||
|
||||
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;
|
||||
|
||||
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()); }
|
||||
}
|
||||
|
||||
fn WriteCacheToFile(const WeatherOutput& data) -> std::expected<void, string> {
|
||||
std::expected<fs::path, string> cachePath = GetCachePath();
|
||||
fn WriteCacheToFile(const WeatherOutput& data) -> std::expected<void, String> {
|
||||
std::expected<fs::path, String> cachePath = GetCachePath();
|
||||
|
||||
if (!cachePath)
|
||||
return std::unexpected(cachePath.error());
|
||||
|
@ -67,7 +67,7 @@ namespace {
|
|||
if (!ofs.is_open())
|
||||
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)
|
||||
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()); }
|
||||
}
|
||||
|
||||
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;
|
||||
str->append(static_cast<char*>(contents), 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);
|
||||
CURL* curl = curl_easy_init();
|
||||
string responseBuffer;
|
||||
String responseBuffer;
|
||||
|
||||
if (!curl)
|
||||
return std::unexpected("Failed to initialize cURL");
|
||||
|
@ -127,7 +127,7 @@ namespace {
|
|||
fn Weather::getWeatherInfo() const -> WeatherOutput {
|
||||
using namespace std::chrono;
|
||||
|
||||
if (std::expected<WeatherOutput, string> data = ReadCacheFromFile()) {
|
||||
if (std::expected<WeatherOutput, String> data = ReadCacheFromFile()) {
|
||||
const WeatherOutput& dataVal = *data;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
fn handleApiResult = [](const std::expected<WeatherOutput, string>& result) -> WeatherOutput {
|
||||
fn handleApiResult = [](const std::expected<WeatherOutput, String>& result) -> WeatherOutput {
|
||||
if (!result) {
|
||||
ERROR_LOG("API request failed: {}", result.error());
|
||||
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());
|
||||
|
||||
return *result;
|
||||
};
|
||||
|
||||
if (std::holds_alternative<string>(location)) {
|
||||
const auto& city = std::get<string>(location);
|
||||
if (std::holds_alternative<String>(location)) {
|
||||
const auto& city = std::get<String>(location);
|
||||
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<int>(city.length()));
|
||||
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);
|
||||
|
||||
curl_free(escaped);
|
||||
|
@ -168,7 +168,7 @@ fn Weather::getWeatherInfo() const -> WeatherOutput {
|
|||
const auto& [lat, lon] = std::get<Coords>(location);
|
||||
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
|
||||
);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
struct Condition {
|
||||
string description;
|
||||
String description;
|
||||
|
||||
struct glaze {
|
||||
using T = Condition;
|
||||
|
@ -30,7 +30,7 @@ struct Coords {
|
|||
|
||||
struct WeatherOutput {
|
||||
Main main;
|
||||
string name;
|
||||
String name;
|
||||
std::vector<Condition> weather;
|
||||
usize dt;
|
||||
|
||||
|
|
101
src/main.cpp
101
src/main.cpp
|
@ -101,19 +101,19 @@ namespace ui {
|
|||
|
||||
namespace {
|
||||
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())
|
||||
return value_func(*exp);
|
||||
|
||||
return error_func(exp.error());
|
||||
}
|
||||
|
||||
fn GetDate() -> string {
|
||||
fn GetDate() -> String {
|
||||
using namespace std::chrono;
|
||||
|
||||
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());
|
||||
|
||||
|
@ -129,18 +129,18 @@ namespace {
|
|||
}
|
||||
|
||||
struct SystemData {
|
||||
std::string date;
|
||||
std::string host;
|
||||
std::string kernel_version;
|
||||
std::expected<string, string> os_version;
|
||||
std::expected<u64, std::string> mem_info;
|
||||
std::optional<string> desktop_environment;
|
||||
std::string window_manager;
|
||||
std::optional<std::expected<string, NowPlayingError>> now_playing;
|
||||
String date;
|
||||
String host;
|
||||
String kernel_version;
|
||||
std::expected<String, String> os_version;
|
||||
std::expected<u64, String> mem_info;
|
||||
std::optional<String> desktop_environment;
|
||||
String window_manager;
|
||||
std::optional<std::expected<String, NowPlayingError>> now_playing;
|
||||
std::optional<WeatherOutput> weather_info;
|
||||
u64 disk_used;
|
||||
u64 disk_total;
|
||||
std::string shell;
|
||||
String shell;
|
||||
|
||||
static fn fetchSystemData(const Config& config) -> SystemData {
|
||||
SystemData data;
|
||||
|
@ -164,7 +164,7 @@ namespace {
|
|||
|
||||
// Conditional tasks
|
||||
std::future<WeatherOutput> weather;
|
||||
std::future<std::expected<string, NowPlayingError>> nowPlaying;
|
||||
std::future<std::expected<String, NowPlayingError>> nowPlaying;
|
||||
|
||||
if (config.weather.enabled)
|
||||
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 {
|
||||
// Fetch data
|
||||
const string& name = config.general.name;
|
||||
const String& name = config.general.name;
|
||||
const Weather weather = config.weather;
|
||||
const bool nowPlayingEnabled = config.now_playing.enabled;
|
||||
|
||||
|
@ -209,23 +209,27 @@ namespace {
|
|||
|
||||
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(hbox({
|
||||
text(string(paletteIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(String(paletteIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
CreateColorCircles(),
|
||||
}));
|
||||
}
|
||||
));
|
||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||
|
||||
// Helper function for aligned rows
|
||||
fn createRow = [&](const auto& icon, const auto& label, const auto& value) {
|
||||
return hbox({
|
||||
text(string(icon)) | color(ui::DEFAULT_THEME.icon),
|
||||
text(string(static_cast<const char*>(label))) | color(ui::DEFAULT_THEME.label),
|
||||
return hbox(
|
||||
{
|
||||
text(String(icon)) | color(ui::DEFAULT_THEME.icon),
|
||||
text(String(static_cast<const char*>(label))) | color(ui::DEFAULT_THEME.label),
|
||||
filler(),
|
||||
text(string(value)) | color(ui::DEFAULT_THEME.value),
|
||||
text(String(value)) | color(ui::DEFAULT_THEME.value),
|
||||
text(" "),
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// System info rows
|
||||
|
@ -236,31 +240,39 @@ namespace {
|
|||
const WeatherOutput& weatherInfo = data.weather_info.value();
|
||||
|
||||
if (weather.show_town_name)
|
||||
content.push_back(hbox({
|
||||
text(string(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
text("Weather") | color(ui::DEFAULT_THEME.label),
|
||||
filler(),
|
||||
|
||||
hbox({
|
||||
hbox(
|
||||
{
|
||||
text(std::format("{}°F ", std::lround(weatherInfo.main.temp))),
|
||||
text("in "),
|
||||
text(weatherInfo.name),
|
||||
text(" "),
|
||||
}) |
|
||||
}
|
||||
) |
|
||||
color(ui::DEFAULT_THEME.value),
|
||||
}));
|
||||
}
|
||||
));
|
||||
else
|
||||
content.push_back(hbox({
|
||||
text(string(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
text("Weather") | color(ui::DEFAULT_THEME.label),
|
||||
filler(),
|
||||
|
||||
hbox({
|
||||
hbox(
|
||||
{
|
||||
text(std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)),
|
||||
text(" "),
|
||||
}) |
|
||||
}
|
||||
) |
|
||||
color(ui::DEFAULT_THEME.value),
|
||||
}));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||
|
@ -273,14 +285,14 @@ namespace {
|
|||
|
||||
expected_visit(
|
||||
data.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& version) { content.push_back(createRow(String(osIcon), "OS", version)); },
|
||||
[](const String& error) { ERROR_LOG("Failed to get OS version: {}", error); }
|
||||
);
|
||||
|
||||
expected_visit(
|
||||
data.mem_info,
|
||||
[&](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
|
||||
|
@ -300,19 +312,21 @@ namespace {
|
|||
|
||||
// Now Playing row
|
||||
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()) {
|
||||
const std::string& npText = *nowPlayingResult;
|
||||
const String& npText = *nowPlayingResult;
|
||||
|
||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||
content.push_back(hbox({
|
||||
text(string(musicIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(String(musicIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||
text("Playing") | color(ui::DEFAULT_THEME.label),
|
||||
text(" "),
|
||||
filler(),
|
||||
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, ui::MAX_PARAGRAPH_LENGTH),
|
||||
text(" "),
|
||||
}));
|
||||
}
|
||||
));
|
||||
} else {
|
||||
const NowPlayingError& error = nowPlayingResult.error();
|
||||
|
||||
|
@ -340,6 +354,11 @@ namespace {
|
|||
if (std::holds_alternative<WindowsError>(error))
|
||||
DEBUG_LOG("WinRT error: {}", to_string(std::get<WindowsError>(error).message()));
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (std::holds_alternative<MacError>(error))
|
||||
DEBUG_LOG("CoreAudio error: {}", std::get<MacError>(error));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#ifdef __FreeBSD__
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#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 char* dbusMethodListNames = "ListNames";
|
||||
|
||||
const std::unique_ptr<sdbus::IProxy> dbusProxy =
|
||||
createProxy(connection, dbusInterface, dbusObjectPath);
|
||||
const std::unique_ptr<sdbus::IProxy> dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath);
|
||||
|
||||
std::vector<string> names;
|
||||
|
||||
|
@ -58,8 +56,7 @@ fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector<string> {
|
|||
std::vector<string> mprisPlayers;
|
||||
|
||||
for (const std::basic_string<char>& name : names)
|
||||
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2";
|
||||
name.find(mprisInterfaceName) != std::string::npos)
|
||||
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != String::npos)
|
||||
mprisPlayers.push_back(name);
|
||||
|
||||
return mprisPlayers;
|
||||
|
@ -74,8 +71,7 @@ fn GetActivePlayer(const std::vector<string>& mprisPlayers) -> string {
|
|||
|
||||
fn GetNowPlaying() -> string {
|
||||
try {
|
||||
const char *playerObjectPath = "/org/mpris/MediaPlayer2",
|
||||
*playerInterfaceName = "org.mpris.MediaPlayer2.Player";
|
||||
const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player";
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
|
||||
|
||||
|
@ -89,24 +85,22 @@ fn GetNowPlaying() -> string {
|
|||
if (activePlayer.empty())
|
||||
return "";
|
||||
|
||||
auto playerProxy = sdbus::createProxy(
|
||||
*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath)
|
||||
);
|
||||
auto playerProxy =
|
||||
sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath));
|
||||
|
||||
sdbus::Variant metadataVariant =
|
||||
playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
|
||||
sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
|
||||
|
||||
if (metadataVariant.containsValueOfType<std::map<std::string, sdbus::Variant>>()) {
|
||||
const auto& metadata = metadataVariant.get<std::map<std::string, sdbus::Variant>>();
|
||||
if (metadataVariant.containsValueOfType<std::map<String, sdbus::Variant>>()) {
|
||||
const auto& metadata = metadataVariant.get<std::map<String, sdbus::Variant>>();
|
||||
|
||||
auto iter = metadata.find("xesam:title");
|
||||
|
||||
if (iter != metadata.end() && iter->second.containsValueOfType<std::string>())
|
||||
return iter->second.get<std::string>();
|
||||
if (iter != metadata.end() && iter->second.containsValueOfType<String>())
|
||||
return iter->second.get<String>();
|
||||
}
|
||||
} catch (const sdbus::Error& e) {
|
||||
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer")
|
||||
return fmt::format("Error: {}", e.what());
|
||||
return std::format("Error: {}", e.what());
|
||||
|
||||
return "No active player";
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <dirent.h>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
|
@ -24,7 +23,6 @@
|
|||
#include "os.h"
|
||||
#include "src/util/macros.h"
|
||||
|
||||
// Minimal global using declarations needed for function signatures
|
||||
using std::expected;
|
||||
using std::optional;
|
||||
|
||||
|
@ -32,7 +30,6 @@ namespace fs = std::filesystem;
|
|||
using namespace std::literals::string_view_literals;
|
||||
|
||||
namespace {
|
||||
// Local using declarations for the anonymous namespace
|
||||
using std::array;
|
||||
using std::bit_cast;
|
||||
using std::getenv;
|
||||
|
@ -121,7 +118,7 @@ namespace {
|
|||
|
||||
XCloseDisplay(display);
|
||||
|
||||
return "Unknown (X11)"; // Changed to empty string
|
||||
return "Unknown (X11)";
|
||||
}
|
||||
|
||||
fn TrimHyprlandWrapper(const string& input) -> string {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
#include <expected>
|
||||
#include <map>
|
||||
#include <flat_map>
|
||||
#include <span>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
|
@ -9,42 +10,41 @@
|
|||
#include "os.h"
|
||||
#include "src/util/types.h"
|
||||
|
||||
fn GetMemInfo() -> expected<u64, string> {
|
||||
fn GetMemInfo() -> expected<u64, String> {
|
||||
u64 mem = 0;
|
||||
usize size = sizeof(mem);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
std::array<char, 256> kernelVersion;
|
||||
fn GetKernelVersion() -> String {
|
||||
std::array<char, 256> 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();
|
||||
}
|
||||
|
||||
fn GetHost() -> string {
|
||||
std::array<char, 256> hwModel;
|
||||
fn GetHost() -> String {
|
||||
std::array<char, 256> hwModel {};
|
||||
size_t hwModelLen = sizeof(hwModel);
|
||||
|
||||
sysctlbyname("hw.model", hwModel.data(), &hwModelLen, nullptr, 0);
|
||||
|
||||
// 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
|
||||
std::map<std::string, std::string> modelNameByHwModel = {
|
||||
std::flat_map<std::string_view, std::string_view> modelNameByHwModel = {
|
||||
// MacBook Pro
|
||||
{ "MacBookPro18,3", "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)" },
|
||||
};
|
||||
|
||||
return modelNameByHwModel[hwModel.data()];
|
||||
return String(modelNameByHwModel[hwModel.data()]);
|
||||
}
|
||||
|
||||
// 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 };
|
||||
}
|
||||
|
||||
fn GetShell() -> string { return ""; }
|
||||
fn GetShell() -> String { return ""; }
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
#include "../../util/macros.h"
|
||||
|
||||
|
@ -13,14 +12,14 @@
|
|||
|
||||
@interface Bridge : NSObject
|
||||
+ (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion;
|
||||
+ (std::expected<string, string>)macOSVersion;
|
||||
+ (std::expected<String, String>)macOSVersion;
|
||||
@end
|
||||
|
||||
#else
|
||||
|
||||
extern "C++" {
|
||||
fn GetCurrentPlayingInfo() -> std::expected<std::string, NowPlayingError>;
|
||||
fn GetMacOSVersion() -> std::expected<std::string, const char*>;
|
||||
fn GetCurrentPlayingInfo() -> std::expected<String, NowPlayingError>;
|
||||
fn GetMacOSVersion() -> std::expected<String, String>;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
#import <dispatch/dispatch.h>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#import <objc/runtime.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#import "bridge.h"
|
||||
|
||||
|
@ -12,47 +15,51 @@ using MRMediaRemoteGetNowPlayingInfoFunction =
|
|||
|
||||
@implementation Bridge
|
||||
+ (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion {
|
||||
CFURLRef ref = CFURLCreateWithFileSystemPath(
|
||||
CFURLRef urlRef = CFURLCreateWithFileSystemPath(
|
||||
kCFAllocatorDefault, CFSTR("/System/Library/PrivateFrameworks/MediaRemote.framework"), kCFURLPOSIXPathStyle, false
|
||||
);
|
||||
|
||||
if (!ref) {
|
||||
if (!urlRef) {
|
||||
completion(std::unexpected("Failed to create CFURL for MediaRemote framework"));
|
||||
return;
|
||||
}
|
||||
|
||||
CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, ref);
|
||||
CFRelease(ref);
|
||||
CFBundleRef bundleRef = CFBundleCreate(kCFAllocatorDefault, urlRef);
|
||||
|
||||
if (!bundle) {
|
||||
CFRelease(urlRef);
|
||||
|
||||
if (!bundleRef) {
|
||||
completion(std::unexpected("Failed to create bundle for MediaRemote framework"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto mrMediaRemoteGetNowPlayingInfo = std::bit_cast<MRMediaRemoteGetNowPlayingInfoFunction>(
|
||||
CFBundleGetFunctionPointerForName(bundle, CFSTR("MRMediaRemoteGetNowPlayingInfo"))
|
||||
CFBundleGetFunctionPointerForName(bundleRef, CFSTR("MRMediaRemoteGetNowPlayingInfo"))
|
||||
);
|
||||
|
||||
if (!mrMediaRemoteGetNowPlayingInfo) {
|
||||
CFRelease(bundle);
|
||||
CFRelease(bundleRef);
|
||||
completion(std::unexpected("Failed to get MRMediaRemoteGetNowPlayingInfo function pointer"));
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::remove_pointer_t<CFBundleRef>> sharedBundle(bundleRef, [](CFBundleRef bundle) {
|
||||
if (bundle)
|
||||
CFRelease(bundle);
|
||||
});
|
||||
|
||||
mrMediaRemoteGetNowPlayingInfo(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
^(NSDictionary* information) {
|
||||
NSDictionary* nowPlayingInfo = information; // Immutable, no copy needed
|
||||
CFRelease(bundle);
|
||||
completion(
|
||||
nowPlayingInfo ? std::expected<NSDictionary*, const char*>(nowPlayingInfo)
|
||||
information ? std::expected<NSDictionary*, const char*>(information)
|
||||
: std::unexpected("No now playing information")
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
+ (std::expected<string, string>)macOSVersion {
|
||||
+ (std::expected<String, String>)macOSVersion {
|
||||
NSProcessInfo* processInfo = [NSProcessInfo processInfo];
|
||||
NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion];
|
||||
|
||||
|
@ -71,14 +78,14 @@ using MRMediaRemoteGetNowPlayingInfoFunction =
|
|||
NSString* versionName = versionNames[majorVersion] ? versionNames[majorVersion] : @"Unknown";
|
||||
|
||||
NSString* fullVersion = [NSString stringWithFormat:@"macOS %@ %@", versionNumber, versionName];
|
||||
return std::string([fullVersion UTF8String]);
|
||||
return String([fullVersion UTF8String]);
|
||||
}
|
||||
@end
|
||||
|
||||
extern "C++" {
|
||||
// NOLINTBEGIN(misc-use-internal-linkage)
|
||||
fn GetCurrentPlayingInfo() -> std::expected<std::string, NowPlayingError> {
|
||||
__block std::expected<std::string, NowPlayingError> result;
|
||||
fn GetCurrentPlayingInfo() -> std::expected<String, NowPlayingError> {
|
||||
__block std::expected<String, NowPlayingError> result;
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
|
||||
[Bridge fetchCurrentPlayingMetadata:^(std::expected<NSDictionary*, const char*> metadataResult) {
|
||||
|
@ -95,17 +102,17 @@ extern "C++" {
|
|||
return;
|
||||
}
|
||||
|
||||
NSString* title = [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoTitle"];
|
||||
NSString* artist = [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoArtist"];
|
||||
NSString* title = metadata[@"kMRMediaRemoteNowPlayingInfoTitle"];
|
||||
NSString* artist = metadata[@"kMRMediaRemoteNowPlayingInfoArtist"];
|
||||
|
||||
if (!title && !artist)
|
||||
result = std::unexpected("No metadata");
|
||||
result = std::unexpected(NowPlayingError { "No metadata" });
|
||||
else if (!title)
|
||||
result = std::string([artist UTF8String]);
|
||||
result = String([artist UTF8String]);
|
||||
else if (!artist)
|
||||
result = std::string([title UTF8String]);
|
||||
result = String([title UTF8String]);
|
||||
else
|
||||
result = std::string([[NSString stringWithFormat:@"%@ - %@", title, artist] UTF8String]);
|
||||
result = String([[NSString stringWithFormat:@"%@ - %@", title, artist] UTF8String]);
|
||||
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
@ -114,7 +121,7 @@ extern "C++" {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
16
src/os/os.h
16
src/os/os.h
|
@ -10,42 +10,42 @@ using std::optional, std::expected;
|
|||
/**
|
||||
* @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.
|
||||
*/
|
||||
fn GetNowPlaying() -> expected<string, NowPlayingError>;
|
||||
fn GetNowPlaying() -> expected<String, NowPlayingError>;
|
||||
|
||||
/**
|
||||
* @brief Get the OS version.
|
||||
*/
|
||||
fn GetOSVersion() -> expected<string, string>;
|
||||
fn GetOSVersion() -> expected<String, String>;
|
||||
|
||||
/**
|
||||
* @brief Get the current desktop environment.
|
||||
*/
|
||||
fn GetDesktopEnvironment() -> optional<string>;
|
||||
fn GetDesktopEnvironment() -> optional<String>;
|
||||
|
||||
/**
|
||||
* @brief Get the current window manager.
|
||||
*/
|
||||
fn GetWindowManager() -> string;
|
||||
fn GetWindowManager() -> String;
|
||||
|
||||
/**
|
||||
* @brief Get the current shell.
|
||||
*/
|
||||
fn GetShell() -> string;
|
||||
fn GetShell() -> String;
|
||||
|
||||
/**
|
||||
* @brief Get the product family
|
||||
*/
|
||||
fn GetHost() -> string;
|
||||
fn GetHost() -> String;
|
||||
|
||||
/**
|
||||
* @brief Get the kernel version.
|
||||
*/
|
||||
fn GetKernelVersion() -> string;
|
||||
fn GetKernelVersion() -> String;
|
||||
|
||||
/**
|
||||
* @brief Get the number of installed packages.
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
// probably stupid but it fixes the issue with windows.h defining ERROR
|
||||
#ifdef _WIN32
|
||||
#undef ERROR
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
|
@ -11,18 +13,15 @@
|
|||
|
||||
#include "types.h"
|
||||
|
||||
#define fn auto // Rust-style function shorthand
|
||||
#define fn auto
|
||||
|
||||
// Terminal color implementation to replace fmt::color
|
||||
namespace term {
|
||||
// Text styles
|
||||
enum class Emphasis : u8 { none = 0, bold = 1, italic = 2 };
|
||||
|
||||
constexpr fn operator|(Emphasis emphA, Emphasis emphB)->Emphasis {
|
||||
return static_cast<Emphasis>(static_cast<int>(emphA) | static_cast<int>(emphB));
|
||||
}
|
||||
|
||||
// Terminal colors
|
||||
enum class Color : u8 {
|
||||
black = 30,
|
||||
red = 31,
|
||||
|
@ -42,70 +41,57 @@ namespace term {
|
|||
bright_white = 97
|
||||
};
|
||||
|
||||
// Style wrapper for foreground color
|
||||
struct FgColor {
|
||||
Color col;
|
||||
|
||||
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); }
|
||||
|
||||
// Combined style (emphasis + color)
|
||||
struct Style {
|
||||
Emphasis emph = Emphasis::none;
|
||||
FgColor fg_col = FgColor(static_cast<Color>(-1)); // Invalid color
|
||||
|
||||
[[nodiscard]] fn ansiCode() const -> std::string {
|
||||
std::string result;
|
||||
[[nodiscard]] fn ansiCode() const -> String {
|
||||
String result;
|
||||
|
||||
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";
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<int>(fg_col.col) != -1) {
|
||||
if (static_cast<int>(fg_col.col) != -1)
|
||||
result += fg_col.ansiCode();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Combine emphasis and color
|
||||
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";
|
||||
|
||||
// Print with style
|
||||
template <typename... Args>
|
||||
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);
|
||||
}
|
||||
|
||||
// Print with foreground color only
|
||||
template <typename... Args>
|
||||
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);
|
||||
}
|
||||
|
||||
// Print with emphasis only
|
||||
template <typename... Args>
|
||||
fn Print(Emphasis emph, std::format_string<Args...> fmt, Args&&... args) -> void {
|
||||
Print({ .emph = emph }, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Print without styling (plain text)
|
||||
template <typename... Args>
|
||||
fn Print(std::format_string<Args...> fmt, Args&&... args) -> void {
|
||||
std::print(fmt, std::forward<Args>(args)...);
|
||||
|
@ -114,15 +100,20 @@ namespace term {
|
|||
|
||||
namespace log_colors {
|
||||
using term::Color;
|
||||
constexpr auto debug = Color::cyan, info = Color::green, warn = Color::yellow, error = Color::red,
|
||||
|
||||
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 };
|
||||
|
||||
template <typename... Args>
|
||||
void 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());
|
||||
fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_string<Args...> fmt, Args&&... args)
|
||||
-> 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] = [&] {
|
||||
switch (level) {
|
||||
|
@ -141,19 +132,21 @@ void LogImpl(const LogLevel level, const std::source_location& loc, std::format_
|
|||
#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
|
||||
term::Print(term::Fg(log_colors::timestamp), "[{:%H:%M:%S}] ", now);
|
||||
term::Print(term::Emphasis::bold | term::Fg(color), "{} ", levelStr);
|
||||
// Message
|
||||
term::Print(fmt, std::forward<Args>(args)...);
|
||||
// File info (debug builds only)
|
||||
const String filename = std::filesystem::path(loc.file_name()).lexically_normal().string();
|
||||
|
||||
using namespace term;
|
||||
|
||||
Print(Fg(log_colors::timestamp), "[{:%H:%M:%S}] ", now);
|
||||
Print(Emphasis::bold | Fg(color), "{} ", levelStr);
|
||||
Print(fmt, std::forward<Args>(args)...);
|
||||
|
||||
#ifndef NDEBUG
|
||||
term::Print(term::Fg(log_colors::file_info), "\n{:>14} ", "╰──");
|
||||
term::Print(term::Emphasis::italic | term::Fg(log_colors::file_info), "{}:{}", filename, loc.line());
|
||||
Print(Fg(log_colors::file_info), "\n{:>14} ", "╰──");
|
||||
Print(Emphasis::italic | Fg(log_colors::file_info), "{}:{}", filename, loc.line());
|
||||
#endif
|
||||
term::Print("\n");
|
||||
|
||||
Print("\n");
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
|
@ -133,7 +134,7 @@ using isize = std::ptrdiff_t;
|
|||
* @typedef string
|
||||
* @brief Represents a string.
|
||||
*/
|
||||
using string = std::string;
|
||||
using String = std::string;
|
||||
|
||||
/**
|
||||
* @enum NowPlayingCode
|
||||
|
@ -150,13 +151,13 @@ enum class NowPlayingCode : u8 {
|
|||
* @typedef LinuxError
|
||||
* @brief Represents a Linux-specific error.
|
||||
*/
|
||||
using LinuxError = std::string;
|
||||
using LinuxError = String;
|
||||
#elif defined(__APPLE__)
|
||||
/**
|
||||
* @typedef MacError
|
||||
* @brief Represents a macOS-specific error.
|
||||
*/
|
||||
using MacError = std::string;
|
||||
using MacError = String;
|
||||
#elif defined(_WIN32)
|
||||
/**
|
||||
* @typedef WindowsError
|
||||
|
@ -179,7 +180,7 @@ using NowPlayingError = std::variant<
|
|||
|
||||
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
|
||||
char* rawPtr = nullptr;
|
||||
size_t bufferSize = 0;
|
||||
|
@ -190,7 +191,7 @@ inline auto GetEnv(const std::string& name) -> std::expected<std::string, EnvErr
|
|||
if (!rawPtr)
|
||||
return std::unexpected(EnvError::NotFound);
|
||||
|
||||
const std::string result(rawPtr);
|
||||
const String result(rawPtr);
|
||||
free(rawPtr);
|
||||
return result;
|
||||
#else
|
||||
|
@ -198,6 +199,6 @@ inline auto GetEnv(const std::string& name) -> std::expected<std::string, EnvErr
|
|||
if (!value)
|
||||
return std::unexpected(EnvError::NotFound);
|
||||
|
||||
return std::string(value);
|
||||
return String(value);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -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
|
Loading…
Add table
Reference in a new issue