diff --git a/meson.build b/meson.build index 68b4578..17a3516 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( default_options: [ 'default_library=static', 'warning_level=everything', - 'buildtype=debugoptimized', + 'buildtype=release', 'cpp_args=-fvisibility=hidden', ], ) @@ -49,6 +49,7 @@ common_cpp_args = [ '-Wunused-function', '-fno-strict-enums', '-nostdlib++', + '-march=native', ] if host_machine.system() == 'windows' @@ -130,9 +131,11 @@ deps += ftxui_dep cmake_opts = cmake.subproject_options() -cmake_opts.add_cmake_defines({ - 'CMAKE_CXX_FLAGS': '-Wno-everything', -}) +cmake_opts.add_cmake_defines( + { + 'CMAKE_CXX_FLAGS': '-Wno-everything', + }, +) cmake_opts.append_compile_args('cpp', '-Wno-everything') @@ -153,4 +156,4 @@ executable( objc_args: objc_args, link_args: link_args, dependencies: deps, -) \ No newline at end of file +) diff --git a/src/main.cpp b/src/main.cpp index 204f700..a2fc31a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,7 +26,8 @@ template <> struct fmt::formatter : fmt::formatter { template constexpr fn format(const BytesToGiB& BTG, FmtCtx& ctx) const -> typename FmtCtx::iterator { - return fmt::format_to(fmt::formatter::format(static_cast(BTG.value) / GIB, ctx), "GiB"); + // Format as double with GiB suffix, no space + return fmt::format_to(ctx.out(), "{:.2f}GiB", static_cast(BTG.value) / GIB); } }; @@ -51,7 +52,7 @@ namespace { if (!date.empty() && std::isspace(date.front())) date.erase(date.begin()); - // Append appropriate suffix for the date + // Append appropriate suffix for the datE if (date.ends_with("1") && date != "11") date += "st"; else if (date.ends_with("2") && date != "12") @@ -74,42 +75,47 @@ namespace { std::string window_manager; std::optional> now_playing; std::optional weather_info; + u64 disk_used; + u64 disk_total; + std::string shell; static fn fetchSystemData(const Config& config) -> SystemData { SystemData data; - // Group tasks by dependency/type - auto [date, host, kernel] = std::tuple( - std::async(std::launch::async, GetDate), - std::async(std::launch::async, GetHost), - std::async(std::launch::async, GetKernelVersion) - ); + // Single-threaded execution for core system info (faster on Windows) + data.date = GetDate(); + data.host = GetHost(); + data.kernel_version = GetKernelVersion(); + data.os_version = GetOSVersion(); + data.mem_info = GetMemInfo(); - auto [osVer, mem, desktop, winManager] = std::tuple( - std::async(std::launch::async, GetOSVersion), - std::async(std::launch::async, GetMemInfo), - std::async(std::launch::async, GetDesktopEnvironment), - std::async(std::launch::async, GetWindowManager) - ); + // Desktop environment info (not relevant for Windows) + data.desktop_environment = std::nullopt; + data.window_manager = "Windows"; - // Conditional async tasks + // Parallel execution for disk/shell only + auto diskShell = std::async(std::launch::async, [] { + auto [used, total] = GetDiskUsage(); + return std::make_tuple(used, total, GetShell()); + }); + + // Conditional tasks std::future weather; std::future> nowPlaying; - if (config.weather.get().enabled) - weather = std::async(std::launch::async, [&] { return config.weather.get().getWeatherInfo(); }); + if (config.weather.get().enabled) { + weather = std::async(std::launch::async, [&config] { return config.weather.get().getWeatherInfo(); }); + } - if (config.now_playing.get().enabled) + if (config.now_playing.get().enabled) { nowPlaying = std::async(std::launch::async, GetNowPlaying); + } - // Ordered wait for fastest completion - data.date = date.get(); - data.host = host.get(); - data.kernel_version = kernel.get(); - data.os_version = osVer.get(); - data.mem_info = mem.get(); - data.desktop_environment = desktop.get(); - data.window_manager = winManager.get(); + // Get remaining results + auto [used, total, shell] = diskShell.get(); + data.disk_used = used; + data.disk_total = total; + data.shell = shell; if (weather.valid()) data.weather_info = weather.get(); @@ -160,25 +166,21 @@ namespace { 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(hbox({ + text("  ") | color(iconColor), // Palette icon + CreateColorCircles(), + })); content.push_back(separator() | color(borderColor)); // Helper function for aligned rows fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) { - return hbox( - { - text(icon) | color(iconColor), - text(label) | color(labelColor), - filler(), - text(value) | color(valueColor), - text(" "), - } - ); + return hbox({ + text(icon) | color(iconColor), + text(label) | color(labelColor), + filler(), + text(value) | color(valueColor), + text(" "), + }); }; // System info rows @@ -189,39 +191,31 @@ namespace { const WeatherOutput& weatherInfo = data.weather_info.value(); if (weather.show_town_name) - content.push_back(hbox( - { - text(weatherIcon) | color(iconColor), - text("Weather") | color(labelColor), - filler(), + content.push_back(hbox({ + text(weatherIcon) | color(iconColor), + text("Weather") | color(labelColor), + filler(), - hbox( - { - text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))), - 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(), + 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), - } - )); + hbox({ + text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), + text(" "), + }) | + color(valueColor), + })); } content.push_back(separator() | color(borderColor)); @@ -237,11 +231,20 @@ namespace { else ERROR_LOG("Failed to get OS version: {}", data.os_version.error()); + // Add disk row after memory info if (data.mem_info.has_value()) - content.push_back(createRow(memoryIcon, "RAM", fmt::format("{:.2f}", BytesToGiB { *data.mem_info }))); + content.push_back(createRow(memoryIcon, "RAM", fmt::format("{}", BytesToGiB { *data.mem_info }))); else ERROR_LOG("Failed to get memory info: {}", data.mem_info.error()); + // Add Disk usage row + content.push_back( + createRow(" 󰋊 ", "Disk", fmt::format("{}/{}", BytesToGiB { data.disk_used }, BytesToGiB { data.disk_total })) + ); + + // Add Shell row + content.push_back(createRow("  ", "Shell", data.shell)); + content.push_back(separator() | color(borderColor)); if (data.desktop_environment.has_value() && *data.desktop_environment != data.window_manager) @@ -258,16 +261,14 @@ namespace { const std::string& npText = *nowPlayingResult; content.push_back(separator() | color(borderColor)); - content.push_back(hbox( - { - text(musicIcon) | color(iconColor), - text("Playing") | color(labelColor), - text(" "), - filler(), - paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30), - text(" "), - } - )); + content.push_back(hbox({ + text(musicIcon) | color(iconColor), + text("Playing") | color(labelColor), + text(" "), + filler(), + paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30), + text(" "), + })); } else { const NowPlayingError& error = nowPlayingResult.error(); @@ -299,10 +300,10 @@ namespace { fn main() -> i32 { const Config& config = Config::getInstance(); + SystemData data = SystemData::fetchSystemData(config); - SystemData data = SystemData::fetchSystemData(config); - - Element document = hbox({ SystemInfoBox(config, data), filler() }); + // Add vertical box with forced newline + Element document = vbox({ hbox({ SystemInfoBox(config, data), filler() }), text("") }); Screen screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); Render(screen, document); diff --git a/src/os/os.h b/src/os/os.h index f7a1ded..a1c2b85 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -51,3 +51,9 @@ fn GetKernelVersion() -> string; * @brief Get the number of installed packages. */ fn GetPackageCount() -> u64; + +/** + * @brief Get the current disk usage. + * @return std::pair Used space/total space + */ +fn GetDiskUsage() -> std::pair; diff --git a/src/os/windows.cpp b/src/os/windows.cpp index 4fa534a..3255ae2 100644 --- a/src/os/windows.cpp +++ b/src/os/windows.cpp @@ -1,15 +1,9 @@ #ifdef __WIN32__ -#include +// clang-format off #define WIN32_LEAN_AND_MEAN #include #include -#include -#include -#include -#include - -// clang-format off #include #include #include @@ -17,6 +11,11 @@ #include // clang-format on +#include +#include +#include +#include + #include "os.h" using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW); @@ -76,15 +75,64 @@ namespace { return _stricmp(proc.c_str(), name.c_str()) == 0; }); } + + fn GetParentProcessId(DWORD pid) -> DWORD { + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) + return 0; + + PROCESSENTRY32 pe32; + pe32.dwSize = sizeof(PROCESSENTRY32); + DWORD parentPid = 0; + + if (Process32First(hSnapshot, &pe32)) { + while (true) { + if (pe32.th32ProcessID == pid) { + parentPid = pe32.th32ParentProcessID; + break; + } + if (!Process32Next(hSnapshot, &pe32)) { + break; + } + } + } + CloseHandle(hSnapshot); + return parentPid; + } + + fn GetProcessName(DWORD pid) -> string { + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) + return ""; + + PROCESSENTRY32 pe32; + pe32.dwSize = sizeof(PROCESSENTRY32); + string processName; + + if (Process32First(hSnapshot, &pe32)) { + while (true) { + if (pe32.th32ProcessID == pid) { + // Explicitly cast array to string to avoid implicit array decay + processName = std::string(static_cast(pe32.szExeFile)); + break; + } + + if (!Process32Next(hSnapshot, &pe32)) + break; + } + } + CloseHandle(hSnapshot); + return processName; + } } fn GetMemInfo() -> expected { - u64 mem = 0; - - if (!GetPhysicallyInstalledSystemMemory(&mem)) - return std::unexpected("Failed to get physical system memory."); - - return mem * 1024; + MEMORYSTATUSEX memInfo; + memInfo.dwLength = sizeof(MEMORYSTATUSEX); + if (!GlobalMemoryStatusEx(&memInfo)) { + return std::unexpected("Failed to get memory status"); + } + return memInfo.ullTotalPhys; } fn GetNowPlaying() -> expected { @@ -141,7 +189,7 @@ fn GetOSVersion() -> expected { return result; } - return std::unexpected("Failed to get OS version."); + return "Windows"; } fn GetHost() -> string { @@ -180,22 +228,20 @@ fn GetWindowManager() -> string { string windowManager; // Check for third-party WMs - if (IsProcessRunning(processes, "glazewm.exe")) { + if (IsProcessRunning(processes, "glazewm.exe")) windowManager = "GlazeWM"; - } else if (IsProcessRunning(processes, "fancywm.exe")) { + else if (IsProcessRunning(processes, "fancywm.exe")) windowManager = "FancyWM"; - } else if (IsProcessRunning(processes, "komorebi.exe") || IsProcessRunning(processes, "komorebic.exe")) { + else if (IsProcessRunning(processes, "komorebi.exe") || IsProcessRunning(processes, "komorebic.exe")) windowManager = "Komorebi"; - } // Fallback to DWM detection if (windowManager.empty()) { BOOL compositionEnabled = FALSE; - if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) { + if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) windowManager = compositionEnabled ? "Desktop Window Manager" : "Windows Manager (Basic)"; - } else { + else windowManager = "Windows Manager"; - } } return windowManager; @@ -244,4 +290,87 @@ fn GetDesktopEnvironment() -> optional { } catch (...) { return std::nullopt; } } +fn GetShell() -> string { + // Detect MSYS2/MinGW shells + if (getenv("MSYSTEM")) { + const char* shell = getenv("SHELL"); + string shellExe; + + // First try SHELL, then LOGINSHELL + if (!shell || strlen(shell) == 0) { + shell = getenv("LOGINSHELL"); + } + + if (shell) { + string shellPath = shell; + size_t lastSlash = shellPath.find_last_of("\\/"); + shellExe = (lastSlash != string::npos) ? shellPath.substr(lastSlash + 1) : shellPath; + std::ranges::transform(shellExe, shellExe.begin(), ::tolower); + } + + // Fallback to process ancestry if both env vars are missing + if (shellExe.empty()) { + DWORD pid = GetCurrentProcessId(); + + while (pid != 0) { + string processName = GetProcessName(pid); + std::ranges::transform(processName, processName.begin(), [](unsigned char character) { + return static_cast(std::tolower(character)); + }); + + if (processName == "bash.exe" || processName == "zsh.exe" || processName == "fish.exe" || + processName == "mintty.exe") { + string name = processName.substr(0, processName.find(".exe")); + if (!name.empty()) + name[0] = static_cast(std::toupper(static_cast(name[0]))); // Capitalize first letter + return name; + } + pid = GetParentProcessId(pid); + } + + return "MSYS2"; + } + + if (shellExe.find("bash") != string::npos) + return "Bash"; + if (shellExe.find("zsh") != string::npos) + return "Zsh"; + if (shellExe.find("fish") != string::npos) + return "Fish"; + return shellExe.empty() ? "MSYS2" : "MSYS2/" + shellExe; + } + + // Detect Windows shells + const std::unordered_map knownShells = { + { "cmd.exe", "Command Prompt" }, + { "powershell.exe", "PowerShell" }, + { "pwsh.exe", "PowerShell Core" }, + { "windowsterminal.exe", "Windows Terminal" }, + { "mintty.exe", "Mintty" }, + { "bash.exe", "Windows Subsystem for Linux" } + }; + + DWORD pid = GetCurrentProcessId(); + while (pid != 0) { + string processName = GetProcessName(pid); + std::ranges::transform(processName, processName.begin(), ::tolower); + + if (auto shellIterator = knownShells.find(processName); shellIterator != knownShells.end()) + return shellIterator->second; + + pid = GetParentProcessId(pid); + } + + return "Windows Console"; +} + +fn GetDiskUsage() -> std::pair { + ULARGE_INTEGER freeBytes, totalBytes; + + if (GetDiskFreeSpaceExW(L"C:\\", nullptr, &totalBytes, &freeBytes)) + return { totalBytes.QuadPart - freeBytes.QuadPart, totalBytes.QuadPart }; + + return { 0, 0 }; +} + #endif