diff --git a/.clang-tidy b/.clang-tidy index d686033..f5d7e7a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,6 +4,7 @@ Checks: > -ctad-maybe-unsupported, -abseil-*, -altera-*, + -boost-*, -bugprone-easily-swappable-parameters, -bugprone-implicit-widening-of-multiplication-result, -cert-env33-c, diff --git a/flake.lock b/flake.lock index c6a51bc..5f0eea5 100644 --- a/flake.lock +++ b/flake.lock @@ -1,266 +1,5 @@ { "nodes": { - "codeium": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1733165974, - "narHash": "sha256-ijRHGhvvfp7dfkb2/8iT5i2SsdZJzH/r1uh4GnoDz5Y=", - "owner": "jcdickinson", - "repo": "codeium.nvim", - "rev": "27d2b1ce8c7ba14dbf6e4504bdea8e5548be5476", - "type": "github" - }, - "original": { - "owner": "jcdickinson", - "repo": "codeium.nvim", - "type": "github" - } - }, - "devshell": { - "inputs": { - "nixpkgs": [ - "nixvim", - "nixvim", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1728330715, - "narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=", - "owner": "numtide", - "repo": "devshell", - "rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "devshell", - "type": "github" - } - }, - "flake-compat": { - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "nixvim", - "nixvim", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1733312601, - "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { - "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "git-hooks": { - "inputs": { - "flake-compat": [ - "nixvim", - "nixvim", - "flake-compat" - ], - "gitignore": "gitignore", - "nixpkgs": [ - "nixvim", - "nixvim", - "nixpkgs" - ], - "nixpkgs-stable": [ - "nixvim", - "nixvim", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1734279981, - "narHash": "sha256-NdaCraHPp8iYMWzdXAt5Nv6sA3MUzlCiGiR586TCwo0=", - "owner": "cachix", - "repo": "git-hooks.nix", - "rev": "aa9f40c906904ebd83da78e7f328cd8aeaeae785", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "git-hooks.nix", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "nixvim", - "nixvim", - "git-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "home-manager": { - "inputs": { - "nixpkgs": [ - "nixvim", - "nixvim", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1734093295, - "narHash": "sha256-hSwgGpcZtdDsk1dnzA0xj5cNaHgN9A99hRF/mxMtwS4=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "66c5d8b62818ec4c1edb3e941f55ef78df8141a8", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" - } - }, - "ixx": { - "inputs": { - "flake-utils": [ - "nixvim", - "nixvim", - "nuschtosSearch", - "flake-utils" - ], - "nixpkgs": [ - "nixvim", - "nixvim", - "nuschtosSearch", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1729958008, - "narHash": "sha256-EiOq8jF4Z/zQe0QYVc3+qSKxRK//CFHMB84aYrYGwEs=", - "owner": "NuschtOS", - "repo": "ixx", - "rev": "9fd01aad037f345350eab2cd45e1946cc66da4eb", - "type": "github" - }, - "original": { - "owner": "NuschtOS", - "ref": "v0.0.6", - "repo": "ixx", - "type": "github" - } - }, - "nix-darwin": { - "inputs": { - "nixpkgs": [ - "nixvim", - "nixvim", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1733570843, - "narHash": "sha256-sQJAxY1TYWD1UyibN/FnN97paTFuwBw3Vp3DNCyKsMk=", - "owner": "lnl7", - "repo": "nix-darwin", - "rev": "a35b08d09efda83625bef267eb24347b446c80b8", - "type": "github" - }, - "original": { - "owner": "lnl7", - "repo": "nix-darwin", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1738012240, @@ -277,69 +16,6 @@ } }, "nixpkgs_2": { - "locked": { - "lastModified": 1702346276, - "narHash": "sha256-eAQgwIWApFQ40ipeOjVSoK4TEHVd6nbSd9fApiHIw5A=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "cf28ee258fd5f9a52de6b9865cdb93a1f96d09b7", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { - "locked": { - "lastModified": 1734474063, - "narHash": "sha256-Yk7+G3aWZpl9dnPBbZievN3htxiONcLXcxwdE9n0mX4=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "86dd3715b283806e773b3cde008baf18dd8a5bf8", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_4": { - "locked": { - "lastModified": 1734119587, - "narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_5": { - "locked": { - "lastModified": 1733097829, - "narHash": "sha256-9hbb1rqGelllb4kVUCZ307G2k3/UhmA8PPGBoyuWaSw=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "2c15aa59df0017ca140d9ba302412298ab4bf22a", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_6": { "locked": { "lastModified": 1735554305, "narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=", @@ -355,83 +31,10 @@ "type": "github" } }, - "nixvim": { - "inputs": { - "codeium": "codeium", - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_3", - "nixvim": "nixvim_2", - "treefmt-nix": "treefmt-nix_2" - }, - "locked": { - "lastModified": 1734505002, - "narHash": "sha256-oH0HhXrLmDb4Q+twZOSgduU9ABzdfcEzVaSePHw4lTk=", - "owner": "pupbrained", - "repo": "nvim-config", - "rev": "d4ece42d394b92ec5fbea5e6a80c2a81a2091a63", - "type": "github" - }, - "original": { - "owner": "pupbrained", - "repo": "nvim-config", - "type": "github" - } - }, - "nixvim_2": { - "inputs": { - "devshell": "devshell", - "flake-compat": "flake-compat", - "flake-parts": "flake-parts", - "git-hooks": "git-hooks", - "home-manager": "home-manager", - "nix-darwin": "nix-darwin", - "nixpkgs": "nixpkgs_4", - "nuschtosSearch": "nuschtosSearch", - "treefmt-nix": "treefmt-nix" - }, - "locked": { - "lastModified": 1734472356, - "narHash": "sha256-RIoG3zXarfuHfzM/z/NPjoHHxl3sqYrtEatSLA1/bIk=", - "owner": "nix-community", - "repo": "nixvim", - "rev": "4f1fe403b18c45614d6b81423038a34cff371244", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "nixvim", - "type": "github" - } - }, - "nuschtosSearch": { - "inputs": { - "flake-utils": "flake-utils_3", - "ixx": "ixx", - "nixpkgs": [ - "nixvim", - "nixvim", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1733773348, - "narHash": "sha256-Y47y+LesOCkJaLvj+dI/Oa6FAKj/T9sKVKDXLNsViPw=", - "owner": "NuschtOS", - "repo": "search", - "rev": "3051be7f403bff1d1d380e4612f0c70675b44fc9", - "type": "github" - }, - "original": { - "owner": "NuschtOS", - "repo": "search", - "type": "github" - } - }, "root": { "inputs": { "nixpkgs": "nixpkgs", - "nixvim": "nixvim", - "treefmt-nix": "treefmt-nix_3", + "treefmt-nix": "treefmt-nix", "utils": "utils" } }, @@ -450,94 +53,9 @@ "type": "github" } }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_4": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "treefmt-nix": { "inputs": { - "nixpkgs": [ - "nixvim", - "nixvim", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1733761991, - "narHash": "sha256-s4DalCDepD22jtKL5Nw6f4LP5UwoMcPzPZgHWjAfqbQ=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "0ce9d149d99bc383d1f2d85f31f6ebd146e46085", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" - } - }, - "treefmt-nix_2": { - "inputs": { - "nixpkgs": "nixpkgs_5" - }, - "locked": { - "lastModified": 1733761991, - "narHash": "sha256-s4DalCDepD22jtKL5Nw6f4LP5UwoMcPzPZgHWjAfqbQ=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "0ce9d149d99bc383d1f2d85f31f6ebd146e46085", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" - } - }, - "treefmt-nix_3": { - "inputs": { - "nixpkgs": "nixpkgs_6" + "nixpkgs": "nixpkgs_2" }, "locked": { "lastModified": 1737483750, @@ -555,7 +73,7 @@ }, "utils": { "inputs": { - "systems": "systems_4" + "systems": "systems" }, "locked": { "lastModified": 1731533236, diff --git a/flake.nix b/flake.nix index 2dfb3d6..bdf5cd1 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,6 @@ description = "C/C++ environment"; inputs = { - nixvim.url = "github:pupbrained/nvim-config"; nixpkgs.url = "github:NixOS/nixpkgs"; treefmt-nix.url = "github:numtide/treefmt-nix"; utils.url = "github:numtide/flake-utils"; @@ -13,7 +12,6 @@ nixpkgs, treefmt-nix, utils, - nixvim, ... }: utils.lib.eachDefaultSystem ( @@ -78,6 +76,8 @@ sdbus-cpp valgrind linuxKernel.packages.linux_zen.perf.out + xorg.libX11 + wayland ]); darwinPkgs = nixpkgs.lib.optionals stdenv.isDarwin (with pkgs.pkgsStatic.darwin.apple_sdk.frameworks; [ diff --git a/meson.build b/meson.build index 73791ae..cb8ef88 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,8 @@ if host_machine.system() == 'darwin' deps += dependency('iconv') elif host_machine.system() == 'linux' or host_machine.system() == 'freebsd' deps += dependency('sdbus-c++') + deps += dependency('x11') + deps += dependency('wayland-client') endif objc_args = [] @@ -111,4 +113,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 4f7265f..bc1b7f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,15 +71,17 @@ namespace { fn SystemInfoBox(const Config& config) -> Element { // Fetch data - const std::string& name = config.general.get().name.get(); - const std::string& date = GetDate(); - const std::string& host = GetHost(); - const std::string& kernelVersion = GetKernelVersion(); - const std::string& osVersion = GetOSVersion(); - u64 memInfo = GetMemInfo(); - Weather weather = config.weather.get(); - bool nowPlayingEnabled = config.now_playing.get().enabled; - const std::string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; + const std::string& name = config.general.get().name.get(); + const std::string& date = GetDate(); + const Weather weather = config.weather.get(); + const std::string& host = GetHost(); + const std::string& kernelVersion = GetKernelVersion(); + const std::string& osVersion = GetOSVersion(); + const u64 memInfo = GetMemInfo(); + const std::string& desktopEnvironment = GetDesktopEnvironment(); + const std::string& windowManager = GetWindowManager(); + const bool nowPlayingEnabled = config.now_playing.get().enabled; + const std::string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; // Icon constants (using Nerd Font v3) constexpr const char* calendarIcon = "  "; @@ -87,7 +89,7 @@ namespace { constexpr const char* kernelIcon = "  "; constexpr const char* osIcon = "  "; constexpr const char* memoryIcon = "  "; - constexpr const char* weatherIcon = " 󰖐 "; + constexpr const char* weatherIcon = "  "; constexpr const char* musicIcon = "  "; const Color::Palette16 labelColor = Color::Yellow; const Color::Palette16 valueColor = Color::White; @@ -165,6 +167,14 @@ namespace { if (memInfo > 0) content.push_back(createRow(memoryIcon, "RAM", fmt::format("{:.2f}", BytesToGiB { memInfo }))); + content.push_back(separator() | color(borderColor)); + + if (!desktopEnvironment.empty() && desktopEnvironment != windowManager) + content.push_back(createRow(" 󰇄 ", "DE", desktopEnvironment)); + + if (!windowManager.empty()) + content.push_back(createRow("  ", "WM", windowManager)); + // Now Playing row if (nowPlayingEnabled && !nowPlaying.empty()) { content.push_back(separator() | color(borderColor)); @@ -185,6 +195,9 @@ namespace { fn main() -> i32 { const Config& config = Config::getInstance(); + DEBUG_LOG("Window Manager: {}", GetWindowManager()); + DEBUG_LOG("Desktop Environment: {}", GetDesktopEnvironment()); + Element document = hbox({ SystemInfoBox(config), filler() }); Screen screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); diff --git a/src/os/linux.cpp b/src/os/linux.cpp index ca7683d..c446356 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -1,52 +1,249 @@ #ifdef __linux__ +#include +#include +#include #include +#include #include #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"); +namespace { + fn ParseLineAsNumber(const std::string& input) -> u64 { + usize start = input.find_first_of("0123456789"); - if (start == std::string::npos) { - ERROR_LOG("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() -> u64 { + constexpr const char* path = "/proc/meminfo"; + + std::ifstream input(path); + + if (!input.is_open()) { + ERROR_LOG("Failed to open {}", path); + return 0; + } + + std::string line; + + while (std::getline(input, line)) + if (line.starts_with("MemTotal")) + return ParseLineAsNumber(line); + + ERROR_LOG("MemTotal line not found in {}", path); return 0; } - usize end = input.find_first_not_of("0123456789", start); + fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector { + const sdbus::ServiceName dbusInterface = sdbus::ServiceName("org.freedesktop.DBus"); + const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus"); + const char* dbusMethodListNames = "ListNames"; - return std::stoull(input.substr(start, end - start)); -} + const std::unique_ptr dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath); -fn MeminfoParse() -> u64 { - constexpr const char* path = "/proc/meminfo"; + std::vector names; - std::ifstream input(path); + dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names); - if (!input.is_open()) { - ERROR_LOG("Failed to open {}", path); - return 0; + std::vector mprisPlayers; + + for (const std::basic_string& name : names) + if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != std::string::npos) + mprisPlayers.push_back(name); + + return mprisPlayers; } - std::string line; + fn GetActivePlayer(const std::vector& mprisPlayers) -> string { + if (!mprisPlayers.empty()) + return mprisPlayers.front(); - while (std::getline(input, line)) - if (line.starts_with("MemTotal")) - return ParseLineAsNumber(line); + return ""; + } - ERROR_LOG("MemTotal line not found in {}", path); - return 0; + fn GetX11WindowManager() -> string { + Display* display = XOpenDisplay(nullptr); + if (!display) + return "Unknown (X11)"; + + Atom supportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False); + Atom wmName = XInternAtom(display, "_NET_WM_NAME", False); + Atom utf8String = XInternAtom(display, "UTF8_STRING", False); + + // ignore unsafe buffer access warning, can't really get around it +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + Window root = DefaultRootWindow(display); +#pragma clang diagnostic pop + + Window wmWindow = 0; + Atom actualType = 0; + int actualFormat = 0; + unsigned long nitems = 0, bytesAfter = 0; + unsigned char* data = nullptr; + + if (XGetWindowProperty( + display, + root, + supportingWmCheck, + 0, + 1, + False, + XA_WINDOW, + &actualType, + &actualFormat, + &nitems, + &bytesAfter, + &data + ) == Success && + data) { + wmWindow = *std::bit_cast(data); + XFree(data); + data = nullptr; + + if (XGetWindowProperty( + display, + wmWindow, + wmName, + 0, + 1024, + False, + utf8String, + &actualType, + &actualFormat, + &nitems, + &bytesAfter, + &data + ) == Success && + data) { + std::string name(std::bit_cast(data)); + XFree(data); + XCloseDisplay(display); + return name; + } + } + + XCloseDisplay(display); + + return "Unknown (X11)"; + } + + fn TrimHyprlandWrapper(const std::string& input) -> std::string { + if (input.find("hyprland") != std::string::npos) + return "Hyprland"; + return input; + } + + fn ReadProcessCmdline(int pid) -> std::string { + std::string path = "/proc/" + std::to_string(pid) + "/cmdline"; + std::ifstream cmdlineFile(path); + std::string cmdline; + if (std::getline(cmdlineFile, cmdline)) { + // Replace null bytes with spaces + std::ranges::replace(cmdline, '\0', ' '); + return cmdline; + } + return ""; + } + + fn DetectHyprlandSpecific() -> std::string { + // Check environment variables first + const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP"); + if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland")) { + return "Hyprland"; + } + + // Check for Hyprland's specific environment variable + if (std::getenv("HYPRLAND_INSTANCE_SIGNATURE")) { + return "Hyprland"; + } + + // Check for Hyprland socket + std::string socketPath = "/run/user/" + std::to_string(getuid()) + "/hypr"; + if (std::filesystem::exists(socketPath)) { + return "Hyprland"; + } + + return ""; + } + + fn GetWaylandCompositor() -> std::string { + // First try Hyprland-specific detection + std::string hypr = DetectHyprlandSpecific(); + if (!hypr.empty()) + return hypr; + + // Then try the standard Wayland detection + wl_display* display = wl_display_connect(nullptr); + if (!display) + return ""; + + int fileDescriptor = wl_display_get_fd(display); + + struct ucred cred; + socklen_t len = sizeof(cred); + if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { + wl_display_disconnect(display); + return ""; + } + + // Read both comm and cmdline + std::string compositorName; + + // 1. Check comm (might be wrapped) + std::string commPath = "/proc/" + std::to_string(cred.pid) + "/comm"; + std::ifstream commFile(commPath); + if (commFile >> compositorName) { + std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n'); + compositorName.erase(removedRange.begin(), compositorName.end()); + } + + // 2. Check cmdline for actual binary reference + std::string cmdline = ReadProcessCmdline(cred.pid); + if (cmdline.find("hyprland") != std::string::npos) { + wl_display_disconnect(display); + return "Hyprland"; + } + + // 3. Check exe symlink + std::string exePath = "/proc/" + std::to_string(cred.pid) + "/exe"; + std::array buf; + ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); + if (lenBuf != -1) { + buf.at(static_cast(lenBuf)) = '\0'; + std::string exe(buf.data()); + if (exe.find("hyprland") != std::string::npos) { + wl_display_disconnect(display); + return "Hyprland"; + } + } + + wl_display_disconnect(display); + + // Final cleanup of wrapper names + return TrimHyprlandWrapper(compositorName); + } } -fn GetMemInfo() -> u64 { return MeminfoParse() * 1024; } - fn GetOSVersion() -> std::string { constexpr const char* path = "/etc/os-release"; @@ -74,32 +271,7 @@ fn GetOSVersion() -> std::string { return ""; } -fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector { - const sdbus::ServiceName dbusInterface = sdbus::ServiceName("org.freedesktop.DBus"); - const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus"); - const char* dbusMethodListNames = "ListNames"; - - const std::unique_ptr dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath); - - std::vector names; - - dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names); - - std::vector mprisPlayers; - - for (const std::basic_string& name : names) - if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != std::string::npos) - mprisPlayers.push_back(name); - - return mprisPlayers; -} - -fn GetActivePlayer(const std::vector& mprisPlayers) -> string { - if (!mprisPlayers.empty()) - return mprisPlayers.front(); - - return ""; -} +fn GetMemInfo() -> u64 { return MeminfoParse() * 1024; } fn GetNowPlaying() -> string { try { @@ -147,6 +319,115 @@ fn GetNowPlaying() -> string { return ""; } +fn GetWindowManager() -> string { + // Check environment variables first + const char* xdgSessionType = std::getenv("XDG_SESSION_TYPE"); + const char* waylandDisplay = std::getenv("WAYLAND_DISPLAY"); + + // Prefer Wayland detection if Wayland session + if ((waylandDisplay != nullptr) || (xdgSessionType && strstr(xdgSessionType, "wayland"))) { + std::string compositor = GetWaylandCompositor(); + if (!compositor.empty()) + return compositor; + + // Fallback environment check + const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP"); + if (xdgCurrentDesktop) { + std::string desktop(xdgCurrentDesktop); + std::ranges::transform(compositor, compositor.begin(), ::tolower); + if (desktop.find("hyprland") != std::string::npos) + return "hyprland"; + } + } + + // X11 detection + std::string x11wm = GetX11WindowManager(); + if (!x11wm.empty()) + return x11wm; + + return "Unknown"; +} + +fn GetDesktopEnvironment() -> string { + namespace fs = std::filesystem; + + // Check standard environment variables first + if (const char* xdgDe = std::getenv("XDG_CURRENT_DESKTOP")) { + if (xdgDe[0] != '\0') { + std::string de(xdgDe); + if (size_t colon = de.find(':'); colon != std::string::npos) + de.erase(colon); + if (!de.empty()) + return de; + } + } + + if (const char* desktopSession = std::getenv("DESKTOP_SESSION")) { + if (desktopSession[0] != '\0') { + return desktopSession; + } + } + + // Check session files in standard locations + const std::vector sessionPaths = { "/usr/share/xsessions", "/usr/share/wayland-sessions" }; + + // Map of DE identifiers to their session file patterns + const std::vector>> dePatterns = { + { "KDE", { "plasma", "plasmax11", "kde" } }, + { "GNOME", { "gnome", "gnome-xorg", "gnome-wayland" } }, + { "XFCE", { "xfce" } }, + { "MATE", { "mate" } }, + { "Cinnamon", { "cinnamon" } }, + { "Budgie", { "budgie" } }, + { "LXQt", { "lxqt" } }, + { "Unity", { "unity" } } + }; + + for (const auto& sessionPath : sessionPaths) { + if (!fs::exists(sessionPath)) + continue; + + for (const auto& entry : fs::directory_iterator(sessionPath)) { + if (!entry.is_regular_file()) + continue; + + const std::string filename = entry.path().stem(); + std::string lowercaseFilename; + std::ranges::transform(filename, std::back_inserter(lowercaseFilename), ::tolower); + + for (const auto& [deName, patterns] : dePatterns) { + for (const auto& pattern : patterns) { + if (lowercaseFilename.find(pattern) != std::string::npos) { + return deName; + } + } + } + } + } + + // Fallback: Check process-based detection + const std::vector> processChecks = { + { "plasmashell", "KDE" }, + { "gnome-shell", "GNOME" }, + { "xfce4-session", "XFCE" }, + { "mate-session", "MATE" }, + { "cinnamon-sessio", "Cinnamon" }, + { "budgie-wm", "Budgie" }, + { "lxqt-session", "LXQt" } + }; + + std::ifstream cmdline("/proc/self/environ"); + std::string envVars((std::istreambuf_iterator(cmdline)), std::istreambuf_iterator()); + + for (const auto& [process, deName] : processChecks) { + if (envVars.find(process) != std::string::npos) { + return deName; + } + } + + return "Unknown"; +} + fn GetShell() -> string { const char* shell = std::getenv("SHELL");