This commit is contained in:
Mars 2025-04-19 23:41:41 -04:00
parent a8e175a5f9
commit ab548687e1
2 changed files with 155 additions and 106 deletions

View file

@ -1,9 +1,7 @@
#include <ctime> #include <ctime>
#include <expected> #include <expected>
#include <fmt/chrono.h>
#include <fmt/color.h>
#include <fmt/format.h>
#include <ftxui/dom/elements.hpp> #include <ftxui/dom/elements.hpp>
#include <ftxui/screen/color.hpp>
#include <ftxui/screen/screen.hpp> #include <ftxui/screen/screen.hpp>
#include <future> #include <future>
#include <string> #include <string>
@ -11,10 +9,9 @@
#include <variant> #include <variant>
#include "config/config.h" #include "config/config.h"
#include "ftxui/screen/color.hpp"
#include "os/os.h" #include "os/os.h"
constexpr bool SHOW_ICONS = true; constexpr inline bool SHOW_ICONS = true;
struct BytesToGiB { struct BytesToGiB {
u64 value; u64 value;
@ -24,46 +21,110 @@ struct BytesToGiB {
constexpr u64 GIB = 1'073'741'824; constexpr u64 GIB = 1'073'741'824;
template <> template <>
struct fmt::formatter<BytesToGiB> : fmt::formatter<double> { struct std::formatter<BytesToGiB> : std::formatter<double> {
template <typename FmtCtx> auto format(const BytesToGiB& BTG, auto& ctx) const {
constexpr fn format(const BytesToGiB& BTG, FmtCtx& ctx) const -> typename FmtCtx::iterator { return std::format_to(ctx.out(), "{:.2f}GiB", static_cast<f64>(BTG.value) / GIB);
// Format as double with GiB suffix, no space
return fmt::format_to(ctx.out(), "{:.2f}GiB", static_cast<f64>(BTG.value) / GIB);
} }
}; };
namespace ui {
using ftxui::Color;
constexpr int MAX_PARAGRAPH_LENGTH = 30;
constexpr int PADDING = 3;
// Color themes
struct Theme {
Color::Palette16 icon;
Color::Palette16 label;
Color::Palette16 value;
Color::Palette16 border;
Color::Palette16 accent;
};
constexpr Theme DEFAULT_THEME = {
.icon = Color::Cyan,
.label = Color::Yellow,
.value = Color::White,
.border = Color::GrayLight,
.accent = Color::Magenta,
};
struct Icons {
std::string_view user;
std::string_view palette;
std::string_view calendar;
std::string_view host;
std::string_view kernel;
std::string_view os;
std::string_view memory;
std::string_view weather;
std::string_view music;
std::string_view disk;
std::string_view shell;
std::string_view desktop;
std::string_view window_manager;
};
constexpr Icons EMPTY_ICONS = {
.user = "",
.palette = "",
.calendar = "",
.host = "",
.kernel = "",
.os = "",
.memory = "",
.weather = "",
.music = "",
.disk = "",
.shell = "",
.desktop = "",
.window_manager = "",
};
// Using your original icons
constexpr Icons NERD_ICONS = {
.user = "",
.palette = "",
.calendar = "",
.host = " 󰌢 ",
.kernel = "",
.os = "",
.memory = "",
.weather = "",
.music = "",
.disk = " 󰋊 ",
.shell = "",
.desktop = " 󰇄 ",
.window_manager = "  ",
};
}
namespace { namespace {
fn GetDate() -> std::string { template <typename T, typename E, typename ValueFunc, typename ErrorFunc>
// Get current local time auto expected_visit(const std::expected<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) {
const std::time_t now = std::time(nullptr); if (exp.has_value())
std::tm localTime; return value_func(*exp);
#ifdef _WIN32 return error_func(exp.error());
if (localtime_s(&localTime, &now) != 0) }
ERROR_LOG("localtime_s failed");
#else
if (localtime_r(&now, &localTime) == nullptr)
ERROR_LOG("localtime_r failed");
#endif
// Format the date using fmt::format fn GetDate() -> string {
std::string date = fmt::format("{:%e}", localTime); using namespace std::chrono;
// Remove leading whitespace const year_month_day ymd = year_month_day { floor<days>(system_clock::now()) };
if (!date.empty() && std::isspace(date.front()))
date.erase(date.begin());
// Append appropriate suffix for the datE string month = std::format("{:%B}", ymd);
if (date.back() == '1' && date != "11")
date += "st";
else if (date.back() == '2' && date != "12")
date += "nd";
else if (date.back() == '3' && date != "13")
date += "rd";
else
date += "th";
return fmt::format("{:%B} {}", localTime, date); i32 day = static_cast<unsigned>(ymd.day());
const char* suffix = (day >= 11 && day <= 13) ? "th"
: (day % 10 == 1) ? "st"
: (day % 10 == 2) ? "nd"
: (day % 10 == 3) ? "rd"
: "th";
return std::format("{} {}{}", month, day, suffix);
} }
struct SystemData { struct SystemData {
@ -84,15 +145,15 @@ namespace {
SystemData data; SystemData data;
// Single-threaded execution for core system info (faster on Windows) // Single-threaded execution for core system info (faster on Windows)
data.date = GetDate(); data.date = std::move(GetDate());
data.host = GetHost(); data.host = std::move(GetHost());
data.kernel_version = GetKernelVersion(); data.kernel_version = std::move(GetKernelVersion());
data.os_version = GetOSVersion(); data.os_version = std::move(GetOSVersion());
data.mem_info = GetMemInfo(); data.mem_info = std::move(GetMemInfo());
// Desktop environment info // Desktop environment info
data.desktop_environment = GetDesktopEnvironment(); data.desktop_environment = std::move(GetDesktopEnvironment());
data.window_manager = GetWindowManager(); data.window_manager = std::move(GetWindowManager());
// Parallel execution for disk/shell only // Parallel execution for disk/shell only
auto diskShell = std::async(std::launch::async, [] { auto diskShell = std::async(std::launch::async, [] {
@ -128,13 +189,12 @@ namespace {
using namespace ftxui; using namespace ftxui;
fn CreateColorCircles() -> Element { fn CreateColorCircles() -> Element {
Elements circles(16); return hbox(
std::views::iota(0, 16) | std::views::transform([](int colorIndex) {
std::generate_n(circles.begin(), 16, [colorIndex = 0]() mutable { return hbox({ text("") | bold | color(static_cast<Color::Palette256>(colorIndex)), text(" ") });
return hbox({ text("") | bold | color(static_cast<Color::Palette256>(colorIndex++)), text(" ") }); }) |
}); std::ranges::to<Elements>()
);
return hbox(circles);
} }
fn SystemInfoBox(const Config& config, const SystemData& data) -> Element { fn SystemInfoBox(const Config& config, const SystemData& data) -> Element {
@ -143,44 +203,32 @@ namespace {
const Weather weather = config.weather; const Weather weather = config.weather;
const bool nowPlayingEnabled = config.now_playing.enabled; const bool nowPlayingEnabled = config.now_playing.enabled;
const char *calendarIcon = "", *hostIcon = "", *kernelIcon = "", *osIcon = "", *memoryIcon = "", *weatherIcon = "", const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, deIcon, wmIcon] =
*musicIcon = ""; SHOW_ICONS ? ui::NERD_ICONS : ui::EMPTY_ICONS;
if (SHOW_ICONS) {
calendarIcon = "";
hostIcon = " 󰌢 ";
kernelIcon = "";
osIcon = "";
memoryIcon = "";
weatherIcon = "";
musicIcon = "";
}
constexpr Color::Palette16 labelColor = Color::Yellow;
constexpr Color::Palette16 valueColor = Color::White;
constexpr Color::Palette16 borderColor = Color::GrayLight;
constexpr Color::Palette16 iconColor = Color::Cyan;
Elements content; Elements content;
content.push_back(text("Hello " + name + "! ") | bold | color(Color::Cyan)); content.push_back(text(string(userIcon) + "Hello " + name + "! ") | bold | color(Color::Cyan));
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
content.push_back(hbox( content.push_back(hbox(
{ {
text("") | color(iconColor), // Palette icon text(string(paletteIcon)) | color(ui::DEFAULT_THEME.icon),
CreateColorCircles(), CreateColorCircles(),
} }
)); ));
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
// Helper function for aligned rows // Helper function for aligned rows
fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) { auto createRow = [&]<typename Icon, typename Label, typename Value>(Icon&& icon, Label&& label, Value&& value)
requires std::constructible_from<string, Icon> && std::constructible_from<string, Label> &&
std::constructible_from<string, Value>
{
return hbox( return hbox(
{ {
text(icon) | color(iconColor), text(string(std::forward<Icon>(icon))) | color(ui::DEFAULT_THEME.icon),
text(label) | color(labelColor), text(string(std::forward<Label>(label))) | color(ui::DEFAULT_THEME.label),
filler(), filler(),
text(value) | color(valueColor), text(string(std::forward<Value>(value))) | color(ui::DEFAULT_THEME.value),
text(" "), text(" "),
} }
); );
@ -196,40 +244,40 @@ namespace {
if (weather.show_town_name) if (weather.show_town_name)
content.push_back(hbox( content.push_back(hbox(
{ {
text(weatherIcon) | color(iconColor), text(string(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
text("Weather") | color(labelColor), text("Weather") | color(ui::DEFAULT_THEME.label),
filler(), filler(),
hbox( hbox(
{ {
text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))), text(std::format("{}°F ", std::lround(weatherInfo.main.temp))),
text("in "), text("in "),
text(weatherInfo.name), text(weatherInfo.name),
text(" "), text(" "),
} }
) | ) |
color(valueColor), color(ui::DEFAULT_THEME.value),
} }
)); ));
else else
content.push_back(hbox( content.push_back(hbox(
{ {
text(weatherIcon) | color(iconColor), text(string(weatherIcon)) | color(ui::DEFAULT_THEME.icon),
text("Weather") | color(labelColor), text("Weather") | color(ui::DEFAULT_THEME.label),
filler(), filler(),
hbox( hbox(
{ {
text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), text(std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)),
text(" "), text(" "),
} }
) | ) |
color(valueColor), color(ui::DEFAULT_THEME.value),
} }
)); ));
} }
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
if (!data.host.empty()) if (!data.host.empty())
content.push_back(createRow(hostIcon, "Host", data.host)); content.push_back(createRow(hostIcon, "Host", data.host));
@ -237,30 +285,32 @@ namespace {
if (!data.kernel_version.empty()) if (!data.kernel_version.empty())
content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version)); content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version));
if (data.os_version.has_value()) expected_visit(
content.push_back(createRow(osIcon, "OS", *data.os_version)); data.os_version,
else [&](const string& version) { content.push_back(createRow(string(osIcon), "OS", version)); },
ERROR_LOG("Failed to get OS version: {}", data.os_version.error()); [](const string& error) { ERROR_LOG("Failed to get OS version: {}", error); }
);
if (data.mem_info.has_value()) expected_visit(
content.push_back(createRow(memoryIcon, "RAM", fmt::format("{}", BytesToGiB { *data.mem_info }))); data.mem_info,
else [&](const u64& mem) { content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { mem }))); },
ERROR_LOG("Failed to get memory info: {}", data.mem_info.error()); [](const string& error) { ERROR_LOG("Failed to get memory info: {}", error); }
);
// Add Disk usage row // Add Disk usage row
content.push_back( content.push_back(
createRow(" 󰋊 ", "Disk", fmt::format("{}/{}", BytesToGiB { data.disk_used }, BytesToGiB { data.disk_total })) createRow(diskIcon, "Disk", std::format("{}/{}", BytesToGiB { data.disk_used }, BytesToGiB { data.disk_total }))
); );
content.push_back(createRow("", "Shell", data.shell)); content.push_back(createRow(shellIcon, "Shell", data.shell));
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
if (data.desktop_environment.has_value() && *data.desktop_environment != data.window_manager) if (data.desktop_environment.has_value() && *data.desktop_environment != data.window_manager)
content.push_back(createRow(" 󰇄 ", "DE", *data.desktop_environment)); content.push_back(createRow(deIcon, "DE", *data.desktop_environment));
if (!data.window_manager.empty()) if (!data.window_manager.empty())
content.push_back(createRow("  ", "WM", data.window_manager)); content.push_back(createRow(wmIcon, "WM", data.window_manager));
// Now Playing row // Now Playing row
if (nowPlayingEnabled && data.now_playing.has_value()) { if (nowPlayingEnabled && data.now_playing.has_value()) {
@ -268,11 +318,11 @@ namespace {
nowPlayingResult.has_value()) { nowPlayingResult.has_value()) {
const std::string& npText = *nowPlayingResult; const std::string& npText = *nowPlayingResult;
content.push_back(separator() | color(borderColor)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
content.push_back(hbox( content.push_back(hbox(
{ {
text(musicIcon) | color(iconColor), text(string(musicIcon)) | color(ui::DEFAULT_THEME.icon),
text("Playing") | color(labelColor), text("Playing") | color(ui::DEFAULT_THEME.label),
text(" "), text(" "),
filler(), filler(),
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30), paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30),

View file

@ -1,4 +1,3 @@
#include <ranges>
#ifdef _WIN32 #ifdef _WIN32
// clang-format off // clang-format off
@ -7,12 +6,13 @@
#include <wincrypt.h> #include <wincrypt.h>
#include <dwmapi.h> #include <dwmapi.h>
#include <tlhelp32.h> #include <tlhelp32.h>
#include <algorithm>
#include <vector>
#include <cstring>
// clang-format on // clang-format on
#include <algorithm>
#include <cstring>
#include <guiddef.h> #include <guiddef.h>
#include <ranges>
#include <vector>
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Media.Control.h> #include <winrt/Windows.Media.Control.h>
#include <winrt/Windows.Storage.h> #include <winrt/Windows.Storage.h>
@ -112,8 +112,7 @@ namespace {
if (!snapshot.isValid()) if (!snapshot.isValid())
return 0; return 0;
PROCESSENTRY32 pe32; PROCESSENTRY32 pe32 { .dwSize = sizeof(PROCESSENTRY32) };
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(snapshot.h_snapshot, &pe32)) if (!Process32First(snapshot.h_snapshot, &pe32))
return 0; return 0;