diff --git a/CMakeLists.txt b/CMakeLists.txt index f0cf66f..1e74232 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,34 +1,75 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.19) -# Get version file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) -string(STRIP ${VER_RAW} VER) +string(STRIP ${VER_RAW} HYPRSYSTEMINFO_VERSION) -project(hsi VERSION ${VER} LANGUAGES CXX) +add_compile_definitions(HYPRSYSTEMINFO_VERSION="${HYPRSYSTEMINFO_VERSION}") + +project( + hyprsysteminfo + VERSION ${HYPRSYSTEMINFO_VERSION} + DESCRIPTION "System info utility for Hyprland") + +include(CTest) +include(CheckIncludeFile) +include(GNUInstallDirs) + +set(PREFIX ${CMAKE_INSTALL_PREFIX}) +set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) +set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) + +find_package(glaze QUIET) +if (NOT glaze_FOUND) + set(GLAZE_VERSION v6.1.0) + message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") + include(FetchContent) + FetchContent_Declare( + glaze + GIT_REPOSITORY https://github.com/stephenberry/glaze.git + GIT_TAG ${GLAZE_VERSION} + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(glaze) +endif() + +find_package(PkgConfig REQUIRED) +pkg_check_modules( + deps + REQUIRED + IMPORTED_TARGET + hyprtoolkit + hyprutils>=0.10.2 + libdrm + libpci + pixman-1 +) set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_compile_options( + -Wall + -Wextra + -Wno-unused-parameter + -Wno-unused-value + -Wno-missing-field-initializers + -Wpedantic) +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) -find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient) -find_package(PkgConfig REQUIRED) +if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) + message(STATUS "Configuring hyprsysteminfo in Debug") + add_compile_definitions(hyprsysteminfo_DEBUG) +else() + add_compile_options(-O3) + message(STATUS "Configuring hyprsysteminfo in Release") +endif() -pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils) +file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") -qt_standard_project_setup(REQUIRES 6.5) +add_executable(hyprsysteminfo ${SRCFILES}) -add_subdirectory(src) +target_link_libraries(hyprsysteminfo PkgConfig::deps) -qt_add_resources(hyprsysteminfo "resource" - PREFIX "/" - FILES - resource/hyprlandlogo.svg -) +install( + FILES assets/install/hyprsysteminfo.desktop + DESTINATION "share/applications") -include(GNUInstallDirs) -install(TARGETS hyprsysteminfo - BUNDLE DESTINATION . - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) - -install(FILES assets/install/hyprsysteminfo.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) +install(TARGETS hyprsysteminfo) diff --git a/README.md b/README.md index 04abd68..cf15fcd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -# hyprsysteminfo -A tiny qt6/qml application to display information about the running system, +## hyprsysteminfo +A tiny hyprtoolkit application to display information about the running system, or copy diagnostics data, without the terminal. -![](./assets/showcase.png) - ## Footnotes [^1]: Some distributions may not support a graphical logo in hyprsysteminfo. To check if your distribution supports a graphical logo, run `cat /etc/os-release`. If the output includes a `LOGO` line, a graphical logo should appear in hyprsysteminfo. If there is no `LOGO` line, a graphical logo will not be displayed. diff --git a/assets/showcase.png b/assets/showcase.png index a934f49..8cbc9e1 100644 Binary files a/assets/showcase.png and b/assets/showcase.png differ diff --git a/flake.lock b/flake.lock index de8411a..607603a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,8 +1,13 @@ { "nodes": { - "hyprland-qt-support": { + "aquamarine": { "inputs": { - "hyprlang": "hyprlang", + "hyprutils": [ + "hyprutils" + ], + "hyprwayland-scanner": [ + "hyprwayland-scanner" + ], "nixpkgs": [ "nixpkgs" ], @@ -11,37 +16,63 @@ ] }, "locked": { - "lastModified": 1737634706, - "narHash": "sha256-nGCibkfsXz7ARx5R+SnisRtMq21IQIhazp6viBU8I/A=", + "lastModified": 1767024902, + "narHash": "sha256-sMdk6QkMDhIOnvULXKUM8WW8iyi551SWw2i6KQHbrrU=", "owner": "hyprwm", - "repo": "hyprland-qt-support", - "rev": "8810df502cdee755993cb803eba7b23f189db795", + "repo": "aquamarine", + "rev": "b8a0c5ba5a9fbd2c660be7dd98bdde0ff3798556", "type": "github" }, "original": { "owner": "hyprwm", - "repo": "hyprland-qt-support", + "repo": "aquamarine", + "type": "github" + } + }, + "hyprgraphics": { + "inputs": { + "hyprutils": [ + "hyprutils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1766946335, + "narHash": "sha256-MRD+Jr2bY11MzNDfenENhiK6pvN+nHygxdHoHbZ1HtE=", + "owner": "hyprwm", + "repo": "hyprgraphics", + "rev": "4af02a3925b454deb1c36603843da528b67ded6c", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprgraphics", "type": "github" } }, "hyprlang": { "inputs": { - "hyprutils": "hyprutils", + "hyprutils": [ + "hyprutils" + ], "nixpkgs": [ - "hyprland-qt-support", "nixpkgs" ], "systems": [ - "hyprland-qt-support", "systems" ] }, "locked": { - "lastModified": 1737634606, - "narHash": "sha256-W7W87Cv6wqZ9PHegI6rH1+ve3zJPiyevMFf0/HwdbCQ=", + "lastModified": 1764612430, + "narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "f41271d35cc0f370d300413d756c2677f386af9d", + "rev": "0d00dc118981531aa731150b6ea551ef037acddd", "type": "github" }, "original": { @@ -50,25 +81,59 @@ "type": "github" } }, + "hyprtoolkit": { + "inputs": { + "aquamarine": [ + "aquamarine" + ], + "hyprgraphics": [ + "hyprgraphics" + ], + "hyprlang": [ + "hyprlang" + ], + "hyprutils": [ + "hyprutils" + ], + "hyprwayland-scanner": [ + "hyprwayland-scanner" + ], + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1767025790, + "narHash": "sha256-LEGGn0KRXr3swO80ESKE72KR6J4arRX/9psBnNF7O0A=", + "owner": "hyprwm", + "repo": "hyprtoolkit", + "rev": "b42b3281d766e67eca990dba89e85cc7c1d3e26d", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprtoolkit", + "type": "github" + } + }, "hyprutils": { "inputs": { "nixpkgs": [ - "hyprland-qt-support", - "hyprlang", "nixpkgs" ], "systems": [ - "hyprland-qt-support", - "hyprlang", "systems" ] }, "locked": { - "lastModified": 1737632363, - "narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=", + "lastModified": 1766253372, + "narHash": "sha256-1+p4Kw8HdtMoFSmJtfdwjxM4bPxDK9yg27SlvUMpzWA=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "006620eb29d54ea9086538891404c78563d1bae1", + "rev": "51a4f93ce8572e7b12b7284eb9e6e8ebf16b4be9", "type": "github" }, "original": { @@ -77,7 +142,7 @@ "type": "github" } }, - "hyprutils_2": { + "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" @@ -87,26 +152,52 @@ ] }, "locked": { - "lastModified": 1737632363, - "narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=", + "lastModified": 1763640274, + "narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=", "owner": "hyprwm", - "repo": "hyprutils", - "rev": "006620eb29d54ea9086538891404c78563d1bae1", + "repo": "hyprwayland-scanner", + "rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671", "type": "github" }, "original": { "owner": "hyprwm", - "repo": "hyprutils", + "repo": "hyprwayland-scanner", + "type": "github" + } + }, + "hyprwire": { + "inputs": { + "hyprutils": [ + "hyprutils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1766253414, + "narHash": "sha256-O70C7PD8r/8fwrVofU5gaswXrQ7WFg0m/M1eWnm3+1U=", + "owner": "hyprwm", + "repo": "hyprwire", + "rev": "c0541f6fa55bfc98a7bfdfe07b0e448d616a3a1b", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprwire", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1737469691, - "narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=", + "lastModified": 1766902085, + "narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab", + "rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4", "type": "github" }, "original": { @@ -118,8 +209,13 @@ }, "root": { "inputs": { - "hyprland-qt-support": "hyprland-qt-support", - "hyprutils": "hyprutils_2", + "aquamarine": "aquamarine", + "hyprgraphics": "hyprgraphics", + "hyprlang": "hyprlang", + "hyprtoolkit": "hyprtoolkit", + "hyprutils": "hyprutils", + "hyprwayland-scanner": "hyprwayland-scanner", + "hyprwire": "hyprwire", "nixpkgs": "nixpkgs", "systems": "systems" } diff --git a/flake.nix b/flake.nix index a842cb6..982183c 100644 --- a/flake.nix +++ b/flake.nix @@ -5,16 +5,56 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; + aquamarine = { + url = "github:hyprwm/aquamarine"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + inputs.hyprutils.follows = "hyprutils"; + inputs.hyprwayland-scanner.follows = "hyprwayland-scanner"; + }; + + hyprgraphics = { + url = "github:hyprwm/hyprgraphics"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + inputs.hyprutils.follows = "hyprutils"; + }; + hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; - hyprland-qt-support = { - url = "github:hyprwm/hyprland-qt-support"; + hyprlang = { + url = "github:hyprwm/hyprlang"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; + inputs.hyprutils.follows = "hyprutils"; + }; + + hyprwayland-scanner = { + url = "github:hyprwm/hyprwayland-scanner"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + }; + + hyprwire = { + url = "github:hyprwm/hyprwire"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + inputs.hyprutils.follows = "hyprutils"; + }; + + hyprtoolkit = { + url = "github:hyprwm/hyprtoolkit"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + inputs.aquamarine.follows = "aquamarine"; + inputs.hyprutils.follows = "hyprutils"; + inputs.hyprlang.follows = "hyprlang"; + inputs.hyprgraphics.follows = "hyprgraphics"; + inputs.hyprwayland-scanner.follows = "hyprwayland-scanner"; }; }; diff --git a/nix/default.nix b/nix/default.nix index dc33dc8..fcf4b9a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -4,46 +4,48 @@ stdenv, cmake, - qt6, pkg-config, + aquamarine, + cairo, + glaze-hyprsysteminfo, + hyprgraphics, + hyprtoolkit, hyprutils, + libdrm, pciutils, - hyprland-qt-support, + pixman, version ? "0", -}: let - inherit (lib.strings) makeBinPath; -in - stdenv.mkDerivation { - pname = "hyprsysteminfo"; - inherit version; +}: - src = nix-gitignore.gitignoreSource [] ./..; +stdenv.mkDerivation { + pname = "hyprsysteminfo"; + inherit version; - nativeBuildInputs = [ - cmake - pkg-config - qt6.wrapQtAppsHook - ]; + src = nix-gitignore.gitignoreSource [] ./..; - buildInputs = [ - qt6.qtbase - qt6.qtdeclarative - qt6.qtsvg - qt6.qtwayland - hyprutils - hyprland-qt-support - ]; + nativeBuildInputs = [ + cmake + pkg-config + ]; - preFixup = '' - qtWrapperArgs+=(--prefix PATH : "${makeBinPath [pciutils]}") - ''; + buildInputs = [ + aquamarine + cairo + glaze-hyprsysteminfo + hyprgraphics + hyprtoolkit + hyprutils + libdrm + pciutils + pixman + ]; - meta = { - description = "A tiny qt6/qml application to display information about the running system"; - homepage = "https://github.com/hyprwm/hyprsysteminfo"; - license = lib.licenses.bsd3; - maintainers = [lib.maintainers.fufexan]; - mainProgram = "hyprsysteminfo"; - platforms = lib.platforms.linux; - }; - } + meta = { + description = "System info utility for Hyprland"; + homepage = "https://github.com/hyprwm/hyprsysteminfo"; + license = lib.licenses.bsd3; + maintainers = [lib.maintainers.fufexan]; + mainProgram = "hyprsysteminfo"; + platforms = lib.platforms.linux; + }; +} diff --git a/nix/overlays.nix b/nix/overlays.nix index ffab21d..24ed99f 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -14,12 +14,28 @@ in { default = self.overlays.hyprsysteminfo; hyprsysteminfo = lib.composeManyExtensions [ + inputs.aquamarine.overlays.default + inputs.hyprgraphics.overlays.default + inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default - inputs.hyprland-qt-support.overlays.default + inputs.hyprwayland-scanner.overlays.default + inputs.hyprtoolkit.overlays.default + inputs.hyprwire.overlays.default + self.overlays.glaze (final: prev: { hyprsysteminfo = final.callPackage ./. { version = "${version}+date=${date}_${self.shortRev or "dirty"}"; + stdenv = final.gcc15Stdenv; }; }) ]; + + # Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true. + # Since we don't include openssl, the build failes without the `enableSSL = false;` override + glaze = final: prev: { + glaze-hyprsysteminfo = prev.glaze.override { + enableSSL = false; + enableInterop = false; + }; + }; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 0bac937..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -find_package(Qt6 REQUIRED COMPONENTS WaylandClientPrivate) - -qt_add_executable(hyprsysteminfo - main.cpp - util/Utils.cpp - SystemInfo.cpp - SystemIconProvider.cpp - WaylandScreen.cpp -) - -qt_add_qml_module(hyprsysteminfo - URI org.hyprland.systeminfo - VERSION 1.0 - QML_FILES main.qml -) - -target_link_libraries(hyprsysteminfo PRIVATE - Qt6::Widgets Qt6::QuickControls2 Qt6::WaylandClientPrivate PkgConfig::hyprutils -) diff --git a/src/SystemIconProvider.cpp b/src/SystemIconProvider.cpp deleted file mode 100644 index 9900e78..0000000 --- a/src/SystemIconProvider.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "SystemIconProvider.hpp" -#include -#include -#include - -QPixmap CSystemIconProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) { - auto icon = QIcon::fromTheme(id); - - if (!requestedSize.isValid()) { - qCritical() << "Icon requests without an explicit size are not allowed."; - return QPixmap(); - } - - auto pixmap = icon.pixmap(requestedSize.width(), requestedSize.height()); - - if (pixmap.isNull()) { - qWarning() << "Could not load icon" << id; - return QPixmap(); - } - - if (size != nullptr) - *size = pixmap.size(); - - return pixmap; -} diff --git a/src/SystemIconProvider.hpp b/src/SystemIconProvider.hpp deleted file mode 100644 index bd4b3a8..0000000 --- a/src/SystemIconProvider.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -class CSystemIconProvider : public QQuickImageProvider { - public: - CSystemIconProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {} - - QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override; -}; diff --git a/src/SystemInfo.cpp b/src/SystemInfo.cpp deleted file mode 100644 index ea8235b..0000000 --- a/src/SystemInfo.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include "SystemInfo.hpp" -#include "WaylandScreen.hpp" -#include "util/Utils.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Hyprutils::String; - -CSystemInternals::CSystemInternals(QObject* parent) : QObject(parent) { - setenv("LC_ALL", "en_US.UTF-8", true); - - // gather data from os-release - if (auto data = readFile("/etc/os-release")) { - CVarList lines(data.value(), 0, '\n'); - - for (auto& line : lines) { - CVarList param(line, 2, '='); - - const auto& key = param[0]; - auto value = param[1]; - - if (value.length() >= 2 && value.at(0) == '\"') - value = value.substr(1, value.length() - 2); - - if (key == "PRETTY_NAME") { - systemName = QString::fromLocal8Bit(value); - continue; - } - - if (key == "HOME_URL") { - systemURL = QString::fromLocal8Bit(value); - continue; - } - - if (key == "LOGO") { - systemLogoName = QString::fromLocal8Bit(value); - continue; - } - } - } - - // get kernel ver - utsname unamebuf; - if (uname(&unamebuf) == 0) { - systemKernel = unamebuf.release; - } - - // get hyprland info - if (getenv("HYPRLAND_INSTANCE_SIGNATURE")) { - hlSystemVersion = execAndGet("hyprctl", {"version"}); - auto DATA = hlSystemVersion.toStdString(); - - if (DATA.contains("Tag: ")) { - auto temp = DATA.substr(DATA.find("Tag: ") + 5); - temp = temp.substr(0, temp.find(",")); - hyprlandVersionLong = QString::fromLocal8Bit(temp); - hyprlandVersion = QString::fromLocal8Bit(temp.substr(0, temp.find("-"))); - } - - if (hyprlandVersionLong.length() <= 0 && DATA.contains("at commit")) { - auto temp = DATA.substr(DATA.find("at commit") + 10); - temp = temp.substr(0, temp.find(" ")); - hyprlandVersionLong = QString::fromLocal8Bit(temp.substr(0, 7)); - hyprlandVersion = QString::fromLocal8Bit(temp.substr(0, 7)); - } - - if (hyprlandVersionLong.isEmpty()) { - hyprlandVersionLong = QStringLiteral("unknown"); - hyprlandVersion = QStringLiteral("unknown"); - } - - hlSystemInfo = execAndGet("hyprctl", {"systeminfo"}); - if (!hlSystemInfo.contains("Hyprland")) - hlSystemInfo = ""; - } - - // get cpu info - { - const auto DATA = execAndGet("lscpu").toStdString(); - if (DATA.contains("odel name")) { - std::string arch, model, ghz, nproc; - - CVarList data(DATA, 0, '\n'); - for (auto& line : data) { - std::string left, right; - left = trim(line.substr(0, line.find(":"))); - right = trim(line.substr(line.find(":") + 1)); - - if (left == "Architecture") { - arch = right; - continue; - } - if (left == "Model name") { - model = right; - continue; - } - if (left == "CPU(s)") { - nproc = right; - continue; - } - if (left == "CPU max MHz") { - try { - ghz = std::format("{:.02}GHz", std::stof(right) / 1000.F); - } catch (...) { ghz = "?GHz"; } - continue; - } - } - - cpuInfo = QString::fromLocal8Bit(std::format("{} at {}x{} ({})", model, nproc, ghz, arch)); - } - } - - // get gpu info - { - auto ok = false; - const auto DATA = execAndGet("lspci", {"-vnn"}, &ok).toStdString(); - CVarList lines(DATA, 0, '\n'); - - if (ok) { - for (auto& line : lines) { - if (!line.contains("VGA")) - continue; - gpuInfo.emplace_back(QString::fromLocal8Bit(std::format("{}", line.substr(line.find(":", line.find("VGA")) + 2)))); - } - - if (gpuInfo.isEmpty()) - gpuInfo.emplaceBack(QStringLiteral("No GPUs found")); - } else - gpuInfo.emplaceBack(QStringLiteral("missing dependency: lspci")); - } - - // get ram info - { - const auto DATA = execAndGet("free").toStdString(); - if (DATA.contains("total")) { - CVarList data(DATA, 0, '\n'); - - auto ramIntToReadable = [](const std::string& datapoint) -> std::string { - try { - auto asInt = std::stoull(datapoint); - return std::format("{:.03}GB", asInt / 1000000.0); - } catch (...) { return "[error]"; } - }; - - CVarList props(data[1], 0, 's', true); - - ramInfo = QString::fromLocal8Bit(std::format("{} / {}", ramIntToReadable(props[2]), ramIntToReadable(props[1]))); - } - } - - // other, misc - if (auto current = qEnvironmentVariable("XDG_CURRENT_DESKTOP"); !current.isEmpty()) - DE = current; - - if (auto procUptime = readFile("/proc/uptime")) { - CVarList data(procUptime.value(), 0, 's', true); - - try { - int uptimeSeconds = std::round(std::stof(data[0])); - int uptimeDays = std::floor(uptimeSeconds / 3600.0 / 24.0); - int uptimeHours = std::floor((uptimeSeconds % (3600 * 24)) / 3600.0); - int uptimeMinutes = std::floor((uptimeSeconds % (3600)) / 60.0); - - auto upStr = std::format("{}{}{}", (uptimeDays > 0 ? std::format("{} days, ", uptimeDays) : ""), (uptimeHours > 0 ? std::format("{} hours, ", uptimeHours) : ""), - (uptimeMinutes > 0 ? std::format("{} minutes, ", uptimeMinutes) : "")); - - if (!upStr.empty()) - upStr = upStr.substr(0, upStr.length() - 2); - - uptime = QString::fromLocal8Bit(upStr); - } catch (...) { ; } - } - - { - std::string screens; - for (const auto& s : SWaylandScreenInfo::enumerateScreens()) { - screens += std::format("{} ({}x{}), ", s.name.toStdString(), s.pixelSize.width(), s.pixelSize.height()); - } - - if (!screens.empty()) - screens = screens.substr(0, screens.length() - 2); - - this->screens = QString::fromLocal8Bit(screens); - } - - if (auto* username = getlogin()) { - std::array hostname; - if (gethostname(hostname.data(), hostname.size()) == 0) - user = QString::fromLocal8Bit(std::format("{}@{}", username, hostname.data())); - } - - { - if (auto productName = readFile("/sys/devices/virtual/dmi/id/product_name")) - board = QString::fromLocal8Bit(trim(productName.value())); - else if (auto boardName = readFile("/sys/devices/virtual/dmi/id/board_name")) - board = QString::fromLocal8Bit(trim(boardName.value())); - } -} - -void CSystemInternals::copySystemInfo() { - QGuiApplication::clipboard()->setText(hlSystemInfo); - execAndGet("hyprctl", {"notify", "5", "5000", "0", "Copied system info to the clipboard."}); -} - -void CSystemInternals::copyVersion() { - QGuiApplication::clipboard()->setText(hlSystemVersion); - execAndGet("hyprctl", {"notify", "5", "5000", "0", "Copied version info to the clipboard."}); -} diff --git a/src/SystemInfo.hpp b/src/SystemInfo.hpp deleted file mode 100644 index 3c91a39..0000000 --- a/src/SystemInfo.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -class CSystemInternals : public QObject { - Q_OBJECT; - QML_NAMED_ELEMENT(SystemInfo); - QML_SINGLETON; - Q_PROPERTY(QString systemLogoName MEMBER systemLogoName CONSTANT); - Q_PROPERTY(QString systemName MEMBER systemName CONSTANT); - Q_PROPERTY(QString systemUrl MEMBER systemURL CONSTANT); - Q_PROPERTY(QString systemKernel MEMBER systemKernel CONSTANT); - Q_PROPERTY(QString hyprlandVersion MEMBER hyprlandVersion CONSTANT); - Q_PROPERTY(QString hyprlandVersionLong MEMBER hyprlandVersionLong CONSTANT); - Q_PROPERTY(QString cpuInfo MEMBER cpuInfo CONSTANT); - Q_PROPERTY(QVector gpuInfo MEMBER gpuInfo CONSTANT); - Q_PROPERTY(QString ramInfo MEMBER ramInfo CONSTANT); - Q_PROPERTY(QString uptime MEMBER uptime CONSTANT); - Q_PROPERTY(QString de MEMBER DE CONSTANT); - Q_PROPERTY(QString screens MEMBER screens CONSTANT); - Q_PROPERTY(QString model MEMBER board CONSTANT); - Q_PROPERTY(QString user MEMBER user CONSTANT); - - public: - explicit CSystemInternals(QObject* parent = nullptr); - - QString systemLogoName, systemName = "Linux", systemURL = "https://kernel.org/", systemKernel = "unknown"; - QString hyprlandVersion, hyprlandVersionLong; - - QString cpuInfo = "missing dependency: lscpu"; - QVector gpuInfo; - QString ramInfo = "?"; - - QString hlSystemInfo = "[error]", hlSystemVersion = "[error]"; - - QString uptime = "unknown", DE = "Unknown", screens = "unknown", board = "", user = ""; - - Q_INVOKABLE void copySystemInfo(); - Q_INVOKABLE void copyVersion(); -}; diff --git a/src/WaylandScreen.cpp b/src/WaylandScreen.cpp deleted file mode 100644 index 01aa7a9..0000000 --- a/src/WaylandScreen.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "WaylandScreen.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace QtWaylandClient; - -class CWaylandScreen : public QtWayland::wl_output { - public: - explicit CWaylandScreen(QWaylandDisplay* display, uint32_t version, uint32_t id); - ~CWaylandScreen() override; - Q_DISABLE_COPY_MOVE(CWaylandScreen); - SWaylandScreenInfo info; - - protected: - void output_mode(uint32_t flags, int32_t width, int32_t height, int32_t refresh) override; - void output_name(const QString& name) override; -}; - -std::vector SWaylandScreenInfo::enumerateScreens() { - auto* display = QWaylandIntegration::instance()->display(); - - std::vector screens; - for (const auto& global : display->globals()) { - if (global.interface == QStringLiteral("wl_output")) { - screens.emplace_back(new CWaylandScreen(display, global.version, global.id)); - } - } - - display->forceRoundTrip(); - - std::vector info; - for (auto* screen : screens) { - info.push_back(screen->info); - delete screen; - } - - return info; -} - -CWaylandScreen::CWaylandScreen(QWaylandDisplay* display, uint32_t version, uint32_t id) : QtWayland::wl_output(display->wl_registry(), id, 4u) {} - -CWaylandScreen::~CWaylandScreen() { - release(); -} - -void CWaylandScreen::output_mode(uint32_t flags, int width, int height, int refresh) { - info.pixelSize = QSize(width, height); -} - -void CWaylandScreen::output_name(const QString& name) { - info.name = name; -} diff --git a/src/WaylandScreen.hpp b/src/WaylandScreen.hpp deleted file mode 100644 index 3033325..0000000 --- a/src/WaylandScreen.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -struct SWaylandScreenInfo { - QString name; - QSize pixelSize; - - static std::vector enumerateScreens(); -}; diff --git a/src/helpers/Logger.hpp b/src/helpers/Logger.hpp new file mode 100644 index 0000000..55544ba --- /dev/null +++ b/src/helpers/Logger.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "Memory.hpp" + +#define LOG_DEBUG Hyprutils::CLI::LOG_DEBUG +#define LOG_ERR Hyprutils::CLI::LOG_ERR +#define LOG_WARN Hyprutils::CLI::LOG_WARN +#define LOG_TRACE Hyprutils::CLI::LOG_TRACE +#define LOG_CRIT Hyprutils::CLI::LOG_CRIT + +inline UP g_logger = makeUnique(); diff --git a/src/helpers/Memory.hpp b/src/helpers/Memory.hpp new file mode 100644 index 0000000..4ddb4b6 --- /dev/null +++ b/src/helpers/Memory.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include +#include +#include + +using namespace Hyprutils::Memory; + +#define SP CSharedPointer +#define WP CWeakPointer +#define UP CUniquePointer +#define ASP CAtomicSharedPointer \ No newline at end of file diff --git a/src/icons/Icons.hpp b/src/icons/Icons.hpp new file mode 100644 index 0000000..86f2ad9 --- /dev/null +++ b/src/icons/Icons.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace Icons { + inline constexpr uint8_t HYPRLAND_LOGO[] = { +#embed "../../resource/hyprlandlogo.svg" + }; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0089af0..9ad81d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,21 +1,278 @@ -#include "SystemIconProvider.hpp" -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -int main(int argc, char* argv[]) { - QApplication app(argc, argv); +#include +#include +#include - app.setApplicationName("Hyprland System Info"); +#include "utils/SystemInfo.hpp" +#include "utils/HyprlandIPC.hpp" - if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) - QQuickStyle::setStyle("org.hyprland.style"); +#include - QQmlApplicationEngine engine; - engine.addImageProvider("systemIcons", new CSystemIconProvider); - QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - engine.load("qrc:/qt/qml/org/hyprland/systeminfo/main.qml"); +using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; +using namespace Hyprutils::OS; +using namespace Hyprtoolkit; - return app.exec(); +#define SP CSharedPointer +#define WP CWeakPointer +#define UP CUniquePointer + +static SP backend; + +// +static SP space(SP e) { + auto n = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); + n->addChild(e); + e->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); + e->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_LEFT, true); + e->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_VCENTER, true); + return n; } + +int main(int argc, char** argv, char** envp) { + setenv("HT_QUIET", "1", true); + backend = IBackend::create(); + + const auto FONT_SIZE = CFontSize{CFontSize::HT_FONT_TEXT}.ptSize(); + const auto WINDOW_SIZE = Vector2D{FONT_SIZE * 100.F, FONT_SIZE * 60.F}; + + auto window = + CWindowBuilder::begin()->preferredSize(WINDOW_SIZE)->minSize(WINDOW_SIZE)->maxSize(WINDOW_SIZE)->appTitle("System Information")->appClass("hyprsysteminfo")->commence(); + + window->m_rootElement->addChild(CRectangleBuilder::begin()->color([] { return backend->getPalette()->m_colors.background; })->commence()); + + auto mainLayout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->gap(2)->commence(); + mainLayout->setMargin(4); + + window->m_rootElement->addChild(mainLayout); + + auto topLayout = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); + + // Left: Distro + { + auto distroNull = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, FONT_SIZE * 10.F}})->commence(); + auto distroLayout = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {0.9F, 1.F}})->commence(); + distroLayout->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); + distroLayout->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true); + + auto spacer1 = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); + spacer1->setGrow(true); + + distroLayout->addChild(spacer1); + + auto distroLogoName = Info::getDistroLogoName(); + if (distroLogoName) { + auto icon = backend->systemIcons()->lookupIcon(*distroLogoName); + + if (icon->exists()) { + auto img = CImageBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_PERCENT, {1.F, 0.9F}})->icon(icon)->commence(); + distroLayout->addChild(img); + } + } + + auto distroTextLayout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5F, 1.F}})->commence(); + + auto distroTextA = CTextBuilder::begin() + ->text(Info::getFromEtcOsRelease("NAME").value_or("Unknown")) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence(); + auto distroTextB = CTextBuilder::begin() + ->text(std::format("{}", Info::getFromEtcOsRelease("HOME_URL").value_or(""), Info::getFromEtcOsRelease("HOME_URL").value_or(""))) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence(); + auto distroTextC = + CTextBuilder::begin()->text(Info::kernel())->fontSize({CFontSize::HT_FONT_TEXT, 1.F})->color([] { return backend->getPalette()->m_colors.text; })->commence(); + + distroTextLayout->addChild(distroTextA); + distroTextLayout->addChild(distroTextB); + distroTextLayout->addChild(distroTextC); + + distroLayout->addChild(distroTextLayout); + + distroNull->addChild(distroLayout); + + topLayout->addChild(distroNull); + } + + // Right: Hyprland + + { + auto hlNull = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_ABSOLUTE, {0.5F, FONT_SIZE * 10.F}})->commence(); + auto hlLayout = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_PERCENT, {0.9F, 1.F}})->commence(); + hlLayout->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); + hlLayout->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true); + + auto spacer1 = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); + spacer1->setGrow(true); + + auto logoData = Info::deLogo(); + if (logoData) { + std::vector data; + data.resize(logoData->size()); + std::memcpy(data.data(), logoData->data(), logoData->size()); + + auto img = CImageBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_PERCENT, {1.F, 0.9F}})->data(std::move(data))->commence(); + hlLayout->addChild(img); + } + + auto hlTextLayout = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5F, 1.F}})->commence(); + + auto hlTextA = + CTextBuilder::begin()->text(Info::desktop())->fontSize({CFontSize::HT_FONT_TEXT, 1.F})->color([] { return backend->getPalette()->m_colors.text; })->commence(); + auto hlTextB = CTextBuilder::begin() + ->text(std::format("{}", Info::desktopHome(), Info::desktopHome())) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence(); + auto hlTextC = + CTextBuilder::begin()->text(Info::desktopVersion())->fontSize({CFontSize::HT_FONT_TEXT, 1.F})->color([] { return backend->getPalette()->m_colors.text; })->commence(); + + hlTextLayout->addChild(hlTextA); + hlTextLayout->addChild(hlTextB); + hlTextLayout->addChild(hlTextC); + + hlLayout->addChild(hlTextLayout); + + hlLayout->addChild(spacer1); + + hlNull->addChild(hlLayout); + + topLayout->addChild(hlNull); + } + + mainLayout->addChild(topLayout); + + // Spacer, then system props + + auto hr = Hyprtoolkit::CRectangleBuilder::begin() // + ->color([] { return Hyprtoolkit::CHyprColor{backend->getPalette()->m_colors.text.darken(0.65)}; }) + ->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_ABSOLUTE, {1.F, 9.F}}) + ->commence(); + hr->setMargin(4); + + mainLayout->addChild(hr); + + auto container = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.95F, 1.F}})->gap(2)->commence(); + + container->setMargin(10); + + container->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); + container->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true); + + container->addChild(space(CTextBuilder::begin() + ->text(std::format("User: {}", Info::user())) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence())); + + container->addChild(space(CTextBuilder::begin() + ->text(std::format("Model: {}", Info::model())) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence())); + + container->addChild(space(CTextBuilder::begin() + ->text(std::format("CPU: {}", Info::cpu())) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence())); + + for (const auto& g : Info::gpus()) { + container->addChild(space(CTextBuilder::begin() + ->text(std::format("GPU: {}", g)) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence())); + } + + container->addChild(space(CTextBuilder::begin() + ->text(std::format("RAM: {}", Info::mem())) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence())); + + container->addChild(space(CTextBuilder::begin() + ->text(std::format("Uptime: {}", Info::uptime())) + ->fontSize({CFontSize::HT_FONT_TEXT, 1.F}) + ->color([] { return backend->getPalette()->m_colors.text; }) + ->commence())); + + mainLayout->addChild(container); + + // bottom layout + + auto bottomNull = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->commence(); + bottomNull->setMargin(6); + bottomNull->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); + bottomNull->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_BOTTOM, true); + + auto bottomLayout = CRowLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {1, 1}})->gap(10)->commence(); + + auto spacer = CNullBuilder::begin()->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 1}})->commence(); + spacer->setGrow(true, false); + + bottomLayout->addChild(spacer); + + bottomLayout->addChild( // + CButtonBuilder::begin() + ->label("Copy Hyprland System Info") + ->onMainClick([](SP e) { + // FIXME: toolkit needs to provide clipboard stuff + + auto info = HyprlandIPC::getFromSocket("/systeminfo"); + + if (!info) { + e->rebuild()->label("Failed copying")->commence(); + return; + } + + CProcess proc("wl-copy", {*info}); + if (!proc.runAsync()) { // FIXME: why does runSync hang?! + e->rebuild()->label("Failed copying")->commence(); + return; + } + + e->rebuild()->label("Copied!")->commence(); + }) + ->commence() // + ); + + bottomLayout->addChild( // + CButtonBuilder::begin() + ->label("Exit") + ->onMainClick([w = WP{window}](SP e) { + w->close(); + backend->destroy(); + }) + ->commence() // + ); + + // + + bottomNull->addChild(bottomLayout); + window->m_rootElement->addChild(bottomNull); + + window->m_events.closeRequest.listenStatic([w = WP{window}] { + w->close(); + backend->destroy(); + }); + + window->open(); + + backend->enterLoop(); + + return 0; +} \ No newline at end of file diff --git a/src/main.qml b/src/main.qml deleted file mode 100644 index 18c9db3..0000000 --- a/src/main.qml +++ /dev/null @@ -1,207 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import org.hyprland.systeminfo - -ApplicationWindow { - id: window - - FontMetrics { id: fontMetrics } - - property var firstPanelHeight: fontMetrics.height * 7 - - minimumWidth: Math.max(fontMetrics.height * 50, mainLayout.Layout.minimumWidth) + mainLayout.anchors.margins * 2 - minimumHeight: Math.max(fontMetrics.height * 30, mainLayout.Layout.minimumHeight) + mainLayout.anchors.margins * 2 - maximumWidth: minimumWidth - maximumHeight: minimumHeight - visible: true - - component Separator: Rectangle { - color: Qt.darker(window.palette.text, 1.5) - } - - component VSeparator: Separator { - implicitWidth: 1 - Layout.fillHeight: true - Layout.topMargin: fontMetrics.height - Layout.bottomMargin: fontMetrics.height - } - - component HSeparator: Separator { - implicitHeight: 1 - Layout.fillWidth: true - Layout.leftMargin: fontMetrics.height * 8 - Layout.rightMargin: fontMetrics.height * 8 - } - - SystemPalette { - id: system - colorGroup: SystemPalette.Active - } - - ColumnLayout { - id: mainLayout - spacing: fontMetrics.height - - anchors { - fill: parent - margins: 4 - } - - RowLayout { - // First panel hyprland and distro info - Layout.preferredHeight: firstPanelHeight - Layout.maximumHeight: firstPanelHeight - Layout.topMargin: fontMetrics.height - spacing: fontMetrics.height - - Item { Layout.fillWidth: true } - - RowLayout { - id: distroLogoName - spacing: fontMetrics.height - - Image { - visible: SystemInfo.systemLogoName != "" - source: "image://systemIcons/" + SystemInfo.systemLogoName - sourceSize.width: firstPanelHeight - sourceSize.height: firstPanelHeight - Layout.preferredWidth: firstPanelHeight - Layout.preferredHeight: firstPanelHeight - Layout.alignment: Qt.AlignCenter - smooth: true - } - - ColumnLayout { - id: distroText - - Layout.preferredWidth: hyprlandInfo.visible ? Math.max(Layout.minimumWidth, hyprlandText.Layout.minimumWidth) : Layout.minimumWidth - spacing: 2 - Layout.alignment: Qt.AlignVCenter - - Label { - color: system.windowText - text: SystemInfo.systemName - Layout.alignment: Qt.AlignHCenter - } - - Label { - color: system.windowText - text: SystemInfo.systemUrl - Layout.alignment: Qt.AlignHCenter - } - - Label { - color: system.windowText - text: SystemInfo.systemKernel - Layout.alignment: Qt.AlignHCenter - } - } - } - - Item { visible: hyprlandInfo.visible; Layout.fillWidth: true } - VSeparator { visible: hyprlandInfo.visible } - Item { visible: hyprlandInfo.visible; Layout.fillWidth: true } - - RowLayout { - id: hyprlandInfo - visible: SystemInfo.hyprlandVersionLong != "" - spacing: fontMetrics.height - - Image { - source: "qrc:/resource/hyprlandlogo.svg" - sourceSize.width: firstPanelHeight - sourceSize.height: firstPanelHeight - Layout.preferredWidth: firstPanelHeight - Layout.preferredHeight: firstPanelHeight - Layout.alignment: Qt.AlignCenter - smooth: true - } - - ColumnLayout { - id: hyprlandText - Layout.preferredWidth: Math.max(Layout.minimumWidth, distroText.Layout.minimumWidth) - spacing: 2 - Layout.alignment: Qt.AlignVCenter - - Label { - color: system.windowText - text: "Hyprland" - Layout.alignment: Qt.AlignHCenter - } - - Label { - color: system.windowText - text: SystemInfo.hyprlandVersion - Layout.alignment: Qt.AlignHCenter - } - - Label { - color: system.windowText - visible: SystemInfo.hyprlandVersion != text - text: SystemInfo.hyprlandVersionLong - Layout.alignment: Qt.AlignHCenter - } - } - } - - Item { Layout.fillWidth: true } - } - - HSeparator {} - - ColumnLayout { - spacing: 6 - Layout.leftMargin: fontMetrics.height * 4 - Layout.rightMargin: fontMetrics.height * 4 - - component DetailsLabel: Label { - color: system.windowText - Layout.fillWidth: true - elide: Text.ElideRight - wrapMode: Text.NoWrap - } - - DetailsLabel { text: "User: " + SystemInfo.user; visible: text != "" } - DetailsLabel { text: "Model: " + SystemInfo.model; visible: text != "" } - DetailsLabel { text: "CPU: " + SystemInfo.cpuInfo } - - Repeater { - model: SystemInfo.gpuInfo - - DetailsLabel { - required property string modelData - text: "GPU: " + modelData - } - } - - DetailsLabel { text: "Memory: " + SystemInfo.ramInfo } - DetailsLabel { text: "DE: " + SystemInfo.de } - DetailsLabel { text: "Uptime: " + SystemInfo.uptime } - DetailsLabel { text: "Displays: " + SystemInfo.screens } - } - - Item { Layout.fillHeight: true } - - HSeparator {} - - RowLayout { - visible: SystemInfo.hyprlandVersionLong != "" - spacing: 6 - Layout.leftMargin: 20 - Layout.rightMargin: 20 - - Button { - text: "Copy Hyprland System Info" - onClicked: SystemInfo.copySystemInfo(); - } - - Button { - text: "Copy Hyprland Version" - onClicked: SystemInfo.copyVersion(); - } - } - } -} diff --git a/src/util/Utils.cpp b/src/util/Utils.cpp deleted file mode 100644 index 22510b6..0000000 --- a/src/util/Utils.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Utils.hpp" - -#include -#include -#include -#include -#include - -QString execAndGet(const QString& program, const QStringList& arguments, bool* ok) { - QProcess process; - process.setProcessChannelMode(QProcess::SeparateChannels); - process.start(program, arguments, QIODevice::ReadOnly); - - if (!process.waitForStarted(-1)) { - qCritical() << "Failed to start process" << program << arguments; - if (ok) - *ok = false; - - return ""; - } - - if (!process.waitForFinished(-1)) { - qCritical() << "Failed to run process" << program << arguments; - if (ok) - *ok = false; - - return ""; - } - - if (ok) - *ok = true; - return process.readAll(); -} - -std::optional readFile(const std::string& filename) { - try { - std::ifstream ifs(filename); - if (ifs.good()) { - std::string data(std::istreambuf_iterator{ifs}, {}); - ifs.close(); - return data; - } - } catch (...) {} - - return {}; -} diff --git a/src/util/Utils.hpp b/src/util/Utils.hpp deleted file mode 100644 index 8b268c2..0000000 --- a/src/util/Utils.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include -#include - -QString execAndGet(const QString& program, const QStringList& arguments = {}, bool* ok = nullptr); -std::optional readFile(const std::string& filename); diff --git a/src/utils/HyprlandIPC.cpp b/src/utils/HyprlandIPC.cpp new file mode 100644 index 0000000..5074772 --- /dev/null +++ b/src/utils/HyprlandIPC.cpp @@ -0,0 +1,86 @@ +#include "HyprlandIPC.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace Hyprutils::Memory; + +static int getUID() { + const auto UID = getuid(); + const auto PWUID = getpwuid(UID); + return PWUID ? PWUID->pw_uid : UID; +} + +static std::string getRuntimeDir() { + const auto XDG = getenv("XDG_RUNTIME_DIR"); + + if (!XDG) { + const std::string USERID = std::to_string(getUID()); + return "/run/user/" + USERID + "/hypr"; + } + + return std::string{XDG} + "/hypr"; +} + +std::expected HyprlandIPC::getFromSocket(const std::string& cmd) { + static const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); + + if (!HIS || HIS[0] == '\0') + return std::unexpected("HYPRLAND_INSTANCE_SIGNATURE empty: are we under hyprland?"); + + const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0); + + auto t = timeval{.tv_sec = 5, .tv_usec = 0}; + setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)); + + if (SERVERSOCKET < 0) + return std::unexpected("couldn't open a socket (1)"); + + sockaddr_un serverAddress = {0}; + serverAddress.sun_family = AF_UNIX; + + std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock"; + + strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1); + + if (connect(SERVERSOCKET, rc(&serverAddress), SUN_LEN(&serverAddress)) < 0) + return std::unexpected(std::format("couldn't connect to the hyprland socket at {}", socketPath)); + + auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length()); + + if (sizeWritten < 0) + return std::unexpected("couldn't write (4)"); + + std::string reply = ""; + char buffer[8192] = {0}; + + sizeWritten = read(SERVERSOCKET, buffer, 8192); + + if (sizeWritten < 0) { + if (errno == EWOULDBLOCK) + return std::unexpected("Hyprland IPC didn't respond in time"); + return std::unexpected("couldn't read (5)"); + } + + reply += std::string(buffer, sizeWritten); + + while (sizeWritten == 8192) { + sizeWritten = read(SERVERSOCKET, buffer, 8192); + if (sizeWritten < 0) { + return std::unexpected("couldn't read (5)"); + } + reply += std::string(buffer, sizeWritten); + } + + close(SERVERSOCKET); + + return reply; +} diff --git a/src/utils/HyprlandIPC.hpp b/src/utils/HyprlandIPC.hpp new file mode 100644 index 0000000..a66e61d --- /dev/null +++ b/src/utils/HyprlandIPC.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace HyprlandIPC { + std::expected getFromSocket(const std::string& cmd); +}; diff --git a/src/utils/SystemInfo.cpp b/src/utils/SystemInfo.cpp new file mode 100644 index 0000000..99eb112 --- /dev/null +++ b/src/utils/SystemInfo.cpp @@ -0,0 +1,358 @@ +#include "SystemInfo.hpp" + +#include +#include +#include + +#include +#include +#include + +#include "HyprlandIPC.hpp" +#include "../helpers/Logger.hpp" +#include "../icons/Icons.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Hyprutils::File; +using namespace Hyprutils::String; + +std::optional Info::getFromEtcOsRelease(const std::string_view& sv) { + static std::string content = ""; + static bool once = true; + + if (once) { + once = false; + + auto read = readFileAsString("/etc/os-release"); + content = read.value_or(""); + } + + static CVarList2 vars(std::move(content), 0, '\n', true); + + for (const auto& v : vars) { + if (v.starts_with(sv) && v.contains('=')) { + // found + auto value = trim(v.substr(v.find('=') + 1)); + + if (value.back() == value.front() && value.back() == '"') + value = value.substr(1, value.size() - 2); + + return std::string{value}; + } + } + + return std::nullopt; +} + +std::expected Info::getDistroLogoName() { + auto v = getFromEtcOsRelease("LOGO"); + if (!v) + return std::unexpected("Prop missing"); + return *v; +} + +std::string Info::kernel() { + struct utsname un; + + if (::uname(&un) != 0) + return ""; + + return std::string(un.release); +} + +std::string Info::desktop() { + static std::string x = [] -> std::string { + auto ENV = getenv("XDG_CURRENT_DESKTOP"); + + if (!ENV) + return "Unknown"; + + if (!std::string_view{ENV}.contains(':')) + return ENV; + + CVarList2 vl(ENV, 0, ':', true); + + return std::string{vl[0]}; + }(); + return x; +} + +std::string Info::desktopHome() { + static std::string x = [] -> std::string { + auto D = desktop(); + std::ranges::transform(D, D.begin(), ::tolower); + + if (D == "hyprland") + return "https://hypr.land/"; + if (D == "sway") + return "https://swaywm.org/"; + if (D == "wayfire") + return "https://wayfire.org/"; + if (D == "kde") + return "https://kde.org/"; + if (D == "gnome") + return "https://gnome.org/"; + + return ""; + }(); + return x; +} + +std::string Info::desktopVersion() { + static std::string x = [] -> std::string { + auto D = desktop(); + std::ranges::transform(D, D.begin(), ::tolower); + + if (D == "hyprland") { + auto res = HyprlandIPC::getFromSocket("j/version"); + + if (res) { + auto json = glz::read_json(*res); + if (json && json->contains("tag")) + return (*json)["tag"].get_string(); + } + } + + return ""; + }(); + return x; +} + +std::string Info::user() { + std::string username, host; + + uid_t uid = geteuid(); + struct passwd pwd, *result = NULL; + char buf[16384]; + + if (getpwuid_r(uid, &pwd, buf, sizeof(buf), &result) == 0 && result) + username = pwd.pw_name; + + if (gethostname(buf, sizeof(buf)) == 0) { + buf[HOST_NAME_MAX] = '\0'; + host = buf; + } + + return std::format("{}@{}", username, host); +} + +std::string Info::model() { + auto manu = cat("/sys/class/dmi/id/sys_vendor"); + auto model = cat("/sys/class/dmi/id/product_name"); + + if (manu && model) { + if (model->starts_with(*manu)) + return *model; + return std::format("{} {}", *manu, *model); + } + + if (manu) + return *manu; + if (model) + return *model; + return ""; +} + +std::optional Info::cat(const std::filesystem::path& p) { + auto f = readFileAsString(p.string()); + + if (!f) + return std::nullopt; + + return trim(*f); +} + +static std::string formatKHz(const uint64_t khz) { + if (khz >= 1000000) + return std::format("{:.1f}GHz", khz / 1000000.F); + return std::format("{:.1f}MHz", khz / 1000.F); +} + +static std::optional getFromCpuinfo(const std::string_view& sv) { + static std::string content = ""; + static bool once = true; + + if (once) { + once = false; + + auto read = readFileAsString("/proc/cpuinfo"); + content = read.value_or(""); + } + + static CVarList2 vars(std::move(content), 0, '\n', true); + + for (const auto& v : vars) { + if (v.starts_with(sv) && v.contains(':')) { + // found + auto value = trim(v.substr(v.find(':') + 2)); + + if (value.back() == value.front() && value.back() == '"') + value = value.substr(1, value.size() - 2); + + return std::string{value}; + } + } + + return std::nullopt; +} + +std::string Info::cpu() { + auto nproc = sysconf(_SC_NPROCESSORS_ONLN); + std::string hz = "?GHz"; + try { + hz = formatKHz(std::stoull(cat("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq").value())); + } catch (...) { + ; // oh well + } + + struct utsname u; + std::string arch; + if (uname(&u) == 0) + arch = u.machine; + + // try to read proc names from /proc/cpuinfo + std::string cpuname = getFromCpuinfo("model name").value_or("Unknown"); + + return std::format("{} {}x{} ({})", cpuname, nproc, hz, arch); +} + +static std::optional readU32Hex(const std::string& p) { + auto str = readFileAsString(p); + + if (!str) + return std::nullopt; + + auto s = std::move(*str); + + if (s.rfind("0x", 0) == 0) + s.erase(0, 2); + + try { + return sc(std::stoul(s, nullptr, 16)); + } catch (...) { return std::nullopt; } +} + +std::vector Info::gpus() { + glob_t g = {0}; + if (glob("/sys/class/drm/card[0-9]*", 0, NULL, &g) != 0) + return {}; + + struct pci_access* pacc = pci_alloc(); + pci_init(pacc); + pci_scan_bus(pacc); + + std::vector gpuNames; + + for (size_t i = 0; i < g.gl_pathc; i++) { + const char* cardpath = g.gl_pathv[i]; + + std::filesystem::path vendorPath = std::filesystem::path{cardpath} / "device" / "vendor"; + std::filesystem::path devicePath = std::filesystem::path{cardpath} / "device" / "device"; + std::filesystem::path classPath = std::filesystem::path{cardpath} / "device" / "class"; + + uint32_t vendor = 0, device = 0; + + if (auto ret = readU32Hex(vendorPath.string()); ret) + vendor = *ret; + else + continue; + + if (auto ret = readU32Hex(devicePath.string()); ret) + device = *ret; + else + continue; + + if (auto ret = readU32Hex(classPath.string()); ret) { + if (((*ret) & 0xFF0000u) != 0x030000u) // only keep PCI class 0x03xxxx (display controller) + continue; + } + + static char namebuf[512] = {0}; + pci_lookup_name(pacc, namebuf, sizeof(namebuf), PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE, vendor, device); + + gpuNames.emplace_back(namebuf); + } + + pci_cleanup(pacc); + globfree(&g); + + return gpuNames; +} + +static uint64_t toBytes(uint64_t v, uint64_t unit) { + return v * unit; +} + +static std::string formatBytes(uint64_t bytes) { + // If you have more than 1TiB of RAM then holy fucking shit + if (bytes > 1024 * 1024 * 1024 * 1024ULL) + return std::format("{:.1f}TiB", bytes / sc(1024 * 1024 * 1024 * 1024ULL)); + if (bytes > 1024 * 1024 * 1024) + return std::format("{:.1f}GiB", bytes / sc(1024 * 1024 * 1024)); + if (bytes > 1024 * 1024) + return std::format("{:.1f}MiB", bytes / sc(1024 * 1024)); + if (bytes > 1024) + return std::format("{:.1f}KiB", bytes / sc(1024)); + return std::format("{}B", bytes); +} + +std::string Info::mem() { + struct sysinfo info{}; + if (sysinfo(&info) != 0) + return ""; + + const auto TOTAL = toBytes(info.totalram, info.mem_unit); + auto FREE = toBytes(info.freeram + info.bufferram, info.mem_unit); + + if (const auto meminfo = cat("/proc/meminfo"); meminfo) { + CVarList2 vl(std::string{*meminfo}, 0, '\n', true, true); + for (const auto& l : vl) { + if (!l.starts_with("MemAvailable")) + continue; + + CVarList2 ln(std::string{l}, 0, 's', true, false); + + try { + FREE = toBytes(std::stoull(std::string{ln[1]}), 1000); + } catch (...) {} + break; + } + } + + return std::format("{} / {} ({:.0f}% used)", formatBytes(TOTAL - FREE), formatBytes(TOTAL), sc((TOTAL - FREE) / sc(TOTAL)) * 100.F); +} + +std::string Info::uptime() { + struct sysinfo info{}; + if (::sysinfo(&info) != 0) + return ""; + + uint64_t s = sc(info.uptime); + + const auto DAYS = s / 86400; + s %= 86400; + const auto HRS = s / 3600; + s %= 3600; + const auto MINS = s / 60; + s %= 60; + + return std::format("{} days, {} hours, {} minutes", DAYS, HRS, MINS); +} + +std::optional> Info::deLogo() { + const auto DE = desktop(); + + if (DE == "Hyprland") + return std::span{Icons::HYPRLAND_LOGO, sizeof(Icons::HYPRLAND_LOGO)}; + + return std::nullopt; +} diff --git a/src/utils/SystemInfo.hpp b/src/utils/SystemInfo.hpp new file mode 100644 index 0000000..16bfa6c --- /dev/null +++ b/src/utils/SystemInfo.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Info { + std::expected getDistroLogoName(); + std::optional getFromEtcOsRelease(const std::string_view& sv); + std::string kernel(); + std::string desktop(); + std::string desktopHome(); + std::string desktopVersion(); + std::string user(); + std::optional cat(const std::filesystem::path& p); + std::string model(); + std::string cpu(); + std::vector gpus(); + std::string mem(); + std::string uptime(); + std::optional> deLogo(); +}; \ No newline at end of file