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": {
|
"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": {
|
||||||
|
|
11
flake.nix
11
flake.nix
|
@ -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
|
||||||
]));
|
]));
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
141
src/main.cpp
141
src/main.cpp
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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.
|
* @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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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