i think? this is better??

This commit is contained in:
Mars 2025-03-11 15:56:54 -04:00
parent b3e79b56f7
commit c3b829b68f
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
11 changed files with 413 additions and 678 deletions

View file

@ -20,48 +20,6 @@
}, },
"version": "11.1.4" "version": "11.1.4"
}, },
"reflect-cpp": {
"cargoLocks": null,
"date": "2025-03-02",
"extract": null,
"name": "reflect-cpp",
"passthru": null,
"pinned": false,
"src": {
"deepClone": false,
"fetchSubmodules": false,
"leaveDotGit": false,
"name": null,
"owner": "getml",
"repo": "reflect-cpp",
"rev": "ec8c19fa3e931d736b3f3ff2e400fce4a5f97829",
"sha256": "sha256-3bbaVbU9ICQ8no/3W4M8ePsnrZR3e3CWhT3RO3lL3r0=",
"sparseCheckout": [],
"type": "github"
},
"version": "ec8c19fa3e931d736b3f3ff2e400fce4a5f97829"
},
"sdbus-cpp": {
"cargoLocks": null,
"date": null,
"extract": null,
"name": "sdbus-cpp",
"passthru": null,
"pinned": false,
"src": {
"deepClone": false,
"fetchSubmodules": false,
"leaveDotGit": false,
"name": null,
"owner": "kistler-group",
"repo": "sdbus-cpp",
"rev": "v2.1.0",
"sha256": "sha256-JnjabBr7oELLsUV9a+dAAaRyUzaMIriu90vkaVJg2eY=",
"sparseCheckout": [],
"type": "github"
},
"version": "v2.1.0"
},
"tomlplusplus": { "tomlplusplus": {
"cargoLocks": null, "cargoLocks": null,
"date": null, "date": null,
@ -82,26 +40,5 @@
"type": "github" "type": "github"
}, },
"version": "v3.4.0" "version": "v3.4.0"
},
"yyjson": {
"cargoLocks": null,
"date": null,
"extract": null,
"name": "yyjson",
"passthru": null,
"pinned": false,
"src": {
"deepClone": false,
"fetchSubmodules": false,
"leaveDotGit": false,
"name": null,
"owner": "ibireme",
"repo": "yyjson",
"rev": "0.10.0",
"sha256": "sha256-mp9Oz08qTyhj3P6F1d81SX96vamUY/JWpD2DTYR+v04=",
"sparseCheckout": [],
"type": "github"
},
"version": "0.10.0"
} }
} }

View file

@ -12,29 +12,6 @@
sha256 = "sha256-sUbxlYi/Aupaox3JjWFqXIjcaQa0LFjclQAOleT+FRA="; sha256 = "sha256-sUbxlYi/Aupaox3JjWFqXIjcaQa0LFjclQAOleT+FRA=";
}; };
}; };
reflect-cpp = {
pname = "reflect-cpp";
version = "ec8c19fa3e931d736b3f3ff2e400fce4a5f97829";
src = fetchFromGitHub {
owner = "getml";
repo = "reflect-cpp";
rev = "ec8c19fa3e931d736b3f3ff2e400fce4a5f97829";
fetchSubmodules = false;
sha256 = "sha256-3bbaVbU9ICQ8no/3W4M8ePsnrZR3e3CWhT3RO3lL3r0=";
};
date = "2025-03-02";
};
sdbus-cpp = {
pname = "sdbus-cpp";
version = "v2.1.0";
src = fetchFromGitHub {
owner = "kistler-group";
repo = "sdbus-cpp";
rev = "v2.1.0";
fetchSubmodules = false;
sha256 = "sha256-JnjabBr7oELLsUV9a+dAAaRyUzaMIriu90vkaVJg2eY=";
};
};
tomlplusplus = { tomlplusplus = {
pname = "tomlplusplus"; pname = "tomlplusplus";
version = "v3.4.0"; version = "v3.4.0";
@ -46,15 +23,4 @@
sha256 = "sha256-h5tbO0Rv2tZezY58yUbyRVpsfRjY3i+5TPkkxr6La8M="; sha256 = "sha256-h5tbO0Rv2tZezY58yUbyRVpsfRjY3i+5TPkkxr6La8M=";
}; };
}; };
yyjson = {
pname = "yyjson";
version = "0.10.0";
src = fetchFromGitHub {
owner = "ibireme";
repo = "yyjson";
rev = "0.10.0";
fetchSubmodules = false;
sha256 = "sha256-mp9Oz08qTyhj3P6F1d81SX96vamUY/JWpD2DTYR+v04=";
};
};
} }

192
flake.lock generated
View file

@ -1,96 +1,96 @@
{ {
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1739863612, "lastModified": 1741678040,
"narHash": "sha256-UbtgxplOhFcyjBcNbTVO8+HUHAl/WXFDOb6LvqShiZo=", "narHash": "sha256-rmBsz7BBcDwfvDkxnKHmolKceGJrr0nyz5PQYZg0kMk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "632f04521e847173c54fa72973ec6c39a371211c", "rev": "3ee8818da146871cd570b164fc4f438f78479a50",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixpkgs-unstable", "ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1735554305, "lastModified": 1735554305,
"narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=", "narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd", "rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "nixpkgs-unstable", "ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"treefmt-nix": "treefmt-nix", "treefmt-nix": "treefmt-nix",
"utils": "utils" "utils": "utils"
} }
}, },
"systems": { "systems": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems", "owner": "nix-systems",
"repo": "default", "repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-systems", "owner": "nix-systems",
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
}, },
"treefmt-nix": { "treefmt-nix": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1739829690, "lastModified": 1739829690,
"narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=", "narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "3d0579f5cc93436052d94b73925b48973a104204", "rev": "3d0579f5cc93436052d94b73925b48973a104204",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"type": "github" "type": "github"
} }
}, },
"utils": { "utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"type": "github" "type": "github"
} }
} }
}, },
"root": "root", "root": "root",
"version": 7 "version": 7
} }

286
flake.nix
View file

@ -1,143 +1,143 @@
{ {
description = "C/C++ environment"; description = "C/C++ environment";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
treefmt-nix.url = "github:numtide/treefmt-nix"; treefmt-nix.url = "github:numtide/treefmt-nix";
utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
}; };
outputs = { outputs = {
self, self,
nixpkgs, nixpkgs,
treefmt-nix, treefmt-nix,
utils, utils,
... ...
}: }:
utils.lib.eachDefaultSystem ( utils.lib.eachDefaultSystem (
system: let system: let
pkgs = import nixpkgs {inherit system;}; pkgs = import nixpkgs {inherit system;};
llvmPackages = with pkgs; llvmPackages = with pkgs;
if hostPlatform.isLinux if hostPlatform.isLinux
then llvmPackages_20 then llvmPackages_20
else llvmPackages_19; else llvmPackages_19;
stdenv = with pkgs; stdenv = with pkgs;
( (
if hostPlatform.isLinux if hostPlatform.isLinux
then stdenvAdapters.useMoldLinker then stdenvAdapters.useMoldLinker
else lib.id else lib.id
) )
llvmPackages.stdenv; llvmPackages.stdenv;
sources = import ./_sources/generated.nix { sources = import ./_sources/generated.nix {
inherit (pkgs) fetchFromGitHub fetchgit fetchurl dockerTools; inherit (pkgs) fetchFromGitHub fetchgit fetchurl dockerTools;
}; };
fmt = pkgs.pkgsStatic.fmt.overrideAttrs (old: { fmt = pkgs.pkgsStatic.fmt.overrideAttrs (old: {
inherit (sources.fmt) pname version src; inherit (sources.fmt) pname version src;
}); });
tomlplusplus = pkgs.pkgsStatic.tomlplusplus.overrideAttrs { tomlplusplus = pkgs.pkgsStatic.tomlplusplus.overrideAttrs {
inherit (sources.tomlplusplus) pname version src; inherit (sources.tomlplusplus) pname version src;
doCheck = false; doCheck = false;
}; };
deps = with pkgs.pkgsStatic; deps = with pkgs.pkgsStatic;
[ [
curl curl
fmt fmt
libiconv ftxui
tomlplusplus libiconv
nlohmann_json sqlitecpp
sqlitecpp tomlplusplus
ftxui ]
] ++ linuxPkgs;
++ linuxPkgs;
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs; [
[ valgrind
valgrind (glaze.override {enableAvx2 = true;})
] ]
++ (with pkgsStatic; [ ++ (with pkgsStatic; [
dbus dbus
xorg.libX11 xorg.libX11
wayland wayland
])); ]));
in in
with pkgs; { with pkgs; {
packages = rec { packages = rec {
draconisplusplus = stdenv.mkDerivation { draconisplusplus = stdenv.mkDerivation {
name = "draconis++"; name = "draconis++";
version = "0.1.0"; version = "0.1.0";
src = self; src = self;
nativeBuildInputs = [ nativeBuildInputs = [
cmake cmake
meson meson
ninja ninja
pkg-config pkg-config
]; ];
buildInputs = deps; buildInputs = deps;
configurePhase = '' configurePhase = ''
meson setup build meson setup build
''; '';
buildPhase = '' buildPhase = ''
meson compile -C build meson compile -C build
''; '';
installPhase = '' installPhase = ''
mkdir -p $out/bin mkdir -p $out/bin
mv build/draconis++ $out/bin/draconis++ mv build/draconis++ $out/bin/draconis++
''; '';
}; };
default = draconisplusplus; default = draconisplusplus;
}; };
formatter = treefmt-nix.lib.mkWrapper pkgs { formatter = treefmt-nix.lib.mkWrapper pkgs {
projectRootFile = "flake.nix"; projectRootFile = "flake.nix";
programs = { programs = {
alejandra.enable = true; alejandra.enable = true;
deadnix.enable = true; deadnix.enable = true;
clang-format = { clang-format = {
enable = true; enable = true;
package = pkgs.llvmPackages.clang-tools; package = pkgs.llvmPackages.clang-tools;
}; };
}; };
}; };
devShell = mkShell.override {inherit stdenv;} { devShell = mkShell.override {inherit stdenv;} {
packages = packages =
[ [
alejandra alejandra
bear bear
llvmPackages.clang-tools llvmPackages.clang-tools
cmake cmake
lldb lldb
hyperfine hyperfine
meson meson
ninja ninja
nvfetcher nvfetcher
pkg-config pkg-config
unzip unzip
(writeScriptBin "build" "meson compile -C build") (writeScriptBin "build" "meson compile -C build")
(writeScriptBin "clean" "meson setup build --wipe") (writeScriptBin "clean" "meson setup build --wipe")
(writeScriptBin "run" "meson compile -C build && build/draconis++") (writeScriptBin "run" "meson compile -C build && build/draconis++")
] ]
++ deps; ++ deps;
LD_LIBRARY_PATH = "${lib.makeLibraryPath deps}"; LD_LIBRARY_PATH = "${lib.makeLibraryPath deps}";
NIX_ENFORCE_NO_NATIVE = 0; NIX_ENFORCE_NO_NATIVE = 0;
name = "C++"; name = "C++";
}; };
} }
); );
} }

View file

@ -8,7 +8,7 @@ project(
default_options: [ default_options: [
'default_library=static', 'default_library=static',
'warning_level=everything', 'warning_level=everything',
'buildtype=release', 'buildtype=debugoptimized',
], ],
) )
@ -98,7 +98,7 @@ common_deps = [
dependency('fmt', include_type: 'system', static: true), dependency('fmt', include_type: 'system', static: true),
dependency('libcurl', include_type: 'system', static: true), dependency('libcurl', include_type: 'system', static: true),
dependency('tomlplusplus', include_type: 'system', static: true), dependency('tomlplusplus', include_type: 'system', static: true),
dependency('nlohmann_json', include_type: 'system', static: true), dependency('glaze'),
dependency('openssl', include_type: 'system', static: true, required: false), dependency('openssl', include_type: 'system', static: true, required: false),
] ]
@ -122,13 +122,12 @@ elif host_system == 'windows'
elif host_system == 'linux' or host_system == 'freebsd' elif host_system == 'linux' or host_system == 'freebsd'
platform_deps += [ platform_deps += [
dependency('SQLiteCpp'), dependency('SQLiteCpp'),
dependency('sdbus-c++'),
dependency('x11'), dependency('x11'),
dependency('xcb'), dependency('xcb'),
dependency('xau'), dependency('xau'),
dependency('xdmcp'), dependency('xdmcp'),
dependency('wayland-client'), dependency('wayland-client'),
dependency('dbus-1'), dependency('dbus-1', include_type: 'system'),
] ]
endif endif
@ -164,7 +163,7 @@ objc_args = []
if host_system == 'darwin' if host_system == 'darwin'
objc_args += ['-fobjc-arc'] objc_args += ['-fobjc-arc']
elif cpp.get_id() == 'clang' elif cpp.get_id() == 'clang'
link_args += ['-static-libgcc', '-static-libstdc++', '-static'] link_args += ['-static-libgcc', '-static-libstdc++']
endif endif
# ------------------- # # ------------------- #

View file

@ -2,18 +2,6 @@
src.github = "fmtlib/fmt" src.github = "fmtlib/fmt"
fetch.github = "fmtlib/fmt" fetch.github = "fmtlib/fmt"
[reflect-cpp]
src.git = "https://github.com/getml/reflect-cpp"
fetch.github = "getml/reflect-cpp"
[sdbus-cpp]
src.github = "kistler-group/sdbus-cpp"
fetch.github = "kistler-group/sdbus-cpp"
[tomlplusplus] [tomlplusplus]
src.github = "marzer/tomlplusplus" src.github = "marzer/tomlplusplus"
fetch.github = "marzer/tomlplusplus" fetch.github = "marzer/tomlplusplus"
[yyjson]
src.github = "ibireme/yyjson"
fetch.github = "ibireme/yyjson"

View file

@ -12,56 +12,59 @@
#include "src/util/macros.h" #include "src/util/macros.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std::string_literals;
using namespace nlohmann; using namespace nlohmann;
using namespace std::string_literals;
// Alias for cleaner error handling
template <typename T> template <typename T>
using Result = std::expected<T, std::string>; using Result = std::expected<T, std::string>;
namespace { namespace {
// Common function to get cache path constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false };
fn GetCachePath() -> Result<fs::path> { fn GetCachePath() -> Result<fs::path> {
std::error_code errc; std::error_code errc;
fs::path cachePath = fs::temp_directory_path(errc); fs::path cachePath = fs::temp_directory_path(errc);
if (errc) if (errc)
return std::unexpected("Failed to get temp directory: "s + errc.message()); return std::unexpected("Failed to get temp directory: " + errc.message());
cachePath /= "weather_cache.json"; cachePath /= "weather_cache.json";
return cachePath; return cachePath;
} }
// Function to read cache from file
fn ReadCacheFromFile() -> Result<WeatherOutput> { fn ReadCacheFromFile() -> Result<WeatherOutput> {
Result<fs::path> cachePath = GetCachePath(); Result<fs::path> cachePath = GetCachePath();
if (!cachePath) if (!cachePath)
return std::unexpected(cachePath.error()); return std::unexpected(cachePath.error());
std::ifstream ifs(*cachePath, std::ios::binary); std::ifstream ifs(*cachePath, std::ios::binary);
if (!ifs.is_open()) if (!ifs.is_open())
return std::unexpected("Cache file not found: "s + cachePath->string()); return std::unexpected("Cache file not found: " + cachePath->string());
DEBUG_LOG("Reading from cache file..."); DEBUG_LOG("Reading from cache file...");
try { try {
const std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); const std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
json json = json::parse(content); WeatherOutput result;
WeatherOutput result = json.get<WeatherOutput>(); glz::error_ctx errc = glz::read<glaze_opts>(result, content);
if (errc.ec != glz::error_code::none)
return std::unexpected("JSON parse error: " + glz::format_error(errc, content));
DEBUG_LOG("Successfully read from cache file."); DEBUG_LOG("Successfully read from cache file.");
return result; return result;
} catch (const std::exception& e) { return std::unexpected("JSON parse error: "s + e.what()); } } catch (const std::exception& e) { return std::unexpected("Error reading cache: "s + e.what()); }
} }
// Function to write cache to file
fn WriteCacheToFile(const WeatherOutput& data) -> Result<void> { fn WriteCacheToFile(const WeatherOutput& data) -> Result<void> {
Result<fs::path> cachePath = GetCachePath(); Result<fs::path> cachePath = GetCachePath();
if (!cachePath) if (!cachePath)
return std::unexpected(cachePath.error()); return std::unexpected(cachePath.error());
DEBUG_LOG("Writing to cache file..."); DEBUG_LOG("Writing to cache file...");
fs::path tempPath = *cachePath; fs::path tempPath = *cachePath;
tempPath += ".tmp"; tempPath += ".tmp";
@ -69,26 +72,29 @@ namespace {
{ {
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open()) if (!ofs.is_open())
return std::unexpected("Failed to open temp file: "s + tempPath.string()); return std::unexpected("Failed to open temp file: " + tempPath.string());
json json = data; std::string jsonStr;
ofs << json.dump(); glz::error_ctx errc = glz::write_json(data, jsonStr);
if (errc.ec != glz::error_code::none)
return std::unexpected("JSON serialization error: " + glz::format_error(errc, jsonStr));
ofs << jsonStr;
if (!ofs) if (!ofs)
return std::unexpected("Failed to write to temp file"); return std::unexpected("Failed to write to temp file");
} }
std::error_code errc; std::error_code errc;
fs::rename(tempPath, *cachePath, errc); fs::rename(tempPath, *cachePath, errc);
if (errc) { if (errc) {
fs::remove(tempPath, errc); fs::remove(tempPath, errc);
return std::unexpected("Failed to replace cache file: "s + errc.message()); return std::unexpected("Failed to replace cache file: " + errc.message());
} }
DEBUG_LOG("Successfully wrote to cache file."); DEBUG_LOG("Successfully wrote to cache file.");
return {}; return {};
} catch (const std::exception& e) { return std::unexpected("JSON serialization 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, std::string* str) -> size_t { fn WriteCallback(void* contents, const size_t size, const size_t nmemb, std::string* str) -> size_t {
@ -97,10 +103,8 @@ namespace {
return totalSize; return totalSize;
} }
// Function to make API request fn MakeApiRequest(const std::string& url) -> const Result<WeatherOutput> {
fn MakeApiRequest(const std::string& url) -> Result<WeatherOutput> {
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();
std::string responseBuffer; std::string responseBuffer;
@ -119,37 +123,38 @@ namespace {
if (res != CURLE_OK) if (res != CURLE_OK)
return std::unexpected(fmt::format("cURL error: {}", curl_easy_strerror(res))); return std::unexpected(fmt::format("cURL error: {}", curl_easy_strerror(res)));
DEBUG_LOG("API response size: {}", responseBuffer.size()); WeatherOutput output;
DEBUG_LOG("API response: {}", responseBuffer); glz::error_ctx errc = glz::read<glaze_opts>(output, responseBuffer);
try { if (errc.ec != glz::error_code::none)
json json = json::parse(responseBuffer); return std::unexpected("API response parse error: " + glz::format_error(errc, responseBuffer));
WeatherOutput output = json.get<WeatherOutput>();
return output; return std::move(output);
} catch (const std::exception& e) { return std::unexpected("API response parse error: "s + e.what()); }
} }
} }
// Core function to get weather information
fn Weather::getWeatherInfo() const -> WeatherOutput { fn Weather::getWeatherInfo() const -> WeatherOutput {
using namespace std::chrono; using namespace std::chrono;
if (Result<WeatherOutput> data = ReadCacheFromFile()) { if (Result<WeatherOutput> data = ReadCacheFromFile()) {
const WeatherOutput& dataVal = *data; const WeatherOutput& dataVal = *data;
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)); if (cacheAge < 10min) {
cacheAge < 10min) {
DEBUG_LOG("Using valid cache"); DEBUG_LOG("Using valid cache");
return dataVal; return dataVal;
} }
DEBUG_LOG("Cache expired"); DEBUG_LOG("Cache expired");
} else { } else {
DEBUG_LOG("Cache error: {}", data.error()); DEBUG_LOG("Cache error: {}", data.error());
} }
fn handleApiResult = [](const Result<WeatherOutput>& result) -> WeatherOutput { fn handleApiResult = [](const Result<WeatherOutput>& result) -> WeatherOutput {
if (!result) if (!result) {
ERROR_LOG("API request failed: {}", result.error()); ERROR_LOG("API request failed: {}", result.error());
return WeatherOutput {};
}
if (Result<void> writeResult = WriteCacheToFile(*result); !writeResult) if (Result<void> writeResult = WriteCacheToFile(*result); !writeResult)
ERROR_LOG("Failed to write cache: {}", writeResult.error()); ERROR_LOG("Failed to write cache: {}", writeResult.error());
@ -160,8 +165,8 @@ fn Weather::getWeatherInfo() const -> WeatherOutput {
if (std::holds_alternative<std::string>(location)) { if (std::holds_alternative<std::string>(location)) {
const auto& city = std::get<std::string>(location); const auto& city = std::get<std::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 std::string apiUrl = const std::string apiUrl =
fmt::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, api_key, units); fmt::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, api_key, units);

View file

@ -1,51 +1,27 @@
#pragma once #pragma once
#include <nlohmann/json.hpp> #include <glaze/glaze.hpp>
#include "../util/types.h" #include "../util/types.h"
using degrees = unsigned short; // 0-360, validate manually // NOLINTBEGIN(readability-identifier-naming)
using percentage = unsigned char; // 0-100, validate manually
struct Condition { struct Condition {
std::string description; std::string description;
std::string icon;
std::string main; struct glaze {
usize id; using T = Condition;
static constexpr auto value = glz::object("description", &T::description);
};
}; };
struct Main { struct Main {
f64 feels_like; f64 temp;
f64 temp;
f64 temp_max;
f64 temp_min;
isize pressure;
percentage humidity;
std::optional<isize> grnd_level;
std::optional<isize> sea_level;
};
struct Wind { struct glaze {
degrees deg; using T = Main;
f64 speed; static constexpr auto value = glz::object("temp", &T::temp);
std::optional<f64> gust; };
};
struct Precipitation {
std::optional<f64> one_hour;
std::optional<f64> three_hours;
};
struct Sys {
std::string country;
usize id;
usize sunrise;
usize sunset;
usize type;
};
struct Clouds {
percentage all;
}; };
struct Coords { struct Coords {
@ -54,204 +30,15 @@ struct Coords {
}; };
struct WeatherOutput { struct WeatherOutput {
Clouds clouds; Main main;
isize timezone; std::string name;
isize visibility; std::vector<Condition> weather;
Main main; usize dt;
Coords coords; // JSON key: "coord"
std::optional<Precipitation> rain; struct glaze {
std::optional<Precipitation> snow; using T = WeatherOutput;
std::string base; static constexpr auto value = glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt);
std::string name; };
std::vector<Condition> weather;
Sys sys;
usize cod;
usize dt;
usize id;
Wind wind;
}; };
// JSON Serialization Definitions
// NOLINTBEGIN(readability-identifier-naming)
namespace nlohmann {
using namespace nlohmann;
template <>
struct adl_serializer<Condition> {
static void to_json(json& jsonOut, const Condition& cond) {
jsonOut = json {
{ "description", cond.description },
{ "icon", cond.icon },
{ "main", cond.main },
{ "id", cond.id }
};
}
static void from_json(const json& jsonIn, Condition& cond) {
jsonIn.at("description").get_to(cond.description);
jsonIn.at("icon").get_to(cond.icon);
jsonIn.at("main").get_to(cond.main);
jsonIn.at("id").get_to(cond.id);
}
};
template <>
struct adl_serializer<Main> {
static void to_json(json& jsonOut, const Main& main) {
jsonOut = json {
{ "feels_like", main.feels_like },
{ "temp", main.temp },
{ "temp_max", main.temp_max },
{ "temp_min", main.temp_min },
{ "pressure", main.pressure },
{ "humidity", main.humidity }
};
if (main.grnd_level)
jsonOut["grnd_level"] = *main.grnd_level;
if (main.sea_level)
jsonOut["sea_level"] = *main.sea_level;
}
static void from_json(const json& jsonIn, Main& main) {
jsonIn.at("feels_like").get_to(main.feels_like);
jsonIn.at("temp").get_to(main.temp);
jsonIn.at("temp_max").get_to(main.temp_max);
jsonIn.at("temp_min").get_to(main.temp_min);
jsonIn.at("pressure").get_to(main.pressure);
jsonIn.at("humidity").get_to(main.humidity);
if (jsonIn.contains("grnd_level"))
main.grnd_level = jsonIn["grnd_level"].get<isize>();
if (jsonIn.contains("sea_level"))
main.sea_level = jsonIn["sea_level"].get<isize>();
}
};
template <>
struct adl_serializer<Wind> {
static void to_json(json& jsonOut, const Wind& wind) {
jsonOut = json {
{ "deg", wind.deg },
{ "speed", wind.speed }
};
if (wind.gust)
jsonOut["gust"] = *wind.gust;
}
static void from_json(const json& jsonIn, Wind& wind) {
jsonIn.at("deg").get_to(wind.deg);
jsonIn.at("speed").get_to(wind.speed);
if (jsonIn.contains("gust"))
wind.gust = jsonIn["gust"].get<f64>();
// Validate degrees (0-360)
if (wind.deg > 360)
throw std::runtime_error("Invalid wind degree");
}
};
template <>
struct adl_serializer<Precipitation> {
static void to_json(json& jsonOut, const Precipitation& precip) {
if (precip.one_hour)
jsonOut["1h"] = *precip.one_hour;
if (precip.three_hours)
jsonOut["3h"] = *precip.three_hours;
}
static void from_json(const json& jsonIn, Precipitation& precip) {
if (jsonIn.contains("1h"))
precip.one_hour = jsonIn["1h"].get<f64>();
if (jsonIn.contains("3h"))
precip.three_hours = jsonIn["3h"].get<f64>();
}
};
template <>
struct adl_serializer<Sys> {
static void to_json(json& jsonOut, const Sys& sys) {
jsonOut = json {
{ "country", sys.country },
{ "id", sys.id },
{ "sunrise", sys.sunrise },
{ "sunset", sys.sunset },
{ "type", sys.type }
};
}
static void from_json(const json& jsonIn, Sys& sys) {
jsonIn.at("country").get_to(sys.country);
jsonIn.at("id").get_to(sys.id);
jsonIn.at("sunrise").get_to(sys.sunrise);
jsonIn.at("sunset").get_to(sys.sunset);
jsonIn.at("type").get_to(sys.type);
}
};
template <>
struct adl_serializer<Clouds> {
static void to_json(json& jsonOut, const Clouds& clouds) {
jsonOut = json {
{ "all", clouds.all }
};
}
static void from_json(const json& jsonIn, Clouds& clouds) { jsonIn.at("all").get_to(clouds.all); }
};
template <>
struct adl_serializer<Coords> {
static void to_json(json& jsonOut, const Coords& coords) {
jsonOut = json {
{ "lat", coords.lat },
{ "lon", coords.lon }
};
}
static void from_json(const json& jsonIn, Coords& coords) {
jsonIn.at("lat").get_to(coords.lat);
jsonIn.at("lon").get_to(coords.lon);
}
};
template <>
struct adl_serializer<WeatherOutput> {
static void to_json(json& jsonOut, const WeatherOutput& weatherOut) {
jsonOut = json {
{ "clouds", weatherOut.clouds },
{ "timezone", weatherOut.timezone },
{ "visibility", weatherOut.visibility },
{ "main", weatherOut.main },
{ "coord", weatherOut.coords },
{ "base", weatherOut.base },
{ "name", weatherOut.name },
{ "weather", weatherOut.weather },
{ "sys", weatherOut.sys },
{ "cod", weatherOut.cod },
{ "dt", weatherOut.dt },
{ "id", weatherOut.id },
{ "wind", weatherOut.wind }
};
if (weatherOut.rain)
jsonOut["rain"] = *weatherOut.rain;
if (weatherOut.snow)
jsonOut["snow"] = *weatherOut.snow;
}
static void from_json(const json& jsonIn, WeatherOutput& weatherOut) {
jsonIn.at("clouds").get_to(weatherOut.clouds);
jsonIn.at("timezone").get_to(weatherOut.timezone);
jsonIn.at("visibility").get_to(weatherOut.visibility);
jsonIn.at("main").get_to(weatherOut.main);
jsonIn.at("coord").get_to(weatherOut.coords);
if (jsonIn.contains("rain"))
weatherOut.rain = jsonIn["rain"].get<Precipitation>();
if (jsonIn.contains("snow"))
weatherOut.snow = jsonIn["snow"].get<Precipitation>();
jsonIn.at("base").get_to(weatherOut.base);
jsonIn.at("name").get_to(weatherOut.name);
jsonIn.at("weather").get_to(weatherOut.weather);
jsonIn.at("sys").get_to(weatherOut.sys);
jsonIn.at("cod").get_to(weatherOut.cod);
jsonIn.at("dt").get_to(weatherOut.dt);
jsonIn.at("id").get_to(weatherOut.id);
jsonIn.at("wind").get_to(weatherOut.wind);
}
};
}
// NOLINTEND(readability-identifier-naming) // NOLINTEND(readability-identifier-naming)

View file

@ -166,21 +166,25 @@ namespace {
content.push_back(text("  Hello " + name + "! ") | bold | color(Color::Cyan)); content.push_back(text("  Hello " + name + "! ") | bold | color(Color::Cyan));
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(borderColor));
content.push_back(hbox({ content.push_back(hbox(
text("") | color(iconColor), // Palette icon {
CreateColorCircles(), text("") | color(iconColor), // Palette icon
})); CreateColorCircles(),
}
));
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(borderColor));
// Helper function for aligned rows // Helper function for aligned rows
fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) { fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) {
return hbox({ return hbox(
text(icon) | color(iconColor), {
text(label) | color(labelColor), text(icon) | color(iconColor),
filler(), text(label) | color(labelColor),
text(value) | color(valueColor), filler(),
text(" "), text(value) | color(valueColor),
}); text(" "),
}
);
}; };
// System info rows // System info rows
@ -191,31 +195,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(weatherIcon) | color(iconColor), {
text("Weather") | color(labelColor), text(weatherIcon) | color(iconColor),
filler(), text("Weather") | color(labelColor),
filler(),
hbox({ hbox(
text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))), {
text("in "), text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))),
text(weatherInfo.name), text("in "),
text(" "), text(weatherInfo.name),
}) | text(" "),
color(valueColor), }
})); ) |
color(valueColor),
}
));
else else
content.push_back(hbox({ content.push_back(hbox(
text(weatherIcon) | color(iconColor), {
text("Weather") | color(labelColor), text(weatherIcon) | color(iconColor),
filler(), text("Weather") | color(labelColor),
filler(),
hbox({ hbox(
text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), {
text(" "), text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)),
}) | text(" "),
color(valueColor), }
})); ) |
color(valueColor),
}
));
} }
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(borderColor));
@ -259,14 +271,16 @@ namespace {
const std::string& npText = *nowPlayingResult; const std::string& npText = *nowPlayingResult;
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(borderColor));
content.push_back(hbox({ content.push_back(hbox(
text(musicIcon) | color(iconColor), {
text("Playing") | color(labelColor), text(musicIcon) | color(iconColor),
text(" "), text("Playing") | color(labelColor),
filler(), text(" "),
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30), filler(),
text(" "), paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30),
})); text(" "),
}
));
} else { } else {
const NowPlayingError& error = nowPlayingResult.error(); const NowPlayingError& error = nowPlayingResult.error();

View file

@ -15,6 +15,7 @@
#include <optional> #include <optional>
#include <ranges> #include <ranges>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/statvfs.h>
#include <sys/utsname.h> #include <sys/utsname.h>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
@ -23,18 +24,36 @@
#include "os.h" #include "os.h"
#include "src/util/macros.h" #include "src/util/macros.h"
using std::errc, std::expected, std::from_chars, std::getline, std::istreambuf_iterator, std::less, std::lock_guard, // Minimal global using declarations needed for function signatures
std::mutex, std::ofstream, std::pair, std::string_view, std::vector, std::nullopt, std::array, std::optional, using std::expected;
std::bit_cast, std::to_string, std::ifstream, std::getenv, std::string, std::unexpected, std::ranges::is_sorted, using std::optional;
std::ranges::lower_bound, std::ranges::replace, std::ranges::subrange, std::ranges::transform;
using namespace std::literals::string_view_literals;
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std::literals::string_view_literals;
enum SessionType : u8 { Wayland, X11, TTY, Unknown };
namespace { namespace {
// Local using declarations for the anonymous namespace
using std::array;
using std::bit_cast;
using std::getenv;
using std::ifstream;
using std::istreambuf_iterator;
using std::less;
using std::lock_guard;
using std::mutex;
using std::nullopt;
using std::ofstream;
using std::pair;
using std::string_view;
using std::to_string;
using std::unexpected;
using std::vector;
using std::ranges::is_sorted;
using std::ranges::lower_bound;
using std::ranges::replace;
using std::ranges::subrange;
using std::ranges::transform;
fn GetX11WindowManager() -> string { fn GetX11WindowManager() -> string {
Display* display = XOpenDisplay(nullptr); Display* display = XOpenDisplay(nullptr);
@ -394,6 +413,8 @@ fn GetOSVersion() -> expected<string, string> {
} }
fn GetMemInfo() -> expected<u64, string> { fn GetMemInfo() -> expected<u64, string> {
using std::from_chars, std::errc;
constexpr const char* path = "/proc/meminfo"; constexpr const char* path = "/proc/meminfo";
ifstream input(path); ifstream input(path);
@ -657,9 +678,20 @@ fn GetDesktopEnvironment() -> optional<string> {
} }
fn GetShell() -> string { fn GetShell() -> string {
const char* shell = getenv("SHELL"); const string_view shell = getenv("SHELL");
return shell ? shell : ""; if (shell.ends_with("bash"))
return "Bash";
if (shell.ends_with("zsh"))
return "Zsh";
if (shell.ends_with("fish"))
return "Fish";
if (shell.ends_with("nu"))
return "Nushell";
if (shell.ends_with("sh"))
return "SH";
return !shell.empty() ? string(shell) : "";
} }
fn GetHost() -> string { fn GetHost() -> string {
@ -691,4 +723,13 @@ fn GetKernelVersion() -> string {
return static_cast<const char*>(uts.release); return static_cast<const char*>(uts.release);
} }
fn GetDiskUsage() -> pair<u64, u64> {
struct statvfs stat;
if (statvfs("/", &stat) == -1) {
ERROR_LOG("statvfs() failed: {}", strerror(errno));
return { 0, 0 };
}
return { (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), stat.f_blocks * stat.f_frsize };
}
#endif #endif

View file

@ -8,9 +8,7 @@
#include <guiddef.h> #include <guiddef.h>
#include <variant> #include <variant>
#include <winrt/base.h> #include <winrt/base.h>
#endif #else
#ifdef __APPLE__
#include <variant> #include <variant>
#endif #endif
@ -151,13 +149,13 @@ enum class NowPlayingCode : u8 {
* @brief Represents a Linux-specific error. * @brief Represents a Linux-specific error.
*/ */
using LinuxError = std::string; using LinuxError = std::string;
#elif defined(__APPLE__) #elifdef __APPLE__
/** /**
* @typedef MacError * @typedef MacError
* @brief Represents a macOS-specific error. * @brief Represents a macOS-specific error.
*/ */
using MacError = std::string; using MacError = std::string;
#elif defined(_WIN32) #elifdef _WIN32
/** /**
* @typedef WindowsError * @typedef WindowsError
* @brief Represents a Windows-specific error. * @brief Represents a Windows-specific error.
@ -170,9 +168,9 @@ using NowPlayingError = std::variant<
NowPlayingCode, NowPlayingCode,
#ifdef __linux__ #ifdef __linux__
LinuxError LinuxError
#elif defined(__APPLE__) #elifdef __APPLE__
MacError MacError
#elif defined(_WIN32) #elifdef _WIN32
WindowsError WindowsError
#endif #endif
>; >;