diff --git a/.clang-format b/.clang-format index 93a31a9..64f72d1 100644 --- a/.clang-format +++ b/.clang-format @@ -9,7 +9,7 @@ AllowShortLoopsOnASingleLine: true BasedOnStyle: Chromium BinPackArguments: false BinPackParameters: false -ColumnLimit: 100 +ColumnLimit: 120 ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 2 Cpp11BracedListStyle: false diff --git a/src/config/config.cpp b/src/config/config.cpp index 087bb51..b3d4f31 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -2,28 +2,27 @@ #include #include #include -#include #include "config.h" using rfl::Result; namespace fs = std::filesystem; -inline fn GetConfigPath() -> std::string { +inline fn GetConfigPath() -> fs::path { #ifdef _WIN32 const char* localAppData = std::getenv("LOCALAPPDATA"); if (!localAppData) throw std::runtime_error("Environment variable LOCALAPPDATA is not set"); - return localAppData; + return fs::path(localAppData); #else const char* home = std::getenv("HOME"); if (!home) throw std::runtime_error("Environment variable HOME is not set"); - return std::string(home) + "/.config"; + return fs::path(home) / ".config"; #endif } @@ -34,7 +33,8 @@ fn Config::getInstance() -> Config { const Result result = rfl::toml::load(configPath.string()); if (!result) { - fmt::println(stderr, "Failed to load config file: {}", result.error()->what()); + ERROR_LOG("Failed to load config file: {}", result.error()->what()); + exit(1); } diff --git a/src/config/config.h b/src/config/config.h index f3e5e0b..e108a50 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -1,4 +1,4 @@ -#pragma once + #include #include @@ -18,7 +18,8 @@ struct NowPlaying { }; struct Weather { - bool enabled = false; + bool enabled = false; + bool show_town_name = false; Location location; string api_key; diff --git a/src/main.cpp b/src/main.cpp index 555ed45..4f7265f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,11 +43,11 @@ namespace { date.erase(date.begin()); // Append appropriate suffix for the date - if (date == "1" || date == "21" || date == "31") + if (date.ends_with("1") && date != "11") date += "st"; - else if (date == "2" || date == "22") + else if (date.ends_with("2") && date != "12") date += "nd"; - else if (date == "3" || date == "23") + else if (date.ends_with("3") && date != "13") date += "rd"; else date += "th"; @@ -59,84 +59,125 @@ namespace { fn CreateColorCircles() -> Element { Elements circles; - for (int i = 0; i < 16; ++i) { - circles.push_back(text("◯") | bold | color(Color::Palette256(i))); - circles.push_back(text(" ")); - } + + for (int i = 0; i < 16; ++i) + circles.push_back(hbox({ + text("◯") | bold | color(Color::Palette256(i)), + text(" "), + })); + return hbox(circles); } fn SystemInfoBox(const Config& config) -> Element { // Fetch data const std::string& name = config.general.get().name.get(); - std::string date = GetDate(); + const std::string& date = GetDate(); + const std::string& host = GetHost(); + const std::string& kernelVersion = GetKernelVersion(); + const std::string& osVersion = GetOSVersion(); u64 memInfo = GetMemInfo(); - std::string osVersion = GetOSVersion(); Weather weather = config.weather.get(); bool nowPlayingEnabled = config.now_playing.get().enabled; - std::string nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; + const std::string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; // Icon constants (using Nerd Font v3) constexpr const char* calendarIcon = "  "; - constexpr const char* memoryIcon = "  "; + constexpr const char* hostIcon = " 󰌢 "; + constexpr const char* kernelIcon = "  "; constexpr const char* osIcon = "  "; + constexpr const char* memoryIcon = "  "; constexpr const char* weatherIcon = " 󰖐 "; constexpr const char* musicIcon = "  "; const Color::Palette16 labelColor = Color::Yellow; const Color::Palette16 valueColor = Color::White; const Color::Palette16 borderColor = Color::GrayLight; - const Color iconColor = Color::RGB(100, 200, 255); // Bright cyan + const Color::Palette16 iconColor = Color::Cyan; Elements content; + content.push_back(text("  Hello " + name + "! ") | bold | color(Color::Cyan)); content.push_back(separator() | color(borderColor)); + content.push_back(hbox({ + text("  ") | color(iconColor), // Palette icon + CreateColorCircles(), + })); + content.push_back(separator() | color(borderColor)); // Helper function for aligned rows - auto createRow = - [&](const std::string& emoji, const std::string& label, const std::string& value) { - return hbox({ text(emoji), - text(label) | color(labelColor), - filler(), - text(value) | color(valueColor), - text(" ") }); - }; + auto createRow = [&](const std::string& icon, const std::string& label, const std::string& value) { + return hbox({ + text(icon) | color(iconColor), + text(label) | color(labelColor), + text(" "), + filler(), + text(value) | color(valueColor), + text(" "), + }); + }; // System info rows - content.push_back(createRow(calendarIcon, "Date ", date)); - content.push_back(createRow(memoryIcon, "RAM ", fmt::format("{:.2f}", BytesToGiB { memInfo }))); - content.push_back(createRow(osIcon, "OS ", osVersion)); + content.push_back(createRow(calendarIcon, "Date", date)); // Weather row if (weather.enabled) { - auto weatherInfo = weather.getWeatherInfo(); - content.push_back(separator() | color(borderColor)); - content.push_back(hbox( - { text(weatherIcon), - text("Weather ") | color(labelColor), + WeatherOutput weatherInfo = weather.getWeatherInfo(); + + if (weather.show_town_name) + content.push_back(hbox({ + text(weatherIcon) | color(iconColor), + text("Weather") | color(labelColor), filler(), - hbox({ text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))) | color(Color::Red), - text("in "), - text(weatherInfo.name), - text(" ") }) | - color(valueColor) } - )); + + hbox({ + text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))), + text("in "), + text(weatherInfo.name), + text(" "), + }) | + color(valueColor), + })); + else + content.push_back(hbox({ + text(weatherIcon) | color(iconColor), + text("Weather") | color(labelColor), + filler(), + + hbox({ + text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), + text(" "), + }) | + color(valueColor), + })); } + content.push_back(separator() | color(borderColor)); + + if (!host.empty()) + content.push_back(createRow(hostIcon, "Host", host)); + + if (!kernelVersion.empty()) + content.push_back(createRow(kernelIcon, "Kernel", kernelVersion)); + + if (!osVersion.empty()) + content.push_back(createRow(osIcon, "OS", osVersion)); + + if (memInfo > 0) + content.push_back(createRow(memoryIcon, "RAM", fmt::format("{:.2f}", BytesToGiB { memInfo }))); + // Now Playing row if (nowPlayingEnabled && !nowPlaying.empty()) { content.push_back(separator() | color(borderColor)); - content.push_back(hbox({ text(musicIcon), - text("Now Playing ") | color(labelColor), - filler(), - text(nowPlaying), - text(" ") | color(Color::Magenta) })); + content.push_back(hbox({ + text(musicIcon) | color(iconColor), + text("Music") | color(labelColor), + text(" "), + filler(), + text(nowPlaying.length() > 30 ? nowPlaying.substr(0, 30) + "..." : nowPlaying) | color(Color::Magenta), + text(" "), + })); } - // Color circles section - content.push_back(filler()); - content.push_back(separator() | color(borderColor)); - content.push_back(hbox({ text("  ") | color(iconColor), // Palette icon - CreateColorCircles() })); return vbox(content) | borderRounded | color(Color::White); } } diff --git a/src/os/linux.cpp b/src/os/linux.cpp index 8850ed2..ca7683d 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -1,32 +1,39 @@ #ifdef __linux__ #include -#include #include #include #include +#include #include #include "os.h" +#include "src/util/macros.h" enum SessionType : u8 { Wayland, X11, TTY, Unknown }; fn ParseLineAsNumber(const std::string& input) -> u64 { usize start = input.find_first_of("0123456789"); - if (start == std::string::npos) - throw std::runtime_error("No number found in input"); + if (start == std::string::npos) { + ERROR_LOG("No number found in input"); + return 0; + } usize end = input.find_first_not_of("0123456789", start); return std::stoull(input.substr(start, end - start)); } -fn MeminfoParse(const std::filesystem::path& filepath) -> u64 { - std::ifstream input(filepath); +fn MeminfoParse() -> u64 { + constexpr const char* path = "/proc/meminfo"; - if (!input.is_open()) - throw std::runtime_error("Failed to open " + filepath.string()); + std::ifstream input(path); + + if (!input.is_open()) { + ERROR_LOG("Failed to open {}", path); + return 0; + } std::string line; @@ -34,16 +41,21 @@ fn MeminfoParse(const std::filesystem::path& filepath) -> u64 { if (line.starts_with("MemTotal")) return ParseLineAsNumber(line); - throw std::runtime_error("MemTotal line not found in " + filepath.string()); + ERROR_LOG("MemTotal line not found in {}", path); + return 0; } -fn GetMemInfo() -> u64 { return MeminfoParse("/proc/meminfo") * 1024; } +fn GetMemInfo() -> u64 { return MeminfoParse() * 1024; } fn GetOSVersion() -> std::string { - std::ifstream file("/etc/os-release"); + constexpr const char* path = "/etc/os-release"; - if (!file.is_open()) - throw std::runtime_error("Failed to open /etc/os-release"); + std::ifstream file(path); + + if (!file.is_open()) { + ERROR_LOG("Failed to open {}", path); + return ""; + } string line; const string prefix = "PRETTY_NAME="; @@ -58,7 +70,8 @@ fn GetOSVersion() -> std::string { return prettyName; } - throw std::runtime_error("PRETTY_NAME line not found in /etc/os-release"); + ERROR_LOG("PRETTY_NAME line not found in {}", path); + return ""; } fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector { @@ -66,8 +79,7 @@ fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector { const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus"); const char* dbusMethodListNames = "ListNames"; - const std::unique_ptr dbusProxy = - createProxy(connection, dbusInterface, dbusObjectPath); + const std::unique_ptr dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath); std::vector names; @@ -76,8 +88,7 @@ fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector { std::vector mprisPlayers; for (const std::basic_string& name : names) - if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; - name.find(mprisInterfaceName) != std::string::npos) + if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != std::string::npos) mprisPlayers.push_back(name); return mprisPlayers; @@ -92,30 +103,32 @@ fn GetActivePlayer(const std::vector& mprisPlayers) -> string { fn GetNowPlaying() -> string { try { - const char *playerObjectPath = "/org/mpris/MediaPlayer2", - *playerInterfaceName = "org.mpris.MediaPlayer2.Player"; + const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player"; std::unique_ptr connection = sdbus::createSessionBusConnection(); std::vector mprisPlayers = GetMprisPlayers(*connection); - if (mprisPlayers.empty()) + if (mprisPlayers.empty()) { + DEBUG_LOG("No MPRIS players found"); return ""; + } string activePlayer = GetActivePlayer(mprisPlayers); - if (activePlayer.empty()) + if (activePlayer.empty()) { + DEBUG_LOG("No active player found"); return ""; + } - auto playerProxy = sdbus::createProxy( - *connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath) - ); + std::unique_ptr playerProxy = + sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath)); - sdbus::Variant metadataVariant = - playerProxy->getProperty("Metadata").onInterface(playerInterfaceName); + sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName); if (metadataVariant.containsValueOfType>()) { - const auto& metadata = metadataVariant.get>(); + const std::map, sdbus::Variant>& metadata = + metadataVariant.get>(); auto iter = metadata.find("xesam:title"); @@ -123,8 +136,10 @@ fn GetNowPlaying() -> string { return iter->second.get(); } } catch (const sdbus::Error& e) { - if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") - return fmt::format("Error: {}", e.what()); + if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") { + ERROR_LOG("Error: {}", e.what()); + return ""; + } return "No active player"; } @@ -138,17 +153,33 @@ fn GetShell() -> string { return shell ? shell : ""; } -fn GetProductFamily() -> string { - std::ifstream file("/sys/class/dmi/id/product_family"); +fn GetHost() -> string { + constexpr const char* path = "/sys/class/dmi/id/product_family"; - if (!file.is_open()) - throw std::runtime_error("Failed to open /sys/class/dmi/id/product_family"); + std::ifstream file(path); + if (!file.is_open()) { + ERROR_LOG("Failed to open {}", path); + return ""; + } std::string productFamily; - - std::getline(file, productFamily); + if (!std::getline(file, productFamily)) { + ERROR_LOG("Failed to read from {}", path); + return ""; + } return productFamily; } +fn GetKernelVersion() -> string { + struct utsname uts; + + if (uname(&uts) == -1) { + ERROR_LOG("uname() failed: {}", std::strerror(errno)); + return ""; + } + + return static_cast(uts.release); +} + #endif diff --git a/src/os/os.h b/src/os/os.h index 877e2d4..0464c5d 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -36,4 +36,9 @@ fn GetShell() -> string; /** * @brief Get the product family */ -fn GetProductFamily() -> string; +fn GetHost() -> string; + +/** + * @brief Get the kernel version. + */ +fn GetKernelVersion() -> string;